summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
Diffstat (limited to 'widget')
-rw-r--r--widget/BasicEvents.h1186
-rw-r--r--widget/CommandList.h52
-rw-r--r--widget/CompositorWidget.cpp84
-rw-r--r--widget/CompositorWidget.h286
-rw-r--r--widget/ContentCache.cpp1381
-rw-r--r--widget/ContentCache.h449
-rw-r--r--widget/ContentEvents.h388
-rw-r--r--widget/EventClassList.h61
-rw-r--r--widget/EventForwards.h173
-rw-r--r--widget/EventMessageList.h457
-rw-r--r--widget/FontRange.h27
-rw-r--r--widget/GfxDriverInfo.cpp314
-rw-r--r--widget/GfxDriverInfo.h300
-rw-r--r--widget/GfxInfoBase.cpp1438
-rw-r--r--widget/GfxInfoBase.h136
-rw-r--r--widget/GfxInfoCollector.cpp54
-rw-r--r--widget/GfxInfoCollector.h94
-rw-r--r--widget/GfxInfoWebGL.cpp69
-rw-r--r--widget/GfxInfoWebGL.h27
-rw-r--r--widget/GfxInfoX11.cpp564
-rw-r--r--widget/GfxInfoX11.h84
-rw-r--r--widget/IMEData.h882
-rw-r--r--widget/InProcessCompositorWidget.cpp145
-rw-r--r--widget/InProcessCompositorWidget.h52
-rw-r--r--widget/InputData.cpp777
-rw-r--r--widget/InputData.h617
-rw-r--r--widget/LSBUtils.cpp81
-rw-r--r--widget/LSBUtils.h28
-rw-r--r--widget/LookAndFeel.h640
-rw-r--r--widget/MiscEvents.h196
-rw-r--r--widget/MouseEvents.h724
-rw-r--r--widget/NativeKeyToDOMCodeName.h821
-rw-r--r--widget/NativeKeyToDOMKeyName.h1144
-rw-r--r--widget/PCompositorWidget.ipdl28
-rw-r--r--widget/PlatformWidgetTypes.ipdlh18
-rw-r--r--widget/PluginWidgetProxy.cpp195
-rw-r--r--widget/PluginWidgetProxy.h78
-rw-r--r--widget/PuppetBidiKeyboard.cpp48
-rw-r--r--widget/PuppetBidiKeyboard.h36
-rw-r--r--widget/PuppetWidget.cpp1580
-rw-r--r--widget/PuppetWidget.h463
-rw-r--r--widget/ScreenProxy.cpp189
-rw-r--r--widget/ScreenProxy.h71
-rw-r--r--widget/SharedWidgetUtils.cpp178
-rw-r--r--widget/SystemTimeConverter.h231
-rw-r--r--widget/TextEventDispatcher.cpp780
-rw-r--r--widget/TextEventDispatcher.h486
-rw-r--r--widget/TextEventDispatcherListener.h89
-rw-r--r--widget/TextEvents.h1014
-rw-r--r--widget/TextRange.h314
-rw-r--r--widget/TouchEvents.h220
-rw-r--r--widget/VsyncDispatcher.cpp187
-rw-r--r--widget/VsyncDispatcher.h103
-rw-r--r--widget/WidgetEventImpl.cpp1075
-rw-r--r--widget/WidgetMessageUtils.h39
-rw-r--r--widget/WidgetTraceEvent.h27
-rw-r--r--widget/WidgetUtils.cpp141
-rw-r--r--widget/WidgetUtils.h99
-rw-r--r--widget/WindowSurface.h37
-rw-r--r--widget/WindowSurfaceX11SHM.cpp33
-rw-r--r--widget/WindowSurfaceX11SHM.h35
-rw-r--r--widget/android/ANRReporter.cpp89
-rw-r--r--widget/android/ANRReporter.h26
-rw-r--r--widget/android/AndroidAlerts.cpp126
-rw-r--r--widget/android/AndroidAlerts.h44
-rw-r--r--widget/android/AndroidBridge.cpp1126
-rw-r--r--widget/android/AndroidBridge.h419
-rw-r--r--widget/android/AndroidBridgeUtilities.h13
-rw-r--r--widget/android/AndroidCompositorWidget.cpp63
-rw-r--r--widget/android/AndroidCompositorWidget.h44
-rw-r--r--widget/android/AndroidContentController.cpp157
-rw-r--r--widget/android/AndroidContentController.h59
-rw-r--r--widget/android/AndroidDirectTexture.h58
-rw-r--r--widget/android/AndroidGraphicBuffer.h72
-rw-r--r--widget/android/AndroidJNI.cpp47
-rw-r--r--widget/android/AndroidJNIWrapper.cpp140
-rw-r--r--widget/android/AndroidJNIWrapper.h34
-rw-r--r--widget/android/AndroidJavaWrappers.cpp62
-rw-r--r--widget/android/AndroidJavaWrappers.h218
-rw-r--r--widget/android/GeckoBatteryManager.h30
-rw-r--r--widget/android/GeckoNetworkManager.h53
-rw-r--r--widget/android/GeckoScreenOrientation.h55
-rw-r--r--widget/android/GeneratedJNINatives.h526
-rw-r--r--widget/android/GeneratedJNIWrappers.cpp1874
-rw-r--r--widget/android/GeneratedJNIWrappers.h5456
-rw-r--r--widget/android/GfxInfo.cpp621
-rw-r--r--widget/android/GfxInfo.h102
-rw-r--r--widget/android/NativeJSContainer.cpp881
-rw-r--r--widget/android/NativeJSContainer.h22
-rw-r--r--widget/android/PrefsHelper.h324
-rw-r--r--widget/android/bindings/AndroidRect-classes.txt2
-rw-r--r--widget/android/bindings/Bundle-classes.txt1
-rw-r--r--widget/android/bindings/KeyEvent-classes.txt1
-rw-r--r--widget/android/bindings/Makefile.in27
-rw-r--r--widget/android/bindings/MediaCodec-classes.txt5
-rw-r--r--widget/android/bindings/MotionEvent-classes.txt1
-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.build41
-rw-r--r--widget/android/fennec/FennecJNINatives.h244
-rw-r--r--widget/android/fennec/FennecJNIWrappers.cpp443
-rw-r--r--widget/android/fennec/FennecJNIWrappers.h1469
-rw-r--r--widget/android/fennec/MemoryMonitor.h27
-rw-r--r--widget/android/fennec/Telemetry.h100
-rw-r--r--widget/android/fennec/ThumbnailHelper.h302
-rw-r--r--widget/android/fennec/moz.build21
-rw-r--r--widget/android/jni/Accessors.h274
-rw-r--r--widget/android/jni/Natives.h707
-rw-r--r--widget/android/jni/Refs.h953
-rw-r--r--widget/android/jni/Types.h140
-rw-r--r--widget/android/jni/Utils.cpp301
-rw-r--r--widget/android/jni/Utils.h147
-rw-r--r--widget/android/jni/moz.build24
-rw-r--r--widget/android/moz.build73
-rw-r--r--widget/android/nsAndroidProtocolHandler.cpp183
-rw-r--r--widget/android/nsAndroidProtocolHandler.h37
-rw-r--r--widget/android/nsAppShell.cpp688
-rw-r--r--widget/android/nsAppShell.h223
-rw-r--r--widget/android/nsClipboard.cpp123
-rw-r--r--widget/android/nsClipboard.h23
-rw-r--r--widget/android/nsDeviceContextAndroid.cpp84
-rw-r--r--widget/android/nsDeviceContextAndroid.h32
-rw-r--r--widget/android/nsIAndroidBridge.idl42
-rw-r--r--widget/android/nsIdleServiceAndroid.cpp23
-rw-r--r--widget/android/nsIdleServiceAndroid.h36
-rw-r--r--widget/android/nsLookAndFeel.cpp500
-rw-r--r--widget/android/nsLookAndFeel.h36
-rw-r--r--widget/android/nsPrintOptionsAndroid.cpp37
-rw-r--r--widget/android/nsPrintOptionsAndroid.h23
-rw-r--r--widget/android/nsScreenManagerAndroid.cpp264
-rw-r--r--widget/android/nsScreenManagerAndroid.h66
-rw-r--r--widget/android/nsWidgetFactory.cpp132
-rw-r--r--widget/android/nsWindow.cpp3638
-rw-r--r--widget/android/nsWindow.h289
-rw-r--r--widget/cocoa/ComplexTextInputPanel.h48
-rw-r--r--widget/cocoa/ComplexTextInputPanel.mm261
-rw-r--r--widget/cocoa/CustomCocoaEvents.h18
-rw-r--r--widget/cocoa/GfxInfo.h95
-rw-r--r--widget/cocoa/GfxInfo.mm433
-rw-r--r--widget/cocoa/NativeKeyBindings.h48
-rw-r--r--widget/cocoa/NativeKeyBindings.mm292
-rw-r--r--widget/cocoa/OSXNotificationCenter.h55
-rw-r--r--widget/cocoa/OSXNotificationCenter.mm589
-rw-r--r--widget/cocoa/RectTextureImage.h80
-rw-r--r--widget/cocoa/RectTextureImage.mm171
-rw-r--r--widget/cocoa/SwipeTracker.h96
-rw-r--r--widget/cocoa/SwipeTracker.mm220
-rw-r--r--widget/cocoa/TextInputHandler.h1195
-rw-r--r--widget/cocoa/TextInputHandler.mm4534
-rw-r--r--widget/cocoa/VibrancyManager.h120
-rw-r--r--widget/cocoa/VibrancyManager.mm271
-rw-r--r--widget/cocoa/ViewRegion.h53
-rw-r--r--widget/cocoa/ViewRegion.mm71
-rw-r--r--widget/cocoa/WidgetTraceEvent.mm85
-rw-r--r--widget/cocoa/crashtests/373122-1-inner.html39
-rw-r--r--widget/cocoa/crashtests/373122-1.html9
-rw-r--r--widget/cocoa/crashtests/397209-1.html7
-rw-r--r--widget/cocoa/crashtests/403296-1.xhtml10
-rw-r--r--widget/cocoa/crashtests/419737-1.html8
-rw-r--r--widget/cocoa/crashtests/435223-1.html8
-rw-r--r--widget/cocoa/crashtests/444260-1.xul3
-rw-r--r--widget/cocoa/crashtests/444864-1.html6
-rw-r--r--widget/cocoa/crashtests/449111-1.html4
-rw-r--r--widget/cocoa/crashtests/460349-1.xhtml4
-rw-r--r--widget/cocoa/crashtests/460387-1.html2
-rw-r--r--widget/cocoa/crashtests/464589-1.html20
-rw-r--r--widget/cocoa/crashtests/crashtests.list11
-rw-r--r--widget/cocoa/cursors/arrowN.pngbin0 -> 256 bytes
-rw-r--r--widget/cocoa/cursors/arrowN@2x.pngbin0 -> 655 bytes
-rw-r--r--widget/cocoa/cursors/arrowS.pngbin0 -> 256 bytes
-rw-r--r--widget/cocoa/cursors/arrowS@2x.pngbin0 -> 649 bytes
-rw-r--r--widget/cocoa/cursors/cell.pngbin0 -> 268 bytes
-rw-r--r--widget/cocoa/cursors/cell@2x.pngbin0 -> 647 bytes
-rw-r--r--widget/cocoa/cursors/colResize.pngbin0 -> 321 bytes
-rw-r--r--widget/cocoa/cursors/colResize@2x.pngbin0 -> 836 bytes
-rw-r--r--widget/cocoa/cursors/help.pngbin0 -> 723 bytes
-rw-r--r--widget/cocoa/cursors/help@2x.pngbin0 -> 1693 bytes
-rw-r--r--widget/cocoa/cursors/move.pngbin0 -> 284 bytes
-rw-r--r--widget/cocoa/cursors/move@2x.pngbin0 -> 621 bytes
-rw-r--r--widget/cocoa/cursors/rowResize.pngbin0 -> 331 bytes
-rw-r--r--widget/cocoa/cursors/rowResize@2x.pngbin0 -> 852 bytes
-rw-r--r--widget/cocoa/cursors/sizeNE.pngbin0 -> 279 bytes
-rw-r--r--widget/cocoa/cursors/sizeNE@2x.pngbin0 -> 794 bytes
-rw-r--r--widget/cocoa/cursors/sizeNESW.pngbin0 -> 300 bytes
-rw-r--r--widget/cocoa/cursors/sizeNESW@2x.pngbin0 -> 975 bytes
-rw-r--r--widget/cocoa/cursors/sizeNS.pngbin0 -> 282 bytes
-rw-r--r--widget/cocoa/cursors/sizeNS@2x.pngbin0 -> 660 bytes
-rw-r--r--widget/cocoa/cursors/sizeNW.pngbin0 -> 278 bytes
-rw-r--r--widget/cocoa/cursors/sizeNW@2x.pngbin0 -> 790 bytes
-rw-r--r--widget/cocoa/cursors/sizeNWSE.pngbin0 -> 295 bytes
-rw-r--r--widget/cocoa/cursors/sizeNWSE@2x.pngbin0 -> 976 bytes
-rw-r--r--widget/cocoa/cursors/sizeSE.pngbin0 -> 270 bytes
-rw-r--r--widget/cocoa/cursors/sizeSE@2x.pngbin0 -> 802 bytes
-rw-r--r--widget/cocoa/cursors/sizeSW.pngbin0 -> 271 bytes
-rw-r--r--widget/cocoa/cursors/sizeSW@2x.pngbin0 -> 806 bytes
-rw-r--r--widget/cocoa/cursors/vtIBeam.pngbin0 -> 120 bytes
-rw-r--r--widget/cocoa/cursors/vtIBeam@2x.pngbin0 -> 336 bytes
-rw-r--r--widget/cocoa/cursors/zoomIn.pngbin0 -> 655 bytes
-rw-r--r--widget/cocoa/cursors/zoomIn@2x.pngbin0 -> 1717 bytes
-rw-r--r--widget/cocoa/cursors/zoomOut.pngbin0 -> 650 bytes
-rw-r--r--widget/cocoa/cursors/zoomOut@2x.pngbin0 -> 1714 bytes
-rw-r--r--widget/cocoa/moz.build141
-rw-r--r--widget/cocoa/mozView.h67
-rw-r--r--widget/cocoa/nsAppShell.h71
-rw-r--r--widget/cocoa/nsAppShell.mm907
-rw-r--r--widget/cocoa/nsBidiKeyboard.h24
-rw-r--r--widget/cocoa/nsBidiKeyboard.mm42
-rw-r--r--widget/cocoa/nsChangeObserver.h44
-rw-r--r--widget/cocoa/nsChildView.h664
-rw-r--r--widget/cocoa/nsChildView.mm6580
-rw-r--r--widget/cocoa/nsClipboard.h55
-rw-r--r--widget/cocoa/nsClipboard.mm775
-rw-r--r--widget/cocoa/nsCocoaDebugUtils.h136
-rw-r--r--widget/cocoa/nsCocoaDebugUtils.mm284
-rw-r--r--widget/cocoa/nsCocoaFeatures.h42
-rw-r--r--widget/cocoa/nsCocoaFeatures.mm174
-rw-r--r--widget/cocoa/nsCocoaUtils.h389
-rw-r--r--widget/cocoa/nsCocoaUtils.mm1022
-rw-r--r--widget/cocoa/nsCocoaWindow.h423
-rw-r--r--widget/cocoa/nsCocoaWindow.mm3834
-rw-r--r--widget/cocoa/nsColorPicker.h50
-rw-r--r--widget/cocoa/nsColorPicker.mm188
-rw-r--r--widget/cocoa/nsCursorManager.h65
-rw-r--r--widget/cocoa/nsCursorManager.mm308
-rw-r--r--widget/cocoa/nsDeviceContextSpecX.h41
-rw-r--r--widget/cocoa/nsDeviceContextSpecX.mm165
-rw-r--r--widget/cocoa/nsDragService.h55
-rw-r--r--widget/cocoa/nsDragService.mm669
-rw-r--r--widget/cocoa/nsFilePicker.h74
-rw-r--r--widget/cocoa/nsFilePicker.mm676
-rw-r--r--widget/cocoa/nsIdleServiceX.h33
-rw-r--r--widget/cocoa/nsIdleServiceX.mm77
-rw-r--r--widget/cocoa/nsLookAndFeel.h46
-rw-r--r--widget/cocoa/nsLookAndFeel.mm581
-rw-r--r--widget/cocoa/nsMacCursor.h105
-rw-r--r--widget/cocoa/nsMacCursor.mm382
-rw-r--r--widget/cocoa/nsMacDockSupport.h41
-rw-r--r--widget/cocoa/nsMacDockSupport.mm174
-rw-r--r--widget/cocoa/nsMacWebAppUtils.h22
-rw-r--r--widget/cocoa/nsMacWebAppUtils.mm82
-rw-r--r--widget/cocoa/nsMenuBarX.h128
-rw-r--r--widget/cocoa/nsMenuBarX.mm979
-rw-r--r--widget/cocoa/nsMenuBaseX.h79
-rw-r--r--widget/cocoa/nsMenuGroupOwnerX.h61
-rw-r--r--widget/cocoa/nsMenuGroupOwnerX.mm261
-rw-r--r--widget/cocoa/nsMenuItemIconX.h66
-rw-r--r--widget/cocoa/nsMenuItemIconX.mm466
-rw-r--r--widget/cocoa/nsMenuItemX.h75
-rw-r--r--widget/cocoa/nsMenuItemX.mm369
-rw-r--r--widget/cocoa/nsMenuUtilsX.h31
-rw-r--r--widget/cocoa/nsMenuUtilsX.mm223
-rw-r--r--widget/cocoa/nsMenuX.h101
-rw-r--r--widget/cocoa/nsMenuX.mm1051
-rw-r--r--widget/cocoa/nsNativeThemeCocoa.h178
-rw-r--r--widget/cocoa/nsNativeThemeCocoa.mm3931
-rw-r--r--widget/cocoa/nsNativeThemeColors.h57
-rw-r--r--widget/cocoa/nsPIWidgetCocoa.idl37
-rw-r--r--widget/cocoa/nsPrintDialogX.h68
-rw-r--r--widget/cocoa/nsPrintDialogX.mm682
-rw-r--r--widget/cocoa/nsPrintOptionsX.h44
-rw-r--r--widget/cocoa/nsPrintOptionsX.mm349
-rw-r--r--widget/cocoa/nsPrintSettingsX.h82
-rw-r--r--widget/cocoa/nsPrintSettingsX.mm272
-rw-r--r--widget/cocoa/nsSandboxViolationSink.h36
-rw-r--r--widget/cocoa/nsSandboxViolationSink.mm115
-rw-r--r--widget/cocoa/nsScreenCocoa.h41
-rw-r--r--widget/cocoa/nsScreenCocoa.mm147
-rw-r--r--widget/cocoa/nsScreenManagerCocoa.h33
-rw-r--r--widget/cocoa/nsScreenManagerCocoa.mm152
-rw-r--r--widget/cocoa/nsSound.h27
-rw-r--r--widget/cocoa/nsSound.mm108
-rw-r--r--widget/cocoa/nsStandaloneNativeMenu.h40
-rw-r--r--widget/cocoa/nsStandaloneNativeMenu.mm213
-rw-r--r--widget/cocoa/nsSystemStatusBarCocoa.h40
-rw-r--r--widget/cocoa/nsSystemStatusBarCocoa.mm74
-rw-r--r--widget/cocoa/nsToolkit.h53
-rw-r--r--widget/cocoa/nsToolkit.mm326
-rw-r--r--widget/cocoa/nsWidgetFactory.mm219
-rw-r--r--widget/cocoa/nsWindowMap.h62
-rw-r--r--widget/cocoa/nsWindowMap.mm311
-rw-r--r--widget/cocoa/resources/MainMenu.nib/classes.nib4
-rw-r--r--widget/cocoa/resources/MainMenu.nib/info.nib21
-rw-r--r--widget/cocoa/resources/MainMenu.nib/keyedobjects.nibbin0 -> 1877 bytes
-rw-r--r--widget/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/gonk/GeckoTouchDispatcher.cpp358
-rw-r--r--widget/gonk/GeckoTouchDispatcher.h99
-rw-r--r--widget/gonk/GfxInfo.cpp194
-rw-r--r--widget/gonk/GfxInfo.h69
-rw-r--r--widget/gonk/GonkClipboardData.cpp75
-rw-r--r--widget/gonk/GonkClipboardData.h49
-rw-r--r--widget/gonk/GonkKeyMapping.h301
-rw-r--r--widget/gonk/GonkMemoryPressureMonitoring.cpp359
-rw-r--r--widget/gonk/GonkMemoryPressureMonitoring.h14
-rw-r--r--widget/gonk/GonkPermission.cpp195
-rw-r--r--widget/gonk/GonkPermission.h86
-rw-r--r--widget/gonk/HwcComposer2D.cpp971
-rw-r--r--widget/gonk/HwcComposer2D.h123
-rw-r--r--widget/gonk/HwcUtils.cpp169
-rw-r--r--widget/gonk/HwcUtils.h135
-rw-r--r--widget/gonk/OrientationObserver.cpp332
-rw-r--r--widget/gonk/OrientationObserver.h71
-rw-r--r--widget/gonk/ProcessOrientation.cpp519
-rw-r--r--widget/gonk/ProcessOrientation.h111
-rw-r--r--widget/gonk/WidgetTraceEvent.cpp96
-rw-r--r--widget/gonk/hwchal/HwcHAL.cpp214
-rw-r--r--widget/gonk/hwchal/HwcHAL.h70
-rw-r--r--widget/gonk/hwchal/HwcHALBase.h134
-rw-r--r--widget/gonk/libdisplay/BootAnimation.cpp726
-rw-r--r--widget/gonk/libdisplay/BootAnimation.h30
-rw-r--r--widget/gonk/libdisplay/DisplaySurface.h112
-rw-r--r--widget/gonk/libdisplay/FramebufferSurface.cpp207
-rw-r--r--widget/gonk/libdisplay/FramebufferSurface.h93
-rw-r--r--widget/gonk/libdisplay/GonkDisplay.h91
-rw-r--r--widget/gonk/libdisplay/GonkDisplayJB.cpp461
-rw-r--r--widget/gonk/libdisplay/GonkDisplayJB.h85
-rw-r--r--widget/gonk/libdisplay/GraphicBufferAlloc.cpp53
-rw-r--r--widget/gonk/libdisplay/GraphicBufferAlloc.h44
-rw-r--r--widget/gonk/libdisplay/VirtualDisplaySurface.cpp635
-rw-r--r--widget/gonk/libdisplay/VirtualDisplaySurface.h250
-rw-r--r--widget/gonk/libdisplay/moz.build59
-rw-r--r--widget/gonk/libui/EventHub.cpp1549
-rw-r--r--widget/gonk/libui/EventHub.h435
-rw-r--r--widget/gonk/libui/Input.cpp635
-rw-r--r--widget/gonk/libui/Input.h622
-rw-r--r--widget/gonk/libui/InputApplication.cpp42
-rw-r--r--widget/gonk/libui/InputApplication.h83
-rw-r--r--widget/gonk/libui/InputDevice.cpp184
-rw-r--r--widget/gonk/libui/InputDevice.h156
-rw-r--r--widget/gonk/libui/InputDispatcher.cpp4430
-rw-r--r--widget/gonk/libui/InputDispatcher.h1117
-rw-r--r--widget/gonk/libui/InputListener.cpp182
-rw-r--r--widget/gonk/libui/InputListener.h196
-rw-r--r--widget/gonk/libui/InputManager.cpp93
-rw-r--r--widget/gonk/libui/InputManager.h109
-rw-r--r--widget/gonk/libui/InputReader.cpp6510
-rw-r--r--widget/gonk/libui/InputReader.h1811
-rw-r--r--widget/gonk/libui/InputTransport.cpp957
-rw-r--r--widget/gonk/libui/InputTransport.h443
-rw-r--r--widget/gonk/libui/InputWindow.cpp64
-rw-r--r--widget/gonk/libui/InputWindow.h205
-rw-r--r--widget/gonk/libui/KeyCharacterMap.cpp1153
-rw-r--r--widget/gonk/libui/KeyCharacterMap.h257
-rw-r--r--widget/gonk/libui/KeyLayoutMap.cpp446
-rw-r--r--widget/gonk/libui/KeyLayoutMap.h117
-rw-r--r--widget/gonk/libui/Keyboard.cpp300
-rw-r--r--widget/gonk/libui/Keyboard.h122
-rw-r--r--widget/gonk/libui/KeycodeLabels.h380
-rw-r--r--widget/gonk/libui/PointerController.cpp604
-rw-r--r--widget/gonk/libui/PointerController.h266
-rw-r--r--widget/gonk/libui/PowerManager.h33
-rw-r--r--widget/gonk/libui/SpriteController.cpp515
-rw-r--r--widget/gonk/libui/SpriteController.h319
-rw-r--r--widget/gonk/libui/Tokenizer.cpp175
-rw-r--r--widget/gonk/libui/Tokenizer.h136
-rw-r--r--widget/gonk/libui/Trace.h64
-rw-r--r--widget/gonk/libui/VelocityControl.cpp110
-rw-r--r--widget/gonk/libui/VelocityControl.h107
-rw-r--r--widget/gonk/libui/VelocityTracker.cpp929
-rw-r--r--widget/gonk/libui/VelocityTracker.h269
-rw-r--r--widget/gonk/libui/VirtualKeyMap.cpp171
-rw-r--r--widget/gonk/libui/VirtualKeyMap.h81
-rw-r--r--widget/gonk/libui/android_input.h850
-rw-r--r--widget/gonk/libui/android_keycodes.h315
-rw-r--r--widget/gonk/libui/cutils_log.h569
-rw-r--r--widget/gonk/libui/cutils_trace.h276
-rw-r--r--widget/gonk/libui/linux_input.h1029
-rw-r--r--widget/gonk/libui/sha1.c289
-rw-r--r--widget/gonk/libui/sha1.h31
-rw-r--r--widget/gonk/moz.build96
-rw-r--r--widget/gonk/nativewindow/FakeSurfaceComposer.cpp703
-rw-r--r--widget/gonk/nativewindow/FakeSurfaceComposer.h175
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueue.h22
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueJB.cpp1036
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueJB.h653
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueKK.cpp1265
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueKK.h583
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.cpp193
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.h101
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.cpp559
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.h173
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.cpp243
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.h251
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueDefs.h36
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.cpp96
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.h94
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.cpp886
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.h216
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.cpp32
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.h132
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseJB.cpp245
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseJB.h239
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseKK.cpp252
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseKK.h240
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseLL.cpp257
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseLL.h245
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindow.h22
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowJB.cpp180
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowJB.h133
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowKK.cpp182
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowKK.h135
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowLL.cpp204
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowLL.h133
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumer.h20
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.cpp480
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.h228
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.cpp565
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.h337
-rw-r--r--widget/gonk/nativewindow/moz.build104
-rw-r--r--widget/gonk/nsAppShell.cpp1087
-rw-r--r--widget/gonk/nsAppShell.h112
-rw-r--r--widget/gonk/nsClipboard.cpp366
-rw-r--r--widget/gonk/nsClipboard.h27
-rw-r--r--widget/gonk/nsIdleServiceGonk.cpp33
-rw-r--r--widget/gonk/nsIdleServiceGonk.h47
-rw-r--r--widget/gonk/nsLookAndFeel.cpp465
-rw-r--r--widget/gonk/nsLookAndFeel.h40
-rw-r--r--widget/gonk/nsScreenManagerGonk.cpp1081
-rw-r--r--widget/gonk/nsScreenManagerGonk.h228
-rw-r--r--widget/gonk/nsWidgetFactory.cpp128
-rw-r--r--widget/gonk/nsWindow.cpp744
-rw-r--r--widget/gonk/nsWindow.h154
-rw-r--r--widget/gtk/CompositorWidgetChild.cpp45
-rw-r--r--widget/gtk/CompositorWidgetChild.h38
-rw-r--r--widget/gtk/CompositorWidgetParent.cpp48
-rw-r--r--widget/gtk/CompositorWidgetParent.h37
-rw-r--r--widget/gtk/IMContextWrapper.cpp2359
-rw-r--r--widget/gtk/IMContextWrapper.h481
-rw-r--r--widget/gtk/InProcessX11CompositorWidget.cpp34
-rw-r--r--widget/gtk/InProcessX11CompositorWidget.h30
-rw-r--r--widget/gtk/NativeKeyBindings.cpp374
-rw-r--r--widget/gtk/NativeKeyBindings.h49
-rw-r--r--widget/gtk/PCompositorWidget.ipdl30
-rw-r--r--widget/gtk/PlatformWidgetTypes.ipdlh24
-rw-r--r--widget/gtk/WakeLockListener.cpp365
-rw-r--r--widget/gtk/WakeLockListener.h53
-rw-r--r--widget/gtk/WidgetStyleCache.cpp1162
-rw-r--r--widget/gtk/WidgetStyleCache.h48
-rw-r--r--widget/gtk/WidgetTraceEvent.cpp78
-rw-r--r--widget/gtk/WidgetUtilsGtk.cpp51
-rw-r--r--widget/gtk/WidgetUtilsGtk.h25
-rw-r--r--widget/gtk/WindowSurfaceProvider.cpp114
-rw-r--r--widget/gtk/WindowSurfaceProvider.h69
-rw-r--r--widget/gtk/WindowSurfaceX11.cpp64
-rw-r--r--widget/gtk/WindowSurfaceX11.h39
-rw-r--r--widget/gtk/WindowSurfaceX11Image.cpp119
-rw-r--r--widget/gtk/WindowSurfaceX11Image.h38
-rw-r--r--widget/gtk/WindowSurfaceXRender.cpp83
-rw-r--r--widget/gtk/WindowSurfaceXRender.h36
-rw-r--r--widget/gtk/X11CompositorWidget.cpp108
-rw-r--r--widget/gtk/X11CompositorWidget.h69
-rw-r--r--widget/gtk/compat-gtk3/gdk/gdkversionmacros.h31
-rw-r--r--widget/gtk/compat-gtk3/gtk/gtkenums.h28
-rw-r--r--widget/gtk/compat/gdk/gdkdnd.h33
-rw-r--r--widget/gtk/compat/gdk/gdkkeysyms.h266
-rw-r--r--widget/gtk/compat/gdk/gdkvisual.h17
-rw-r--r--widget/gtk/compat/gdk/gdkwindow.h33
-rw-r--r--widget/gtk/compat/gdk/gdkx.h51
-rw-r--r--widget/gtk/compat/glib/gmem.h56
-rw-r--r--widget/gtk/compat/gtk/gtkwidget.h50
-rw-r--r--widget/gtk/compat/gtk/gtkwindow.h30
-rw-r--r--widget/gtk/crashtests/540078-1.xhtml2
-rw-r--r--widget/gtk/crashtests/673390-1.html1
-rw-r--r--widget/gtk/crashtests/crashtests.list2
-rw-r--r--widget/gtk/gtk2drawing.c3502
-rw-r--r--widget/gtk/gtk3drawing.cpp2843
-rw-r--r--widget/gtk/gtkdrawing.h542
-rw-r--r--widget/gtk/maiRedundantObjectFactory.c93
-rw-r--r--widget/gtk/maiRedundantObjectFactory.h32
-rw-r--r--widget/gtk/moz.build141
-rw-r--r--widget/gtk/mozcontainer.c418
-rw-r--r--widget/gtk/mozcontainer.h86
-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.c634
-rw-r--r--widget/gtk/mozgtk/stub/moz.build16
-rw-r--r--widget/gtk/nsAppShell.cpp271
-rw-r--r--widget/gtk/nsAppShell.h37
-rw-r--r--widget/gtk/nsApplicationChooser.cpp128
-rw-r--r--widget/gtk/nsApplicationChooser.h32
-rw-r--r--widget/gtk/nsBidiKeyboard.cpp55
-rw-r--r--widget/gtk/nsBidiKeyboard.h26
-rw-r--r--widget/gtk/nsCUPSShim.cpp59
-rw-r--r--widget/gtk/nsCUPSShim.h86
-rw-r--r--widget/gtk/nsClipboard.cpp1046
-rw-r--r--widget/gtk/nsClipboard.h58
-rw-r--r--widget/gtk/nsColorPicker.cpp252
-rw-r--r--widget/gtk/nsColorPicker.h73
-rw-r--r--widget/gtk/nsDeviceContextSpecG.cpp498
-rw-r--r--widget/gtk/nsDeviceContextSpecG.h76
-rw-r--r--widget/gtk/nsDragService.cpp2109
-rw-r--r--widget/gtk/nsDragService.h226
-rw-r--r--widget/gtk/nsFilePicker.cpp610
-rw-r--r--widget/gtk/nsFilePicker.h82
-rw-r--r--widget/gtk/nsGTKToolkit.h54
-rw-r--r--widget/gtk/nsGtkCursors.h405
-rw-r--r--widget/gtk/nsGtkKeyUtils.cpp1483
-rw-r--r--widget/gtk/nsGtkKeyUtils.h360
-rw-r--r--widget/gtk/nsGtkUtils.h24
-rw-r--r--widget/gtk/nsIImageToPixbuf.h34
-rw-r--r--widget/gtk/nsIdleServiceGTK.cpp132
-rw-r--r--widget/gtk/nsIdleServiceGTK.h52
-rw-r--r--widget/gtk/nsImageToPixbuf.cpp120
-rw-r--r--widget/gtk/nsImageToPixbuf.h46
-rw-r--r--widget/gtk/nsLookAndFeel.cpp1465
-rw-r--r--widget/gtk/nsLookAndFeel.h91
-rw-r--r--widget/gtk/nsNativeThemeGTK.cpp1994
-rw-r--r--widget/gtk/nsNativeThemeGTK.h93
-rw-r--r--widget/gtk/nsPSPrinters.cpp123
-rw-r--r--widget/gtk/nsPSPrinters.h53
-rw-r--r--widget/gtk/nsPaperPS.cpp43
-rw-r--r--widget/gtk/nsPaperPS.h93
-rw-r--r--widget/gtk/nsPrintDialogGTK.cpp609
-rw-r--r--widget/gtk/nsPrintDialogGTK.h39
-rw-r--r--widget/gtk/nsPrintOptionsGTK.cpp102
-rw-r--r--widget/gtk/nsPrintOptionsGTK.h40
-rw-r--r--widget/gtk/nsPrintSettingsGTK.cpp813
-rw-r--r--widget/gtk/nsPrintSettingsGTK.h151
-rw-r--r--widget/gtk/nsScreenGtk.cpp207
-rw-r--r--widget/gtk/nsScreenGtk.h57
-rw-r--r--widget/gtk/nsScreenManagerGtk.cpp366
-rw-r--r--widget/gtk/nsScreenManagerGtk.h52
-rw-r--r--widget/gtk/nsSound.cpp444
-rw-r--r--widget/gtk/nsSound.h35
-rw-r--r--widget/gtk/nsToolkit.cpp34
-rw-r--r--widget/gtk/nsWidgetFactory.cpp331
-rw-r--r--widget/gtk/nsWindow.cpp7036
-rw-r--r--widget/gtk/nsWindow.h580
-rw-r--r--widget/gtkxtbin/gtk2xtbin.c860
-rw-r--r--widget/gtkxtbin/gtk2xtbin.h131
-rw-r--r--widget/gtkxtbin/moz.build19
-rw-r--r--widget/gtkxtbin/xembed.h32
-rw-r--r--widget/moz.build286
-rw-r--r--widget/nsAppShellSingleton.h69
-rw-r--r--widget/nsBaseAppShell.cpp349
-rw-r--r--widget/nsBaseAppShell.h135
-rw-r--r--widget/nsBaseClipboard.cpp125
-rw-r--r--widget/nsBaseClipboard.h46
-rw-r--r--widget/nsBaseDragService.cpp828
-rw-r--r--widget/nsBaseDragService.h195
-rw-r--r--widget/nsBaseFilePicker.cpp393
-rw-r--r--widget/nsBaseFilePicker.h58
-rw-r--r--widget/nsBaseScreen.cpp90
-rw-r--r--widget/nsBaseScreen.h81
-rw-r--r--widget/nsBaseWidget.cpp3326
-rw-r--r--widget/nsBaseWidget.h753
-rw-r--r--widget/nsClipboardHelper.cpp133
-rw-r--r--widget/nsClipboardHelper.h32
-rw-r--r--widget/nsClipboardProxy.cpp169
-rw-r--r--widget/nsClipboardProxy.h46
-rw-r--r--widget/nsColorPickerProxy.cpp58
-rw-r--r--widget/nsColorPickerProxy.h33
-rw-r--r--widget/nsContentProcessWidgetFactory.cpp77
-rw-r--r--widget/nsDatePickerProxy.cpp61
-rw-r--r--widget/nsDatePickerProxy.h33
-rw-r--r--widget/nsDeviceContextSpecProxy.cpp232
-rw-r--r--widget/nsDeviceContextSpecProxy.h72
-rw-r--r--widget/nsDragServiceProxy.cpp81
-rw-r--r--widget/nsDragServiceProxy.h26
-rw-r--r--widget/nsFilePickerProxy.cpp273
-rw-r--r--widget/nsFilePickerProxy.h75
-rw-r--r--widget/nsGUIEventIPC.h1348
-rw-r--r--widget/nsHTMLFormatConverter.cpp247
-rw-r--r--widget/nsHTMLFormatConverter.h35
-rw-r--r--widget/nsIAppShell.idl76
-rw-r--r--widget/nsIApplicationChooser.idl40
-rw-r--r--widget/nsIBaseWindow.idl230
-rw-r--r--widget/nsIBidiKeyboard.idl32
-rw-r--r--widget/nsIClipboard.idl92
-rw-r--r--widget/nsIClipboardDragDropHookList.idl45
-rw-r--r--widget/nsIClipboardDragDropHooks.idl101
-rw-r--r--widget/nsIClipboardHelper.idl38
-rw-r--r--widget/nsIClipboardOwner.idl29
-rw-r--r--widget/nsIColorPicker.idl72
-rw-r--r--widget/nsIDatePicker.idl50
-rw-r--r--widget/nsIDeviceContextSpec.h85
-rw-r--r--widget/nsIDisplayInfo.idl14
-rw-r--r--widget/nsIDragService.idl152
-rw-r--r--widget/nsIDragSession.idl97
-rw-r--r--widget/nsIFilePicker.idl194
-rw-r--r--widget/nsIFormatConverter.idl52
-rw-r--r--widget/nsIGfxInfo.idl249
-rw-r--r--widget/nsIGfxInfoDebug.idl19
-rw-r--r--widget/nsIIdleService.idl78
-rw-r--r--widget/nsIIdleServiceInternal.idl18
-rw-r--r--widget/nsIJumpListBuilder.idl152
-rw-r--r--widget/nsIJumpListItem.idl137
-rw-r--r--widget/nsIKeyEventInPluginCallback.h42
-rw-r--r--widget/nsIMacDockSupport.idl39
-rw-r--r--widget/nsIMacWebAppUtils.idl35
-rw-r--r--widget/nsINativeMenuService.h30
-rw-r--r--widget/nsIPluginWidget.h41
-rw-r--r--widget/nsIPrintDialogService.h73
-rw-r--r--widget/nsIPrintSession.idl40
-rw-r--r--widget/nsIPrintSettings.idl305
-rw-r--r--widget/nsIPrintSettingsService.idl157
-rw-r--r--widget/nsIPrintSettingsWin.idl69
-rw-r--r--widget/nsIPrinterEnumerator.idl37
-rw-r--r--widget/nsIRollupListener.h71
-rw-r--r--widget/nsIScreen.idl109
-rw-r--r--widget/nsIScreenManager.idl64
-rw-r--r--widget/nsISound.idl67
-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.idl112
-rw-r--r--widget/nsITaskbarProgress.idl58
-rw-r--r--widget/nsITaskbarTabPreview.idl63
-rw-r--r--widget/nsITaskbarWindowPreview.idl70
-rw-r--r--widget/nsITransferable.idl212
-rw-r--r--widget/nsIWidget.h2043
-rw-r--r--widget/nsIWidgetListener.cpp131
-rw-r--r--widget/nsIWidgetListener.h165
-rw-r--r--widget/nsIWinTaskbar.idl178
-rw-r--r--widget/nsIWindowsUIUtils.idl24
-rw-r--r--widget/nsIdleService.cpp897
-rw-r--r--widget/nsIdleService.h225
-rw-r--r--widget/nsNativeTheme.cpp775
-rw-r--r--widget/nsNativeTheme.h187
-rw-r--r--widget/nsPrimitiveHelpers.cpp208
-rw-r--r--widget/nsPrimitiveHelpers.h55
-rw-r--r--widget/nsPrintOptionsImpl.cpp1418
-rw-r--r--widget/nsPrintOptionsImpl.h93
-rw-r--r--widget/nsPrintSession.cpp48
-rw-r--r--widget/nsPrintSession.h42
-rw-r--r--widget/nsPrintSettingsImpl.cpp1019
-rw-r--r--widget/nsPrintSettingsImpl.h102
-rw-r--r--widget/nsScreenManagerProxy.cpp218
-rw-r--r--widget/nsScreenManagerProxy.h67
-rw-r--r--widget/nsShmImage.cpp338
-rw-r--r--widget/nsShmImage.h75
-rw-r--r--widget/nsTransferable.cpp664
-rw-r--r--widget/nsTransferable.h86
-rw-r--r--widget/nsWidgetInitData.h136
-rw-r--r--widget/nsWidgetsCID.h196
-rw-r--r--widget/nsXPLookAndFeel.cpp982
-rw-r--r--widget/nsXPLookAndFeel.h120
-rw-r--r--widget/reftests/507947.html2
-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.html66
-rw-r--r--widget/reftests/progressbar-fallback-default-style.html20
-rw-r--r--widget/reftests/reftest-stylo.list8
-rw-r--r--widget/reftests/reftest.list6
-rw-r--r--widget/tests/TestAppShellSteadyState.cpp503
-rw-r--r--widget/tests/TestChromeMargin.cpp155
-rw-r--r--widget/tests/bug586713_window.xul50
-rw-r--r--widget/tests/chrome.ini100
-rw-r--r--widget/tests/chrome_context_menus_win.xul101
-rw-r--r--widget/tests/empty_window.xul4
-rw-r--r--widget/tests/mochitest.ini12
-rw-r--r--widget/tests/moz.build15
-rw-r--r--widget/tests/native_menus_window.xul285
-rw-r--r--widget/tests/native_mouse_mac_window.xul773
-rw-r--r--widget/tests/standalone_native_menu_window.xul334
-rw-r--r--widget/tests/taskbar_previews.xul127
-rw-r--r--widget/tests/test_assign_event_data.html748
-rw-r--r--widget/tests/test_bug1123480.xul79
-rw-r--r--widget/tests/test_bug1151186.html43
-rw-r--r--widget/tests/test_bug343416.xul202
-rw-r--r--widget/tests/test_bug413277.html36
-rw-r--r--widget/tests/test_bug428405.xul167
-rw-r--r--widget/tests/test_bug429954.xul44
-rw-r--r--widget/tests/test_bug444800.xul102
-rw-r--r--widget/tests/test_bug466599.xul109
-rw-r--r--widget/tests/test_bug478536.xul34
-rw-r--r--widget/tests/test_bug485118.xul71
-rw-r--r--widget/tests/test_bug517396.xul56
-rw-r--r--widget/tests/test_bug522217.xul36
-rw-r--r--widget/tests/test_bug538242.xul55
-rw-r--r--widget/tests/test_bug565392.html70
-rw-r--r--widget/tests/test_bug586713.xul30
-rw-r--r--widget/tests/test_bug593307.xul46
-rw-r--r--widget/tests/test_bug596600.xul177
-rw-r--r--widget/tests/test_bug673301.xul40
-rw-r--r--widget/tests/test_bug760802.xul91
-rw-r--r--widget/tests/test_chrome_context_menus_win.xul27
-rw-r--r--widget/tests/test_clipboard.xul80
-rw-r--r--widget/tests/test_composition_text_querycontent.xul34
-rw-r--r--widget/tests/test_imestate.html1529
-rw-r--r--widget/tests/test_input_events_on_deactive_window.xul236
-rw-r--r--widget/tests/test_key_event_counts.xul90
-rw-r--r--widget/tests/test_keycodes.xul4361
-rw-r--r--widget/tests/test_mouse_scroll.xul28
-rw-r--r--widget/tests/test_native_key_bindings_mac.html343
-rw-r--r--widget/tests/test_native_menus.xul30
-rw-r--r--widget/tests/test_native_mouse_mac.xul30
-rw-r--r--widget/tests/test_panel_mouse_coords.xul83
-rw-r--r--widget/tests/test_picker_no_crash.html36
-rw-r--r--widget/tests/test_platform_colors.xul107
-rw-r--r--widget/tests/test_plugin_input_event.html74
-rw-r--r--widget/tests/test_plugin_scroll_consistency.html61
-rw-r--r--widget/tests/test_position_on_resize.xul94
-rw-r--r--widget/tests/test_secure_input.html148
-rw-r--r--widget/tests/test_sizemode_events.xul105
-rw-r--r--widget/tests/test_standalone_native_menu.xul30
-rw-r--r--widget/tests/test_system_status_bar.xul57
-rw-r--r--widget/tests/test_taskbar_progress.xul126
-rw-r--r--widget/tests/test_wheeltransaction.xul28
-rw-r--r--widget/tests/unit/test_macwebapputils.js44
-rw-r--r--widget/tests/unit/test_taskbar_jumplistitems.js261
-rw-r--r--widget/tests/unit/xpcshell.ini7
-rw-r--r--widget/tests/utils.js27
-rw-r--r--widget/tests/window_bug429954.xul45
-rw-r--r--widget/tests/window_bug478536.xul215
-rw-r--r--widget/tests/window_bug522217.xul72
-rw-r--r--widget/tests/window_bug538242.xul3
-rw-r--r--widget/tests/window_bug593307_centerscreen.xul27
-rw-r--r--widget/tests/window_bug593307_offscreen.xul34
-rw-r--r--widget/tests/window_composition_text_querycontent.xul6992
-rw-r--r--widget/tests/window_imestate_iframes.html380
-rw-r--r--widget/tests/window_mouse_scroll_win.html1531
-rw-r--r--widget/tests/window_picker_no_crash_child.html10
-rw-r--r--widget/tests/window_state_windows.xul87
-rw-r--r--widget/tests/window_wheeltransaction.xul1560
-rw-r--r--widget/uikit/GfxInfo.cpp218
-rw-r--r--widget/uikit/GfxInfo.h78
-rw-r--r--widget/uikit/moz.build19
-rw-r--r--widget/uikit/nsAppShell.h57
-rw-r--r--widget/uikit/nsAppShell.mm271
-rw-r--r--widget/uikit/nsLookAndFeel.h35
-rw-r--r--widget/uikit/nsLookAndFeel.mm401
-rw-r--r--widget/uikit/nsScreenManager.h60
-rw-r--r--widget/uikit/nsScreenManager.mm146
-rw-r--r--widget/uikit/nsWidgetFactory.mm71
-rw-r--r--widget/uikit/nsWindow.h125
-rw-r--r--widget/uikit/nsWindow.mm862
-rw-r--r--widget/windows/AudioSession.cpp453
-rw-r--r--widget/windows/AudioSession.h30
-rw-r--r--widget/windows/CompositorWidgetChild.cpp78
-rw-r--r--widget/windows/CompositorWidgetChild.h45
-rw-r--r--widget/windows/CompositorWidgetParent.cpp81
-rw-r--r--widget/windows/CompositorWidgetParent.h40
-rw-r--r--widget/windows/GfxInfo.cpp1450
-rw-r--r--widget/windows/GfxInfo.h102
-rw-r--r--widget/windows/IEnumFE.cpp173
-rw-r--r--widget/windows/IEnumFE.h90
-rw-r--r--widget/windows/IMMHandler.cpp2910
-rw-r--r--widget/windows/IMMHandler.h495
-rw-r--r--widget/windows/InProcessWinCompositorWidget.cpp41
-rw-r--r--widget/windows/InProcessWinCompositorWidget.h35
-rw-r--r--widget/windows/InkCollector.cpp253
-rw-r--r--widget/windows/InkCollector.h98
-rw-r--r--widget/windows/JumpListBuilder.cpp542
-rw-r--r--widget/windows/JumpListBuilder.h62
-rw-r--r--widget/windows/JumpListItem.cpp630
-rw-r--r--widget/windows/JumpListItem.h132
-rw-r--r--widget/windows/KeyboardLayout.cpp5232
-rw-r--r--widget/windows/KeyboardLayout.h1012
-rw-r--r--widget/windows/LSPAnnotator.cpp161
-rw-r--r--widget/windows/PCompositorWidget.ipdl31
-rw-r--r--widget/windows/PlatformWidgetTypes.ipdlh23
-rw-r--r--widget/windows/TSFTextStore.cpp6423
-rw-r--r--widget/windows/TSFTextStore.h1045
-rw-r--r--widget/windows/TaskbarPreview.cpp431
-rw-r--r--widget/windows/TaskbarPreview.h140
-rw-r--r--widget/windows/TaskbarPreviewButton.cpp145
-rw-r--r--widget/windows/TaskbarPreviewButton.h48
-rw-r--r--widget/windows/TaskbarTabPreview.cpp357
-rw-r--r--widget/windows/TaskbarTabPreview.h70
-rw-r--r--widget/windows/TaskbarWindowPreview.cpp326
-rw-r--r--widget/windows/TaskbarWindowPreview.h85
-rw-r--r--widget/windows/WidgetTraceEvent.cpp132
-rw-r--r--widget/windows/WinCompositorWidget.cpp329
-rw-r--r--widget/windows/WinCompositorWidget.h112
-rw-r--r--widget/windows/WinIMEHandler.cpp1055
-rw-r--r--widget/windows/WinIMEHandler.h190
-rw-r--r--widget/windows/WinMessages.h187
-rw-r--r--widget/windows/WinModifierKeyState.h62
-rw-r--r--widget/windows/WinMouseScrollHandler.cpp1799
-rw-r--r--widget/windows/WinMouseScrollHandler.h625
-rw-r--r--widget/windows/WinNativeEventData.h56
-rw-r--r--widget/windows/WinTaskbar.cpp506
-rw-r--r--widget/windows/WinTaskbar.h46
-rw-r--r--widget/windows/WinTextEventDispatcherListener.cpp78
-rw-r--r--widget/windows/WinTextEventDispatcherListener.h50
-rw-r--r--widget/windows/WinUtils.cpp2110
-rw-r--r--widget/windows/WinUtils.h648
-rw-r--r--widget/windows/WindowHook.cpp129
-rw-r--r--widget/windows/WindowHook.h81
-rw-r--r--widget/windows/WindowsUIUtils.cpp182
-rw-r--r--widget/windows/WindowsUIUtils.h29
-rw-r--r--widget/windows/moz.build123
-rw-r--r--widget/windows/mozwrlbase.h77
-rw-r--r--widget/windows/nsAppShell.cpp436
-rw-r--r--widget/windows/nsAppShell.h59
-rw-r--r--widget/windows/nsBidiKeyboard.cpp187
-rw-r--r--widget/windows/nsBidiKeyboard.h37
-rw-r--r--widget/windows/nsClipboard.cpp1036
-rw-r--r--widget/windows/nsClipboard.h92
-rw-r--r--widget/windows/nsColorPicker.cpp212
-rw-r--r--widget/windows/nsColorPicker.h59
-rw-r--r--widget/windows/nsDataObj.cpp2166
-rw-r--r--widget/windows/nsDataObj.h296
-rw-r--r--widget/windows/nsDataObjCollection.cpp405
-rw-r--r--widget/windows/nsDataObjCollection.h95
-rw-r--r--widget/windows/nsDeviceContextSpecWin.cpp669
-rw-r--r--widget/windows/nsDeviceContextSpecWin.h84
-rw-r--r--widget/windows/nsDragService.cpp641
-rw-r--r--widget/windows/nsDragService.h61
-rw-r--r--widget/windows/nsFilePicker.cpp1383
-rw-r--r--widget/windows/nsFilePicker.h164
-rw-r--r--widget/windows/nsIdleServiceWin.cpp30
-rw-r--r--widget/windows/nsIdleServiceWin.h46
-rw-r--r--widget/windows/nsImageClipboard.cpp497
-rw-r--r--widget/windows/nsImageClipboard.h93
-rw-r--r--widget/windows/nsLookAndFeel.cpp701
-rw-r--r--widget/windows/nsLookAndFeel.h65
-rw-r--r--widget/windows/nsNativeDragSource.cpp109
-rw-r--r--widget/windows/nsNativeDragSource.h66
-rw-r--r--widget/windows/nsNativeDragTarget.cpp486
-rw-r--r--widget/windows/nsNativeDragTarget.h102
-rw-r--r--widget/windows/nsNativeThemeWin.cpp4168
-rw-r--r--widget/windows/nsNativeThemeWin.h128
-rw-r--r--widget/windows/nsPrintOptionsWin.cpp162
-rw-r--r--widget/windows/nsPrintOptionsWin.h36
-rw-r--r--widget/windows/nsPrintSettingsWin.cpp528
-rw-r--r--widget/windows/nsPrintSettingsWin.h59
-rw-r--r--widget/windows/nsScreenManagerWin.cpp181
-rw-r--r--widget/windows/nsScreenManagerWin.h50
-rw-r--r--widget/windows/nsScreenWin.cpp199
-rw-r--r--widget/windows/nsScreenWin.h45
-rw-r--r--widget/windows/nsSound.cpp305
-rw-r--r--widget/windows/nsSound.h37
-rw-r--r--widget/windows/nsToolkit.cpp81
-rw-r--r--widget/windows/nsToolkit.h49
-rw-r--r--widget/windows/nsUXThemeConstants.h251
-rw-r--r--widget/windows/nsUXThemeData.cpp405
-rw-r--r--widget/windows/nsUXThemeData.h120
-rw-r--r--widget/windows/nsWidgetFactory.cpp262
-rw-r--r--widget/windows/nsWinGesture.cpp590
-rw-r--r--widget/windows/nsWinGesture.h288
-rw-r--r--widget/windows/nsWindow.cpp8139
-rw-r--r--widget/windows/nsWindow.h671
-rw-r--r--widget/windows/nsWindowBase.cpp243
-rw-r--r--widget/windows/nsWindowBase.h145
-rw-r--r--widget/windows/nsWindowDbg.cpp67
-rw-r--r--widget/windows/nsWindowDbg.h61
-rw-r--r--widget/windows/nsWindowDefs.h147
-rw-r--r--widget/windows/nsWindowGfx.cpp686
-rw-r--r--widget/windows/nsWindowGfx.h35
-rw-r--r--widget/windows/nsdefs.h45
-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/TestWinDND.cpp716
-rw-r--r--widget/windows/tests/moz.build6
-rw-r--r--widget/windows/touchinjection_sdk80.h107
-rw-r--r--widget/windows/widget.rc30
-rw-r--r--widget/x11/keysym2ucs.c866
-rw-r--r--widget/x11/keysym2ucs.h31
-rw-r--r--widget/x11/moz.build13
-rw-r--r--widget/xremoteclient/XRemoteClient.cpp805
-rw-r--r--widget/xremoteclient/XRemoteClient.h56
-rw-r--r--widget/xremoteclient/moz.build11
-rw-r--r--widget/xremoteclient/nsRemoteClient.h62
877 files changed, 292726 insertions, 0 deletions
diff --git a/widget/BasicEvents.h b/widget/BasicEvents.h
new file mode 100644
index 000000000..da8d819ef
--- /dev/null
+++ b/widget/BasicEvents.h
@@ -0,0 +1,1186 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "mozilla/dom/EventTarget.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsISupportsImpl.h"
+#include "nsIWidget.h"
+#include "nsString.h"
+#include "Units.h"
+
+namespace IPC {
+template<typename T>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+
+/******************************************************************************
+ * 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;
+ // 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.
+ 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 mNoCrossProcessBoundaryForwarding is true, the event is not allowed to
+ // cross process boundary.
+ bool mNoCrossProcessBoundaryForwarding : 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;
+ // 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 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;
+ // The event's action will be handled by APZ. The main thread should not
+ // perform its associated action. This is currently only relevant for
+ // wheel and touch events.
+ 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;
+ // True if the event is suppressed or delayed. This is used when parent side
+ // process the key event after content side, parent side may drop the key
+ // event if it was suppressed or delayed in content side.
+ bool mIsSuppressedOrDelayed : 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 StopCrossProcessForwarding()
+ {
+ mNoCrossProcessBoundaryForwarding = true;
+ }
+ inline void PreventDefault(bool aCalledByDefaultHandler = true)
+ {
+ 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) {
+ mDefaultPreventedByChrome = true;
+ } else {
+ mDefaultPreventedByContent = true;
+ }
+ }
+ // This should be used only before dispatching events into the DOM tree.
+ inline void PreventDefaultBeforeDispatch()
+ {
+ mDefaultPrevented = true;
+ }
+ 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;
+ }
+
+ 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 uint32_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:
+ case eSVGZoomEventClass:
+ 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) {
+ 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)
+ , mSpecifiedEventType(nullptr)
+ {
+ MOZ_COUNT_CTOR(WidgetEvent);
+ mFlags.Clear();
+ mFlags.mIsTrusted = aIsTrusted;
+ SetDefaultCancelableAndBubbles();
+ SetDefaultComposed();
+ SetDefaultComposedInNativeAnonymousContent();
+ }
+
+ WidgetEvent()
+ : WidgetEventTime()
+ {
+ MOZ_COUNT_CTOR(WidgetEvent);
+ }
+
+public:
+ WidgetEvent(bool aIsTrusted, EventMessage aMessage)
+ : WidgetEvent(aIsTrusted, aMessage, eBasicEventClass)
+ {
+ }
+
+ virtual ~WidgetEvent()
+ {
+ MOZ_COUNT_DTOR(WidgetEvent);
+ }
+
+ WidgetEvent(const WidgetEvent& aOther)
+ {
+ MOZ_COUNT_CTOR(WidgetEvent);
+ *this = aOther;
+ }
+
+ 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.
+ LayoutDeviceIntPoint mRefPoint;
+ // The previous mRefPoint, if known, used to calculate mouse movement deltas.
+ LayoutDeviceIntPoint mLastRefPoint;
+ // 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.
+ nsCOMPtr<nsIAtom> mSpecifiedEventType;
+
+ // nsIAtom 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
+ nsCOMPtr<dom::EventTarget> mTarget;
+ nsCOMPtr<dom::EventTarget> mCurrentTarget;
+ nsCOMPtr<dom::EventTarget> mOriginalTarget;
+
+ 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.
+ 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;
+ }
+
+ /**
+ * Helper methods for methods of DOM Event.
+ */
+ void StopPropagation() { mFlags.StopPropagation(); }
+ void StopImmediatePropagation() { mFlags.StopImmediatePropagation(); }
+ void StopCrossProcessForwarding() { mFlags.StopCrossProcessForwarding(); }
+ void PreventDefault(bool aCalledByDefaultHandler = true)
+ {
+ mFlags.PreventDefault(aCalledByDefaultHandler);
+ }
+ void PreventDefaultBeforeDispatch() { mFlags.PreventDefaultBeforeDispatch(); }
+ bool DefaultPrevented() const { return mFlags.DefaultPrevented(); }
+ bool DefaultPreventedByContent() const
+ {
+ return mFlags.DefaultPreventedByContent();
+ }
+ bool IsTrusted() const { return mFlags.IsTrusted(); }
+ bool PropagationStopped() const { return mFlags.PropagationStopped(); }
+
+ /**
+ * 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 is a native event deliverer event for plugin.
+ */
+ bool IsNativeEventDelivererForPlugin() 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 the event mMessage is one of key events.
+ */
+ bool HasKeyEventMessage() const;
+ /**
+ * 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 is native event deliverer event for plugin and
+ * it should be retarted to focused document.
+ */
+ bool IsRetargetedNativeEventDelivererForPlugin() const;
+ /**
+ * Returns true if the event is native event deliverer event for plugin and
+ * it should NOT be retarted to focused document.
+ */
+ bool IsNonRetargetedNativeEventDelivererForPlugin() 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;
+ /**
+ * Initialize mComposed
+ */
+ void SetDefaultComposed()
+ {
+ switch (mClass) {
+ case eCompositionEventClass:
+ mFlags.mComposed = mMessage == eCompositionStart ||
+ mMessage == eCompositionUpdate ||
+ 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;
+ break;
+ case eFocusEventClass:
+ mFlags.mComposed = mMessage == eBlur || mMessage == eFocus;
+ break;
+ case eKeyboardEventClass:
+ mFlags.mComposed = mMessage == eKeyDown || mMessage == eKeyUp ||
+ mMessage == eKeyPress;
+ break;
+ case eMouseEventClass:
+ mFlags.mComposed = mMessage == eMouseClick ||
+ mMessage == eMouseDoubleClick ||
+ mMessage == eMouseDown || mMessage == eMouseUp ||
+ mMessage == eMouseEnter || mMessage == eMouseLeave ||
+ mMessage == eMouseOver || mMessage == eMouseOut ||
+ mMessage == eMouseMove || mMessage == eContextMenu;
+ break;
+ case ePointerEventClass:
+ // All pointer events are composed
+ mFlags.mComposed = mMessage == ePointerDown ||
+ mMessage == ePointerMove || mMessage == ePointerUp ||
+ mMessage == ePointerCancel ||
+ mMessage == ePointerOver ||
+ mMessage == ePointerOut ||
+ mMessage == ePointerEnter ||
+ mMessage == ePointerLeave ||
+ 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;
+ default:
+ mFlags.mComposed = false;
+ break;
+ }
+ }
+
+ void SetComposed(const nsAString& aEventTypeArg)
+ {
+ mFlags.mComposed = // composition events
+ aEventTypeArg.EqualsLiteral("compositionstart") ||
+ aEventTypeArg.EqualsLiteral("compositionupdate") ||
+ aEventTypeArg.EqualsLiteral("compositionend") ||
+ // 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;
+ }
+};
+
+/******************************************************************************
+ * 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
+{
+ nsTArray<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(!mozilla::IsPointer<T>::value, "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()
+ {
+ }
+
+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 000000000..2bd327c86
--- /dev/null
+++ b/widget/CommandList.h
@@ -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/. */
+
+/**
+ * Define NS_DEFIVE_COMMAND(aName, aCommandStr) before including this.
+ * @param aName The name useful in C++ of the command.
+ * @param aCommandStr The command string in JS.
+ */
+
+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(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(ScrollLineDown, cmd_scrollLineDown)
+NS_DEFINE_COMMAND(ScrollLineUp, cmd_scrollLineUp)
+NS_DEFINE_COMMAND(ScrollPageDown, cmd_scrollPageDown)
+NS_DEFINE_COMMAND(ScrollPageUp, cmd_scrollPageUp)
+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)
diff --git a/widget/CompositorWidget.cpp b/widget/CompositorWidget.cpp
new file mode 100644
index 000000000..2cdf8010a
--- /dev/null
+++ b/widget/CompositorWidget.cpp
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CompositorWidget.h"
+#include "GLConsts.h"
+#include "nsBaseWidget.h"
+#include "VsyncDispatcher.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidget::~CompositorWidget()
+{
+}
+
+already_AddRefed<gfx::DrawTarget>
+CompositorWidget::StartRemoteDrawing()
+{
+ return nullptr;
+}
+
+void
+CompositorWidget::CleanupRemoteDrawing()
+{
+ mLastBackBuffer = nullptr;
+}
+
+already_AddRefed<gfx::DrawTarget>
+CompositorWidget::GetBackBufferDrawTarget(gfx::DrawTarget* aScreenTarget,
+ const LayoutDeviceIntRect& aRect,
+ const LayoutDeviceIntRect& aClearRect)
+{
+ MOZ_ASSERT(aScreenTarget);
+ gfx::SurfaceFormat format =
+ aScreenTarget->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 ? gfx::SurfaceFormat::B8G8R8X8 : gfx::SurfaceFormat::B8G8R8A8;
+ gfx::IntSize size = aRect.ToUnknownRect().Size();
+ gfx::IntSize clientSize(GetClientSize().ToUnknownSize());
+
+ RefPtr<gfx::DrawTarget> target;
+ // Re-use back buffer if possible
+ if (mLastBackBuffer &&
+ mLastBackBuffer->GetBackendType() == aScreenTarget->GetBackendType() &&
+ mLastBackBuffer->GetFormat() == format &&
+ size <= mLastBackBuffer->GetSize() &&
+ mLastBackBuffer->GetSize() <= clientSize) {
+ target = mLastBackBuffer;
+ target->SetTransform(gfx::Matrix());
+ if (!aClearRect.IsEmpty()) {
+ gfx::IntRect clearRect = aClearRect.ToUnknownRect() - aRect.ToUnknownRect().TopLeft();
+ target->ClearRect(gfx::Rect(clearRect.x, clearRect.y, clearRect.width, clearRect.height));
+ }
+ } else {
+ target = aScreenTarget->CreateSimilarDrawTarget(size, format);
+ mLastBackBuffer = target;
+ }
+ return target.forget();
+}
+
+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 000000000..93269eac6
--- /dev/null
+++ b/widget/CompositorWidget.h
@@ -0,0 +1,286 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/2D.h"
+#include "mozilla/layers/LayersTypes.h"
+
+class nsIWidget;
+class nsBaseWidget;
+
+namespace mozilla {
+class VsyncObserver;
+namespace layers {
+class Compositor;
+class LayerManagerComposite;
+class Compositor;
+class Composer2D;
+} // namespace layers
+namespace gfx {
+class DrawTarget;
+class SourceSurface;
+} // namespace gfx
+namespace widget {
+
+class WinCompositorWidget;
+class X11CompositorWidget;
+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 CompositorWidgetDelegate;
+
+// 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)
+ WidgetRenderingContext() : mLayerManager(nullptr) {}
+ layers::LayerManagerComposite* mLayerManager;
+#elif defined(MOZ_WIDGET_ANDROID)
+ WidgetRenderingContext() : mCompositor(nullptr) {}
+ layers::Compositor* mCompositor;
+#endif
+};
+
+/**
+ * Access to a widget from the compositor is restricted to these methods.
+ */
+class CompositorWidget
+{
+public:
+ NS_INLINE_DECL_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, 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 LayerManager draws the layer tree.
+ *
+ * Always called from the compositing thread.
+ */
+ virtual void DrawWindowUnderlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+ {}
+
+ /**
+ * Called after the LayerManager draws the layer tree
+ *
+ * Always called from the compositing thread.
+ */
+ virtual void DrawWindowOverlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+ {}
+
+ /**
+ * Return a DrawTarget for the window which can be composited into.
+ *
+ * Called by BasicCompositor on the compositor thread for OMTC drawing
+ * before each composition.
+ *
+ * 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.
+ */
+ virtual void EndRemoteDrawing()
+ {}
+ virtual void EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ 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;
+ }
+
+ /**
+ * 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();
+
+ /**
+ * If this widget has a more efficient composer available for its
+ * native framebuffer, return it.
+ *
+ * This can be called from a non-main thread, but that thread must
+ * hold a strong reference to this.
+ */
+ virtual layers::Composer2D* GetComposer2D() {
+ return nullptr;
+ }
+
+ /*
+ * 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 LayoutDeviceIntRect& aRect,
+ const LayoutDeviceIntRect& aClearRect);
+
+ /**
+ * 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;
+
+ /**
+ * This is only used by out-of-process compositors.
+ */
+ virtual RefPtr<VsyncObserver> GetVsyncObserver() const;
+
+ virtual WinCompositorWidget* AsWindows() {
+ return nullptr;
+ }
+ virtual X11CompositorWidget* AsX11() {
+ return nullptr;
+ }
+ virtual AndroidCompositorWidget* AsAndroid() {
+ return nullptr;
+ }
+
+ /**
+ * Return the platform-specific delegate for the widget, if any.
+ */
+ virtual CompositorWidgetDelegate* AsDelegate() {
+ return nullptr;
+ }
+
+protected:
+ virtual ~CompositorWidget();
+
+ // Back buffer of BasicCompositor
+ RefPtr<gfx::DrawTarget> mLastBackBuffer;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/ContentCache.cpp b/widget/ContentCache.cpp
new file mode 100644
index 000000000..52d62d588
--- /dev/null
+++ b/widget/ContentCache.cpp
@@ -0,0 +1,1381 @@
+/* -*- 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 "mozilla/IMEStateManager.h"
+#include "mozilla/Logging.h"
+#include "mozilla/TextComposition.h"
+#include "mozilla/TextEvents.h"
+#include "nsIWidget.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Move.h"
+
+namespace mozilla {
+
+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);
+}
+
+class GetRectText : public nsAutoCString
+{
+public:
+ explicit GetRectText(const LayoutDeviceIntRect& aRect)
+ {
+ Assign("{ x=");
+ AppendInt(aRect.x);
+ Append(", y=");
+ AppendInt(aRect.y);
+ Append(", width=");
+ AppendInt(aRect.width);
+ Append(", height=");
+ AppendInt(aRect.height);
+ Append(" }");
+ }
+ virtual ~GetRectText() {}
+};
+
+class GetWritingModeName : public nsAutoCString
+{
+public:
+ explicit GetWritingModeName(const WritingMode& aWritingMode)
+ {
+ if (!aWritingMode.IsVertical()) {
+ Assign("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ Assign("Vertical (LTR)");
+ return;
+ }
+ Assign("Vertical (RTL)");
+ }
+ 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");
+ }
+};
+
+/*****************************************************************************
+ * mozilla::ContentCache
+ *****************************************************************************/
+
+LazyLogModule sContentCacheLog("ContentCacheWidgets");
+
+ContentCache::ContentCache()
+ : mCompositionStart(UINT32_MAX)
+{
+}
+
+/*****************************************************************************
+ * mozilla::ContentCacheInChild
+ *****************************************************************************/
+
+ContentCacheInChild::ContentCacheInChild()
+ : ContentCache()
+{
+}
+
+void
+ContentCacheInChild::Clear()
+{
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p Clear()", this));
+
+ mCompositionStart = UINT32_MAX;
+ mText.Truncate();
+ mSelection.Clear();
+ mFirstCharRect.SetEmpty();
+ mCaret.Clear();
+ mTextRectArray.Clear();
+ mEditorRect.SetEmpty();
+}
+
+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.Clear();
+ mSelection.Clear();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent selection(true, eQuerySelectedText, aWidget);
+ aWidget->DispatchEvent(&selection, status);
+ if (NS_WARN_IF(!selection.mSucceeded)) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheSelection(), FAILED, "
+ "couldn't retrieve the selected text", this));
+ return false;
+ }
+ if (selection.mReply.mReversed) {
+ mSelection.mAnchor =
+ selection.mReply.mOffset + selection.mReply.mString.Length();
+ mSelection.mFocus = selection.mReply.mOffset;
+ } else {
+ mSelection.mAnchor = selection.mReply.mOffset;
+ mSelection.mFocus =
+ selection.mReply.mOffset + selection.mReply.mString.Length();
+ }
+ mSelection.mWritingMode = selection.GetWritingMode();
+
+ 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.Clear();
+
+ if (NS_WARN_IF(!mSelection.IsValid())) {
+ return false;
+ }
+
+ // XXX Should be mSelection.mFocus?
+ mCaret.mOffset = mSelection.StartOffset();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWidget);
+ caretRect.InitForQueryCaretRect(mCaret.mOffset);
+ aWidget->DispatchEvent(&caretRect, status);
+ if (NS_WARN_IF(!caretRect.mSucceeded)) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheCaret(), FAILED, "
+ "couldn't retrieve the caret rect at offset=%u",
+ this, mCaret.mOffset));
+ mCaret.Clear();
+ return false;
+ }
+ mCaret.mRect = caretRect.mReply.mRect;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheCaret(), Succeeded, "
+ "mSelection={ mAnchor=%u, mFocus=%u, mWritingMode=%s }, "
+ "mCaret={ mOffset=%u, mRect=%s }",
+ this, mSelection.mAnchor, mSelection.mFocus,
+ GetWritingModeName(mSelection.mWritingMode).get(), mCaret.mOffset,
+ GetRectText(mCaret.mRect).get()));
+ 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 editorRectEvent(true, eQueryEditorRect, aWidget);
+ aWidget->DispatchEvent(&editorRectEvent, status);
+ if (NS_WARN_IF(!editorRectEvent.mSucceeded)) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheEditorRect(), FAILED, "
+ "couldn't retrieve the editor rect", this));
+ return false;
+ }
+ mEditorRect = editorRectEvent.mReply.mRect;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheEditorRect(), Succeeded, "
+ "mEditorRect=%s", this, GetRectText(mEditorRect).get()));
+ 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 queryText(true, eQueryTextContent, aWidget);
+ queryText.InitForQueryTextContent(0, UINT32_MAX);
+ aWidget->DispatchEvent(&queryText, status);
+ if (NS_WARN_IF(!queryText.mSucceeded)) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheText(), FAILED, couldn't retrieve whole text", this));
+ mText.Truncate();
+ return false;
+ }
+ mText = queryText.mReply.mString;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheText(), Succeeded, mText.Length()=%u", this, mText.Length()));
+
+ return CacheSelection(aWidget, aNotification);
+}
+
+bool
+ContentCacheInChild::QueryCharRect(nsIWidget* aWidget,
+ uint32_t aOffset,
+ LayoutDeviceIntRect& aCharRect) const
+{
+ aCharRect.SetEmpty();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent textRect(true, eQueryTextRect, aWidget);
+ textRect.InitForQueryTextRect(aOffset, 1);
+ aWidget->DispatchEvent(&textRect, status);
+ if (NS_WARN_IF(!textRect.mSucceeded)) {
+ return false;
+ }
+ aCharRect = textRect.mReply.mRect;
+
+ // Guarantee the rect is not empty.
+ if (NS_WARN_IF(!aCharRect.height)) {
+ aCharRect.height = 1;
+ }
+ if (NS_WARN_IF(!aCharRect.width)) {
+ aCharRect.width = 1;
+ }
+ return true;
+}
+
+bool
+ContentCacheInChild::QueryCharRectArray(nsIWidget* aWidget,
+ uint32_t aOffset,
+ uint32_t aLength,
+ RectArray& aCharRectArray) const
+{
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetQueryContentEvent textRects(true, eQueryTextRectArray, aWidget);
+ textRects.InitForQueryTextRectArray(aOffset, aLength);
+ aWidget->DispatchEvent(&textRects, status);
+ if (NS_WARN_IF(!textRects.mSucceeded)) {
+ aCharRectArray.Clear();
+ return false;
+ }
+ aCharRectArray = Move(textRects.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={ mOffset=%u, IsValid()=%s }",
+ this, aWidget, GetNotificationName(aNotification), mCaret.mOffset,
+ GetBoolName(mCaret.IsValid())));
+
+ mCompositionStart = UINT32_MAX;
+ mTextRectArray.Clear();
+ mSelection.ClearAnchorCharRects();
+ mSelection.ClearFocusCharRects();
+ mSelection.mRect.SetEmpty();
+ mFirstCharRect.SetEmpty();
+
+ if (NS_WARN_IF(!mSelection.IsValid())) {
+ return false;
+ }
+
+ // 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 = 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.
+ uint32_t length = textComposition->LastData().Length();
+ mTextRectArray.mStart = mCompositionStart;
+ 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));
+ }
+ }
+
+ if (mTextRectArray.InRange(mSelection.mAnchor) &&
+ (!mSelection.mAnchor || mTextRectArray.InRange(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.InRange(mSelection.mFocus) &&
+ (!mSelection.mFocus ||
+ mTextRectArray.InRange(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 textRect(true, eQueryTextRect, aWidget);
+ textRect.InitForQueryTextRect(mSelection.StartOffset(),
+ mSelection.Length());
+ aWidget->DispatchEvent(&textRect, status);
+ if (NS_WARN_IF(!textRect.mSucceeded)) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p CacheTextRects(), FAILED, "
+ "couldn't retrieve text rect of whole selected text", this));
+ } else {
+ mSelection.mRect = textRect.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.InRange(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;
+ }
+ }
+
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p CacheTextRects(), Succeeded, "
+ "mText.Length()=%u, mTextRectArray={ mStart=%u, mRects.Length()=%u }, "
+ "mSelection={ mAnchor=%u, mAnchorCharRects[eNextCharRect]=%s, "
+ "mAnchorCharRects[ePrevCharRect]=%s, mFocus=%u, "
+ "mFocusCharRects[eNextCharRect]=%s, mFocusCharRects[ePrevCharRect]=%s, "
+ "mRect=%s }, mFirstCharRect=%s",
+ this, mText.Length(), mTextRectArray.mStart,
+ mTextRectArray.mRects.Length(), mSelection.mAnchor,
+ GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
+ GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
+ mSelection.mFocus,
+ GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
+ GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
+ GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get()));
+ 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),
+ GetWritingModeName(aWritingMode).get(), mText.Length()));
+
+ if (!aReversed) {
+ mSelection.mAnchor = aStartOffset;
+ mSelection.mFocus = aStartOffset + aLength;
+ } else {
+ mSelection.mAnchor = aStartOffset + aLength;
+ mSelection.mFocus = aStartOffset;
+ }
+ mSelection.mWritingMode = aWritingMode;
+
+ if (NS_WARN_IF(!CacheCaret(aWidget))) {
+ return;
+ }
+ Unused << NS_WARN_IF(!CacheTextRects(aWidget));
+}
+
+/*****************************************************************************
+ * mozilla::ContentCacheInParent
+ *****************************************************************************/
+
+ContentCacheInParent::ContentCacheInParent()
+ : ContentCache()
+ , mCommitStringByRequest(nullptr)
+ , mPendingEventsNeedingAck(0)
+ , mCompositionStartInChild(UINT32_MAX)
+ , mPendingCompositionCount(0)
+ , mWidgetHasComposition(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;
+ 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) {
+ IMEStateManager::MaybeStartOffsetUpdatedInChild(aWidget, mCompositionStart);
+ }
+
+ // When the widget has composition, we should set mCompositionStart to
+ // *current* composition start offset. Note that, in strictly speaking,
+ // widget should not use WidgetQueryContentEvent if there are some pending
+ // compositions (i.e., when mPendingCompositionCount is 2 or more).
+ mCompositionStartInChild = aOther.mCompositionStart;
+ if (mWidgetHasComposition) {
+ if (aOther.mCompositionStart != UINT32_MAX) {
+ mCompositionStart = aOther.mCompositionStart;
+ } else {
+ mCompositionStart = mSelection.StartOffset();
+ NS_WARNING_ASSERTION(mCompositionStart != UINT32_MAX,
+ "mCompositionStart shouldn't be invalid offset when "
+ "the widget has composition");
+ }
+ } else {
+ mCompositionStart = UINT32_MAX;
+ }
+
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p AssignContent(aNotification=%s), "
+ "Succeeded, mText.Length()=%u, mSelection={ mAnchor=%u, mFocus=%u, "
+ "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
+ "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
+ "mFocusCharRects[ePrevCharRect]=%s, mRect=%s }, "
+ "mFirstCharRect=%s, mCaret={ mOffset=%u, mRect=%s }, mTextRectArray={ "
+ "mStart=%u, mRects.Length()=%u }, mWidgetHasComposition=%s, "
+ "mPendingCompositionCount=%u, mCompositionStart=%u, mEditorRect=%s",
+ this, GetNotificationName(aNotification),
+ mText.Length(), mSelection.mAnchor, mSelection.mFocus,
+ GetWritingModeName(mSelection.mWritingMode).get(),
+ GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
+ GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
+ GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
+ GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
+ GetRectText(mSelection.mRect).get(), GetRectText(mFirstCharRect).get(),
+ mCaret.mOffset, GetRectText(mCaret.mRect).get(), mTextRectArray.mStart,
+ mTextRectArray.mRects.Length(), GetBoolName(mWidgetHasComposition),
+ mPendingCompositionCount, mCompositionStart,
+ GetRectText(mEditorRect).get()));
+}
+
+bool
+ContentCacheInParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent,
+ nsIWidget* aWidget) const
+{
+ MOZ_ASSERT(aWidget);
+
+ aEvent.mSucceeded = false;
+ aEvent.mReply.mFocusedWidget = 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) {
+ if (aWidget->PluginHasFocus()) {
+ if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(0))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to "
+ "aEvent.mInput.MakeOffsetAbsolute(0) failure, aEvent={ mMessage=%s, "
+ "mInput={ mOffset=%d, mLength=%d } }",
+ this, ToChar(aEvent.mMessage), aEvent.mInput.mOffset,
+ aEvent.mInput.mLength));
+ return false;
+ }
+ } else if (mWidgetHasComposition) {
+ if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(mCompositionStart))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to "
+ "aEvent.mInput.MakeOffsetAbsolute(mCompositionStart) failure, "
+ "mCompositionStart=%d, aEvent={ mMessage=%s, "
+ "mInput={ mOffset=%d, mLength=%d } }",
+ this, mCompositionStart, ToChar(aEvent.mMessage),
+ aEvent.mInput.mOffset, aEvent.mInput.mLength));
+ return false;
+ }
+ } else if (NS_WARN_IF(!mSelection.IsValid())) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to mSelection is invalid",
+ this));
+ return false;
+ } else if (NS_WARN_IF(!aEvent.mInput.MakeOffsetAbsolute(
+ mSelection.StartOffset()))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), FAILED due to "
+ "aEvent.mInput.MakeOffsetAbsolute(mSelection.StartOffset()) "
+ "failure, mSelection={ StartOffset()=%d, Length()=%d }, "
+ "aEvent={ mMessage=%s, mInput={ mOffset=%d, mLength=%d } }",
+ this, mSelection.StartOffset(), mSelection.Length(),
+ 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 (aWidget->PluginHasFocus()) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), "
+ "return emtpy selection becasue plugin has focus",
+ this));
+ aEvent.mSucceeded = true;
+ aEvent.mReply.mOffset = 0;
+ aEvent.mReply.mReversed = false;
+ aEvent.mReply.mHasSelection = false;
+ return true;
+ }
+ 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 true;
+ }
+ aEvent.mReply.mOffset = mSelection.StartOffset();
+ if (mSelection.Collapsed()) {
+ aEvent.mReply.mString.Truncate(0);
+ } else {
+ if (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.mReply.mString =
+ Substring(mText, aEvent.mReply.mOffset, mSelection.Length());
+ }
+ aEvent.mReply.mReversed = mSelection.Reversed();
+ aEvent.mReply.mHasSelection = true;
+ aEvent.mReply.mWritingMode = mSelection.mWritingMode;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), "
+ "Succeeded, aEvent={ mReply={ mOffset=%u, mString=\"%s\", "
+ "mReversed=%s, mHasSelection=%s, mWritingMode=%s } }",
+ this, aEvent.mReply.mOffset,
+ GetEscapedUTF8String(aEvent.mReply.mString).get(),
+ GetBoolName(aEvent.mReply.mReversed),
+ GetBoolName(aEvent.mReply.mHasSelection),
+ GetWritingModeName(aEvent.mReply.mWritingMode).get()));
+ break;
+ case eQueryTextContent: {
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent("
+ "aEvent={ mMessage=eQueryTextContent, mInput={ mOffset=%u, "
+ "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.mReply.mOffset = inputOffset;
+ aEvent.mReply.mString =
+ Substring(mText, inputOffset, inputEndOffset - inputOffset);
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), "
+ "Succeeded, aEvent={ mReply={ mOffset=%u, mString.Length()=%u } }",
+ this, aEvent.mReply.mOffset, aEvent.mReply.mString.Length()));
+ break;
+ }
+ case eQueryTextRect:
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent("
+ "aEvent={ mMessage=eQueryTextRect, mInput={ mOffset=%u, "
+ "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 true;
+ }
+ // 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.
+ if (aEvent.mInput.mLength) {
+ if (NS_WARN_IF(!GetUnionTextRects(aEvent.mInput.mOffset,
+ aEvent.mInput.mLength,
+ isRelativeToInsertionPoint,
+ aEvent.mReply.mRect))) {
+ // 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,
+ aEvent.mReply.mRect))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), "
+ "FAILED to get caret rect", this));
+ return false;
+ }
+ }
+ if (aEvent.mInput.mOffset < mText.Length()) {
+ aEvent.mReply.mString =
+ Substring(mText, aEvent.mInput.mOffset,
+ mText.Length() >= aEvent.mInput.EndOffset() ?
+ aEvent.mInput.mLength : UINT32_MAX);
+ } else {
+ aEvent.mReply.mString.Truncate(0);
+ }
+ aEvent.mReply.mOffset = aEvent.mInput.mOffset;
+ // 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={ mReply={ mOffset=%u, mString=\"%s\", "
+ "mWritingMode=%s, mRect=%s } }",
+ this, aEvent.mReply.mOffset,
+ GetEscapedUTF8String(aEvent.mReply.mString).get(),
+ GetWritingModeName(aEvent.mReply.mWritingMode).get(),
+ GetRectText(aEvent.mReply.mRect).get()));
+ break;
+ case eQueryCaretRect:
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent("
+ "aEvent={ mMessage=eQueryCaretRect, mInput={ mOffset=%u } }, "
+ "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 true;
+ }
+ // 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.
+ if (NS_WARN_IF(!GetCaretRect(aEvent.mInput.mOffset,
+ isRelativeToInsertionPoint,
+ aEvent.mReply.mRect))) {
+ MOZ_LOG(sContentCacheLog, LogLevel::Error,
+ ("0x%p HandleQueryContentEvent(), "
+ "FAILED to get caret rect", this));
+ return false;
+ }
+ aEvent.mReply.mOffset = aEvent.mInput.mOffset;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), "
+ "Succeeded, aEvent={ mReply={ mOffset=%u, mRect=%s } }",
+ this, aEvent.mReply.mOffset, GetRectText(aEvent.mReply.mRect).get()));
+ break;
+ case eQueryEditorRect:
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent("
+ "aEvent={ mMessage=eQueryEditorRect }, aWidget=0x%p)",
+ this, aWidget));
+ aEvent.mReply.mRect = mEditorRect;
+ MOZ_LOG(sContentCacheLog, LogLevel::Info,
+ ("0x%p HandleQueryContentEvent(), "
+ "Succeeded, aEvent={ mReply={ mRect=%s } }",
+ this, GetRectText(aEvent.mReply.mRect).get()));
+ break;
+ default:
+ break;
+ }
+ aEvent.mSucceeded = true;
+ 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={ mStart=%u, mRects.Length()=%u }, "
+ "mSelection={ mAnchor=%u, mFocus=%u }",
+ this, aOffset, GetBoolName(aRoundToExistingOffset),
+ mTextRectArray.mStart, mTextRectArray.mRects.Length(),
+ mSelection.mAnchor, mSelection.mFocus));
+
+ if (!aOffset) {
+ NS_WARNING_ASSERTION(!mFirstCharRect.IsEmpty(), "empty rect");
+ aTextRect = mFirstCharRect;
+ return !aTextRect.IsEmpty();
+ }
+ 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();
+ }
+
+ uint32_t offset = aOffset;
+ if (!mTextRectArray.InRange(aOffset)) {
+ if (!aRoundToExistingOffset) {
+ aTextRect.SetEmpty();
+ return false;
+ }
+ if (!mTextRectArray.IsValid()) {
+ // 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();
+ }
+ 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={ "
+ "mStart=%u, mRects.Length()=%u }, "
+ "mSelection={ mAnchor=%u, mFocus=%u }",
+ this, aOffset, aLength, GetBoolName(aRoundToExistingOffset),
+ mTextRectArray.mStart, mTextRectArray.mRects.Length(),
+ mSelection.mAnchor, mSelection.mFocus));
+
+ CheckedInt<uint32_t> endOffset =
+ CheckedInt<uint32_t>(aOffset) + aLength;
+ if (!endOffset.isValid()) {
+ return false;
+ }
+
+ if (!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 (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 && aOffset != mSelection.mAnchor &&
+ aOffset != mSelection.mFocus && !mTextRectArray.InRange(aOffset)) {
+ // The first character rect isn't cached.
+ return false;
+ }
+
+ if ((aRoundToExistingOffset && mTextRectArray.HasRects()) ||
+ mTextRectArray.IsOverlappingWith(aOffset, aLength)) {
+ aUnionTextRect =
+ mTextRectArray.GetUnionRectAsFarAsPossible(aOffset, aLength,
+ aRoundToExistingOffset);
+ } else {
+ aUnionTextRect.SetEmpty();
+ }
+
+ if (!aOffset) {
+ aUnionTextRect = aUnionTextRect.Union(mFirstCharRect);
+ }
+ 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={ mOffset=%u, mRect=%s, IsValid()=%s }, mTextRectArray={ "
+ "mStart=%u, mRects.Length()=%u }, mSelection={ mAnchor=%u, mFocus=%u, "
+ "mWritingMode=%s, mAnchorCharRects[eNextCharRect]=%s, "
+ "mAnchorCharRects[ePrevCharRect]=%s, mFocusCharRects[eNextCharRect]=%s, "
+ "mFocusCharRects[ePrevCharRect]=%s }, mFirstCharRect=%s",
+ this, aOffset, GetBoolName(aRoundToExistingOffset),
+ mCaret.mOffset, GetRectText(mCaret.mRect).get(),
+ GetBoolName(mCaret.IsValid()), mTextRectArray.mStart,
+ mTextRectArray.mRects.Length(), mSelection.mAnchor, mSelection.mFocus,
+ GetWritingModeName(mSelection.mWritingMode).get(),
+ GetRectText(mSelection.mAnchorCharRects[eNextCharRect]).get(),
+ GetRectText(mSelection.mAnchorCharRects[ePrevCharRect]).get(),
+ GetRectText(mSelection.mFocusCharRects[eNextCharRect]).get(),
+ GetRectText(mSelection.mFocusCharRects[ePrevCharRect]).get(),
+ GetRectText(mFirstCharRect).get()));
+
+ if (mCaret.IsValid() && 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.mWritingMode.IsVertical()) {
+ aCaretRect.y = aCaretRect.YMost();
+ } else {
+ // XXX bidi-unaware.
+ aCaretRect.x = 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.mWritingMode.IsVertical()) {
+ aCaretRect.height = mCaret.IsValid() ? mCaret.mRect.height : 1;
+ } else {
+ aCaretRect.width = mCaret.IsValid() ? 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()=%u }), "
+ "mPendingEventsNeedingAck=%u, mWidgetHasComposition=%s, "
+ "mPendingCompositionCount=%u, mCommitStringByRequest=0x%p",
+ this, ToChar(aEvent.mMessage),
+ GetEscapedUTF8String(aEvent.mData).get(), aEvent.mData.Length(),
+ aEvent.mRanges ? aEvent.mRanges->Length() : 0, mPendingEventsNeedingAck,
+ GetBoolName(mWidgetHasComposition), mPendingCompositionCount,
+ mCommitStringByRequest));
+
+ // We must be able to simulate the selection because
+ // we might not receive selection updates in time
+ if (!mWidgetHasComposition) {
+ if (aEvent.mWidget && aEvent.mWidget->PluginHasFocus()) {
+ // If focus is on plugin, we cannot get selection range
+ mCompositionStart = 0;
+ } else if (mCompositionStartInChild != UINT32_MAX) {
+ // 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 = mSelection.StartOffset();
+ }
+ MOZ_ASSERT(aEvent.mMessage == eCompositionStart);
+ MOZ_RELEASE_ASSERT(mPendingCompositionCount < UINT8_MAX);
+ mPendingCompositionCount++;
+ }
+
+ mWidgetHasComposition = !aEvent.CausesDOMCompositionEndEvent();
+
+ if (!mWidgetHasComposition) {
+ mCompositionStart = UINT32_MAX;
+ }
+
+ // 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) {
+ MOZ_ASSERT(aEvent.mMessage == eCompositionChange ||
+ aEvent.mMessage == eCompositionCommit);
+ *mCommitStringByRequest = aEvent.mData;
+ 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=%u",
+ this, ToChar(aSelectionEvent.mMessage),
+ aSelectionEvent.mOffset, aSelectionEvent.mLength,
+ GetBoolName(aSelectionEvent.mReversed),
+ GetBoolName(aSelectionEvent.mExpandToClusterBoundary),
+ GetBoolName(aSelectionEvent.mUseNativeLineBreak), mPendingEventsNeedingAck,
+ GetBoolName(mWidgetHasComposition), mPendingCompositionCount));
+
+ 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, mPendingCompositionCount=%u",
+ this, aWidget, ToChar(aMessage), mPendingEventsNeedingAck));
+
+ if (WidgetCompositionEvent::IsFollowedByCompositionEnd(aMessage)) {
+ MOZ_RELEASE_ASSERT(mPendingCompositionCount > 0);
+ mPendingCompositionCount--;
+ }
+
+ MOZ_RELEASE_ASSERT(mPendingEventsNeedingAck > 0);
+ 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), mWidgetHasComposition=%s, mCommitStringByRequest=%p",
+ this, aWidget, GetBoolName(aCancel), GetBoolName(mWidgetHasComposition),
+ mCommitStringByRequest));
+
+ MOZ_ASSERT(!mCommitStringByRequest);
+
+ 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));
+ return false;
+ }
+
+ mCommitStringByRequest = &aCommittedString;
+
+ aWidget->NotifyIME(IMENotification(aCancel ? REQUEST_TO_CANCEL_COMPOSITION :
+ REQUEST_TO_COMMIT_COMPOSITION));
+
+ 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().
+ 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). Finally, TextComposition instance in
+ // the remote process will be destroyed by
+ // IMEStateManager::DispatchCompositionEvent() at receiving the
+ // eCompositionCommit event (Note that TextComposition instance in this
+ // process was already destroyed).
+ return true;
+}
+
+void
+ContentCacheInParent::MaybeNotifyIME(nsIWidget* aWidget,
+ const IMENotification& aNotification)
+{
+ if (!mPendingEventsNeedingAck) {
+ IMEStateManager::NotifyIME(aNotification, aWidget, true);
+ 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);
+
+ // New notifications which are notified during flushing pending notifications
+ // should be merged again.
+ mPendingEventsNeedingAck++;
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(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 (!aWidget->Destroyed()) {
+ mPendingTextChange.Clear();
+ IMEStateManager::NotifyIME(notification, aWidget, true);
+ }
+ }
+
+ if (mPendingSelectionChange.HasNotification()) {
+ IMENotification notification(mPendingSelectionChange);
+ if (!aWidget->Destroyed()) {
+ mPendingSelectionChange.Clear();
+ IMEStateManager::NotifyIME(notification, aWidget, true);
+ }
+ }
+
+ // 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 (!aWidget->Destroyed()) {
+ mPendingLayoutChange.Clear();
+ IMEStateManager::NotifyIME(notification, aWidget, true);
+ }
+ }
+
+ // Finally, send composition update notification because it notifies IME of
+ // finishing handling whole sending events.
+ if (mPendingCompositionUpdate.HasNotification()) {
+ IMENotification notification(mPendingCompositionUpdate);
+ if (!aWidget->Destroyed()) {
+ mPendingCompositionUpdate.Clear();
+ IMEStateManager::NotifyIME(notification, aWidget, true);
+ }
+ }
+
+ if (!--mPendingEventsNeedingAck && !aWidget->Destroyed() &&
+ (mPendingTextChange.HasNotification() ||
+ mPendingSelectionChange.HasNotification() ||
+ mPendingLayoutChange.HasNotification() ||
+ mPendingCompositionUpdate.HasNotification())) {
+ FlushPendingNotifications(aWidget);
+ }
+}
+
+/*****************************************************************************
+ * mozilla::ContentCache::TextRectArray
+ *****************************************************************************/
+
+LayoutDeviceIntRect
+ContentCache::TextRectArray::GetRect(uint32_t aOffset) const
+{
+ LayoutDeviceIntRect rect;
+ if (InRange(aOffset)) {
+ rect = mRects[aOffset - mStart];
+ }
+ return rect;
+}
+
+LayoutDeviceIntRect
+ContentCache::TextRectArray::GetUnionRect(uint32_t aOffset,
+ uint32_t aLength) const
+{
+ LayoutDeviceIntRect rect;
+ if (!InRange(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 000000000..77a9d5617
--- /dev/null
+++ b/widget/ContentCache.h
@@ -0,0 +1,449 @@
+/* -*- 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/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/WritingModes.h"
+#include "nsIWidget.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "Units.h"
+
+namespace mozilla {
+
+class ContentCacheInParent;
+
+/**
+ * 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 InfallibleTArray<LayoutDeviceIntRect> RectArray;
+ typedef widget::IMENotification IMENotification;
+
+ ContentCache();
+
+protected:
+ // Whole text in the target
+ nsString mText;
+
+ // Start offset of the composition string.
+ 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;
+
+ Selection()
+ : mAnchor(UINT32_MAX)
+ , mFocus(UINT32_MAX)
+ {
+ }
+
+ void Clear()
+ {
+ mAnchor = mFocus = UINT32_MAX;
+ mWritingMode = WritingMode();
+ ClearAnchorCharRects();
+ ClearFocusCharRects();
+ mRect.SetEmpty();
+ }
+
+ void ClearAnchorCharRects()
+ {
+ for (size_t i = 0; i < ArrayLength(mAnchorCharRects); i++) {
+ mAnchorCharRects[i].SetEmpty();
+ }
+ }
+ void ClearFocusCharRects()
+ {
+ for (size_t i = 0; i < ArrayLength(mFocusCharRects); i++) {
+ mFocusCharRects[i].SetEmpty();
+ }
+ }
+
+ bool IsValid() const
+ {
+ return mAnchor != UINT32_MAX && mFocus != UINT32_MAX;
+ }
+ bool Collapsed() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the selection is valid");
+ return mFocus == mAnchor;
+ }
+ bool Reversed() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the selection is valid");
+ return mFocus < mAnchor;
+ }
+ uint32_t StartOffset() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the selection is valid");
+ return Reversed() ? mFocus : mAnchor;
+ }
+ uint32_t EndOffset() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the selection is valid");
+ return Reversed() ? mAnchor : mFocus;
+ }
+ uint32_t Length() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the selection is valid");
+ return Reversed() ? mAnchor - mFocus : mFocus - mAnchor;
+ }
+ LayoutDeviceIntRect StartCharRect() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the selection is valid");
+ return Reversed() ? mFocusCharRects[eNextCharRect] :
+ mAnchorCharRects[eNextCharRect];
+ }
+ LayoutDeviceIntRect EndCharRect() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the selection is valid");
+ return Reversed() ? mAnchorCharRects[eNextCharRect] :
+ mFocusCharRects[eNextCharRect];
+ }
+ } mSelection;
+
+ bool IsSelectionValid() const
+ {
+ return mSelection.IsValid() && 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;
+
+ Caret()
+ : mOffset(UINT32_MAX)
+ {
+ }
+
+ void Clear()
+ {
+ mOffset = UINT32_MAX;
+ mRect.SetEmpty();
+ }
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+
+ uint32_t Offset() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the caret is valid");
+ return mOffset;
+ }
+ } mCaret;
+
+ struct TextRectArray final
+ {
+ uint32_t mStart;
+ RectArray mRects;
+
+ TextRectArray()
+ : mStart(UINT32_MAX)
+ {
+ }
+
+ void Clear()
+ {
+ mStart = UINT32_MAX;
+ mRects.Clear();
+ }
+
+ bool IsValid() const
+ {
+ if (mStart == UINT32_MAX) {
+ return false;
+ }
+ CheckedInt<uint32_t> endOffset =
+ CheckedInt<uint32_t>(mStart) + mRects.Length();
+ return endOffset.isValid();
+ }
+ bool HasRects() const
+ {
+ return IsValid() && !mRects.IsEmpty();
+ }
+ uint32_t StartOffset() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the caret is valid");
+ return mStart;
+ }
+ uint32_t EndOffset() const
+ {
+ NS_ASSERTION(IsValid(),
+ "The caller should check if the caret is valid");
+ if (!IsValid()) {
+ return UINT32_MAX;
+ }
+ return mStart + mRects.Length();
+ }
+ bool InRange(uint32_t aOffset) const
+ {
+ return IsValid() &&
+ StartOffset() <= aOffset && aOffset < EndOffset();
+ }
+ bool InRange(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 InRange(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;
+ } mTextRectArray;
+
+ LayoutDeviceIntRect mEditorRect;
+
+ friend class ContentCacheInParent;
+ friend struct IPC::ParamTraits<ContentCache>;
+};
+
+class ContentCacheInChild final : public ContentCache
+{
+public:
+ ContentCacheInChild();
+
+ /**
+ * 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);
+};
+
+class ContentCacheInParent final : public ContentCache
+{
+public:
+ ContentCacheInParent();
+
+ /**
+ * AssignContent() is called when TabParent receives ContentCache from
+ * the content process. This doesn't copy composition information because
+ * it's managed by TabParent 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
+ * TabParent 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;
+
+ // 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.
+ uint32_t mCompositionStartInChild;
+ // mPendingCompositionCount is number of compositions which started in widget
+ // but not yet handled in the child process.
+ uint8_t mPendingCompositionCount;
+ // 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;
+
+ /**
+ * 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);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ContentCache_h
diff --git a/widget/ContentEvents.h b/widget/ContentEvents.h
new file mode 100644
index 000000000..be64b7beb
--- /dev/null
+++ b/widget/ContentEvents.h
@@ -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/. */
+
+#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 "nsStringGlue.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;
+ }
+
+ /// The possible related target
+ nsCOMPtr<dom::EventTarget> mRelatedTarget;
+
+ bool mFromRaise;
+ bool mIsRefocus;
+
+ void AssignFocusEventData(const InternalFocusEvent& aEvent, bool aCopyTargets)
+ {
+ AssignUIEventData(aEvent, aCopyTargets);
+
+ mRelatedTarget = aCopyTargets ? aEvent.mRelatedTarget : nullptr;
+ 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)
+ {
+ }
+
+ 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)
+ {
+ }
+
+ 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::InternalSVGZoomEvent
+ ******************************************************************************/
+
+class InternalSVGZoomEvent : public WidgetGUIEvent
+{
+public:
+ virtual InternalSVGZoomEvent* AsSVGZoomEvent() override { return this; }
+
+ InternalSVGZoomEvent(bool aIsTrusted, EventMessage aMessage)
+ : WidgetGUIEvent(aIsTrusted, aMessage, nullptr, eSVGZoomEventClass)
+ {
+ }
+
+ virtual WidgetEvent* Duplicate() const override
+ {
+ MOZ_ASSERT(mClass == eSVGZoomEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ InternalSVGZoomEvent* result = new InternalSVGZoomEvent(false, mMessage);
+ result->AssignSVGZoomEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ void AssignSVGZoomEventData(const InternalSVGZoomEvent& aEvent,
+ bool aCopyTargets)
+ {
+ AssignGUIEventData(aEvent, aCopyTargets);
+ }
+};
+
+/******************************************************************************
+ * 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 000000000..9667a72c5
--- /dev/null
+++ b/widget/EventClassList.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/. */
+
+/**
+ * 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)
+NS_EVENT_CLASS(Internal, BeforeAfterKeyboardEvent)
+
+// 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, SVGZoomEvent)
+NS_EVENT_CLASS(Internal, SMILTimeEvent)
+
+// MiscEvents.h
+NS_EVENT_CLASS(Widget, CommandEvent)
+NS_EVENT_CLASS(Widget, ContentCommandEvent)
+NS_EVENT_CLASS(Widget, PluginEvent)
+
+// InternalMutationEvent.h (dom/events)
+NS_EVENT_CLASS(Internal, MutationEvent)
diff --git a/widget/EventForwards.h b/widget/EventForwards.h
new file mode 100644
index 000000000..4efac5498
--- /dev/null
+++ b/widget/EventForwards.h
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsTArray.h"
+
+class nsCString;
+
+/**
+ * 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 {
+
+/**
+ * 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_COMMAND(aName, aCommandStr) , Command##aName
+
+typedef int8_t CommandInt;
+enum Command : CommandInt
+{
+ CommandDoNothing
+
+#include "mozilla/CommandList.h"
+};
+#undef NS_DEFINE_COMMAND
+
+} // namespace mozilla
+
+/**
+ * All header files should include this header instead of *Events.h.
+ */
+
+namespace mozilla {
+
+#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
+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 TextRangeArray;
+
+// FontRange.h
+struct FontRange;
+
+} // namespace mozilla
+
+#endif // mozilla_EventForwards_h__
diff --git a/widget/EventMessageList.h b/widget/EventMessageList.h
new file mode 100644
index 000000000..55fc7375e
--- /dev/null
+++ b/widget/EventMessageList.h
@@ -0,0 +1,457 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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)
+
+NS_EVENT_MESSAGE(eBeforeKeyDown)
+NS_EVENT_MESSAGE(eAfterKeyDown)
+NS_EVENT_MESSAGE(eBeforeKeyUp)
+NS_EVENT_MESSAGE(eAfterKeyUp)
+
+// 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)
+
+// 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)
+// 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(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(eStorage)
+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)
+
+//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)
+
+// 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(eSVGUnload)
+NS_EVENT_MESSAGE(eSVGResize)
+NS_EVENT_MESSAGE(eSVGScroll)
+
+// SVG Zoom events
+NS_EVENT_MESSAGE(eSVGZoom)
+
+// XUL command events
+NS_EVENT_MESSAGE(eXULCommand)
+
+// Cut, copy, paste events
+NS_EVENT_MESSAGE(eCopy)
+NS_EVENT_MESSAGE(eCut)
+NS_EVENT_MESSAGE(ePaste)
+
+// 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)
+
+// These are used to send native events to plugins.
+NS_EVENT_MESSAGE(ePluginInputEvent)
+
+// 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(eAnimationStart)
+NS_EVENT_MESSAGE(eAnimationEnd)
+NS_EVENT_MESSAGE(eAnimationIteration)
+
+// 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)
+
+// 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) || defined(MOZ_WIDGET_GONK)
+NS_EVENT_MESSAGE(eOrientationChange)
+#endif
+
+// WebVR events
+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)
+
+// 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)
+
+// SpeakerManager events
+NS_EVENT_MESSAGE(eSpeakerForcedChange)
+
+#ifdef MOZ_GAMEPAD
+// 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)
+#endif
+
+// input and beforeinput events.
+NS_EVENT_MESSAGE(eEditorInput)
+
+// selection events
+NS_EVENT_MESSAGE(eSelectStart)
+NS_EVENT_MESSAGE(eSelectionChange)
+
+// Details element events.
+NS_EVENT_MESSAGE(eToggle)
+
+#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 000000000..15cb4a01d
--- /dev/null
+++ b/widget/FontRange.h
@@ -0,0 +1,27 @@
+/* -*- 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_
+
+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 000000000..6f9a74a4f
--- /dev/null
+++ b/widget/GfxDriverInfo.cpp
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+using namespace mozilla::widget;
+
+int32_t GfxDriverInfo::allFeatures = 0;
+uint64_t GfxDriverInfo::allDriverVersions = ~(uint64_t(0));
+GfxDeviceFamily* const GfxDriverInfo::allDevices = nullptr;
+
+GfxDeviceFamily* GfxDriverInfo::mDeviceFamilies[DeviceFamilyMax];
+nsAString* GfxDriverInfo::mDeviceVendors[DeviceVendorMax];
+
+GfxDriverInfo::GfxDriverInfo()
+ : mOperatingSystem(OperatingSystem::Unknown),
+ mOperatingSystemVersion(0),
+ mAdapterVendor(GfxDriverInfo::GetDeviceVendor(VendorAll)),
+ mDevices(allDevices),
+ 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, nsAString& vendor,
+ 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),
+ mAdapterVendor(vendor),
+ 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),
+ mAdapterVendor(aOrig.mAdapterVendor),
+ 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) {
+ mDevices = new GfxDeviceFamily;
+ *mDevices = *aOrig.mDevices;
+ } else {
+ mDevices = aOrig.mDevices;
+ }
+
+ mDeleteDevices = aOrig.mDeleteDevices;
+}
+
+GfxDriverInfo::~GfxDriverInfo()
+{
+ if (mDeleteDevices)
+ delete mDevices;
+}
+
+// Macros for appending a device to the DeviceFamily.
+#define APPEND_DEVICE(device) APPEND_DEVICE2(#device)
+#define APPEND_DEVICE2(device) deviceFamily->AppendElement(NS_LITERAL_STRING(device))
+
+const GfxDeviceFamily* GfxDriverInfo::GetDeviceFamily(DeviceFamily id)
+{
+ // The code here is too sensitive to fall through to the default case if the
+ // code is invalid.
+ NS_ASSERTION(id >= 0 && id < DeviceFamilyMax, "DeviceFamily id is out of range");
+
+ // If it already exists, we must have processed it once, so return it now.
+ if (mDeviceFamilies[id])
+ return mDeviceFamilies[id];
+
+ mDeviceFamilies[id] = new GfxDeviceFamily;
+ GfxDeviceFamily* deviceFamily = mDeviceFamilies[id];
+
+ switch (id) {
+ case IntelGMA500:
+ APPEND_DEVICE(0x8108); /* IntelGMA500_1 */
+ APPEND_DEVICE(0x8109); /* IntelGMA500_2 */
+ break;
+ case IntelGMA900:
+ APPEND_DEVICE(0x2582); /* IntelGMA900_1 */
+ APPEND_DEVICE(0x2782); /* IntelGMA900_2 */
+ APPEND_DEVICE(0x2592); /* IntelGMA900_3 */
+ APPEND_DEVICE(0x2792); /* IntelGMA900_4 */
+ break;
+ case 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 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 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 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 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 IntelHD3000:
+ APPEND_DEVICE(0x0126);
+ break;
+ case IntelMobileHDGraphics:
+ APPEND_DEVICE(0x0046); /* IntelMobileHDGraphics */
+ break;
+ case 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 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 Geforce7300GT:
+ APPEND_DEVICE(0x0393);
+ break;
+ case Nvidia310M:
+ APPEND_DEVICE(0x0A70);
+ break;
+ case Nvidia8800GTS:
+ APPEND_DEVICE(0x0193);
+ break;
+ case 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 Bug1116812:
+ APPEND_DEVICE(0x2e32);
+ APPEND_DEVICE(0x2a02);
+ break;
+ case Bug1155608:
+ APPEND_DEVICE(0x2e22); /* IntelG45_1 */
+ break;
+ case Bug1207665:
+ APPEND_DEVICE(0xa001); /* Intel Media Accelerator 3150 */
+ APPEND_DEVICE(0xa002);
+ APPEND_DEVICE(0xa011);
+ APPEND_DEVICE(0xa012);
+ break;
+ // This should never happen, but we get a warning if we don't handle this.
+ case DeviceFamilyMax:
+ NS_WARNING("Invalid DeviceFamily id");
+ break;
+ }
+
+ return deviceFamily;
+}
+
+// Macro for assigning a device vendor id to a string.
+#define DECLARE_VENDOR_ID(name, deviceId) \
+ case name: \
+ mDeviceVendors[id]->AssignLiteral(deviceId); \
+ break;
+
+const nsAString& GfxDriverInfo::GetDeviceVendor(DeviceVendor id)
+{
+ NS_ASSERTION(id >= 0 && id < DeviceVendorMax, "DeviceVendor id is out of range");
+
+ if (mDeviceVendors[id])
+ return *mDeviceVendors[id];
+
+ mDeviceVendors[id] = new nsString();
+
+ switch (id) {
+ DECLARE_VENDOR_ID(VendorAll, "");
+ DECLARE_VENDOR_ID(VendorIntel, "0x8086");
+ DECLARE_VENDOR_ID(VendorNVIDIA, "0x10de");
+ DECLARE_VENDOR_ID(VendorAMD, "0x1022");
+ DECLARE_VENDOR_ID(VendorATI, "0x1002");
+ DECLARE_VENDOR_ID(VendorMicrosoft, "0x1414");
+ // Suppress a warning.
+ DECLARE_VENDOR_ID(DeviceVendorMax, "");
+ }
+
+ return *mDeviceVendors[id];
+}
diff --git a/widget/GfxDriverInfo.h b/widget/GfxDriverInfo.h
new file mode 100644
index 000000000..99d560b81
--- /dev/null
+++ b/widget/GfxDriverInfo.h
@@ -0,0 +1,300 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+// Macros for adding a blocklist item to the static list.
+#define APPEND_TO_DRIVER_BLOCKLIST(os, vendor, devices, feature, featureStatus, driverComparator, driverVersion, ruleId, suggestedVersion) \
+ mDriverInfo->AppendElement(GfxDriverInfo(os, vendor, devices, feature, featureStatus, driverComparator, driverVersion, ruleId, suggestedVersion))
+#define APPEND_TO_DRIVER_BLOCKLIST2(os, vendor, devices, feature, featureStatus, driverComparator, driverVersion, ruleId) \
+ mDriverInfo->AppendElement(GfxDriverInfo(os, vendor, devices, feature, featureStatus, driverComparator, driverVersion, ruleId))
+
+#define APPEND_TO_DRIVER_BLOCKLIST_RANGE(os, vendor, 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, vendor, devices, feature, featureStatus, driverComparator, driverVersion, ruleId, suggestedVersion); \
+ info.mDriverVersionMax = driverVersionMax; \
+ mDriverInfo->AppendElement(info); \
+ } while (false)
+
+#define APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2(os, vendor, 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, vendor, devices, feature, featureStatus, driverComparator, driverVersion, ruleId, suggestedVersion, false, true); \
+ info.mDriverVersionMax = driverVersionMax; \
+ mDriverInfo->AppendElement(info); \
+ } while (false)
+
+
+namespace mozilla {
+namespace widget {
+
+enum class OperatingSystem {
+ Unknown,
+ Windows,
+ WindowsXP,
+ WindowsServer2003,
+ WindowsVista,
+ Windows7,
+ Windows8,
+ Windows8_1,
+ Windows10,
+ Linux,
+ OSX,
+ OSX10_5,
+ OSX10_6,
+ OSX10_7,
+ OSX10_8,
+ OSX10_9,
+ OSX10_10,
+ OSX10_11,
+ OSX10_12,
+ 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 DeviceFamily {
+ IntelGMA500,
+ IntelGMA900,
+ IntelGMA950,
+ IntelGMA3150,
+ IntelGMAX3000,
+ IntelGMAX4500HD,
+ IntelHDGraphicsToSandyBridge,
+ IntelHD3000,
+ IntelMobileHDGraphics,
+ NvidiaBlockD3D9Layers,
+ RadeonX1000,
+ Geforce7300GT,
+ Nvidia310M,
+ Nvidia8800GTS,
+ Bug1137716,
+ Bug1116812,
+ Bug1155608,
+ Bug1207665,
+ DeviceFamilyMax
+};
+
+enum DeviceVendor {
+ VendorAll,
+ VendorIntel,
+ VendorNVIDIA,
+ VendorAMD,
+ VendorATI,
+ VendorMicrosoft,
+ DeviceVendorMax
+};
+
+/* Array of devices to match, or an empty array for all devices */
+typedef nsTArray<nsString> GfxDeviceFamily;
+
+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, nsAString& vendor, 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;
+
+ nsString mAdapterVendor;
+
+ static GfxDeviceFamily* const allDevices;
+ 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* mDeviceFamilies[DeviceFamilyMax];
+
+ static const nsAString& GetDeviceVendor(DeviceVendor id);
+ static nsAString* mDeviceVendors[DeviceVendorMax];
+
+ 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)
+ 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 000000000..e53db69c5
--- /dev/null
+++ b/widget/GfxInfoBase.cpp
@@ -0,0 +1,1438 @@
+/* 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 "GfxInfoWebGL.h"
+#include "GfxDriverInfo.h"
+#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 "nsIDOMElement.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMNodeList.h"
+#include "nsTArray.h"
+#include "nsXULAppAPI.h"
+#include "nsIXULAppInfo.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "MediaPrefs.h"
+#include "gfxPrefs.h"
+#include "gfxPlatform.h"
+#include "gfxConfig.h"
+#include "DriverCrashGuard.h"
+
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#endif
+
+using namespace mozilla::widget;
+using namespace mozilla;
+using mozilla::MutexAutoLock;
+
+nsTArray<GfxDriverInfo>* GfxInfoBase::mDriverInfo;
+bool GfxInfoBase::mDriverInfoObserverInitialized;
+
+// Observes for shutdown so that the child GfxDriverInfo list is freed.
+class ShutdownObserver : public nsIObserver
+{
+ virtual ~ShutdownObserver() {}
+
+public:
+ ShutdownObserver() {}
+
+ 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::mDriverInfo;
+ GfxInfoBase::mDriverInfo = nullptr;
+
+ for (uint32_t i = 0; i < DeviceFamilyMax; i++)
+ delete GfxDriverInfo::mDeviceFamilies[i];
+
+ for (uint32_t i = 0; i < DeviceVendorMax; i++)
+ delete GfxDriverInfo::mDeviceVendors[i];
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver)
+
+void InitGfxDriverInfoShutdownObserver()
+{
+ if (GfxInfoBase::mDriverInfoObserverInitialized)
+ return;
+
+ GfxInfoBase::mDriverInfoObserverInitialized = 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 BLACKLIST_PREF_BRANCH "gfx.blacklist."
+#define SUGGESTED_VERSION_PREF BLACKLIST_PREF_BRANCH "suggested-driver-version"
+#define BLACKLIST_ENTRY_TAG_NAME "gfxBlacklistEntry"
+
+static const char*
+GetPrefNameForFeature(int32_t aFeature)
+{
+ const char* name = nullptr;
+ switch(aFeature) {
+ case nsIGfxInfo::FEATURE_DIRECT2D:
+ name = BLACKLIST_PREF_BRANCH "direct2d";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS:
+ name = BLACKLIST_PREF_BRANCH "layers.direct3d9";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS:
+ name = BLACKLIST_PREF_BRANCH "layers.direct3d10";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS:
+ name = BLACKLIST_PREF_BRANCH "layers.direct3d10-1";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS:
+ name = BLACKLIST_PREF_BRANCH "layers.direct3d11";
+ break;
+ case nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE:
+ name = BLACKLIST_PREF_BRANCH "direct3d11angle";
+ break;
+ case nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING:
+ name = BLACKLIST_PREF_BRANCH "hardwarevideodecoding";
+ break;
+ case nsIGfxInfo::FEATURE_OPENGL_LAYERS:
+ name = BLACKLIST_PREF_BRANCH "layers.opengl";
+ break;
+ case nsIGfxInfo::FEATURE_WEBGL_OPENGL:
+ name = BLACKLIST_PREF_BRANCH "webgl.opengl";
+ break;
+ case nsIGfxInfo::FEATURE_WEBGL_ANGLE:
+ name = BLACKLIST_PREF_BRANCH "webgl.angle";
+ break;
+ case nsIGfxInfo::FEATURE_WEBGL_MSAA:
+ name = BLACKLIST_PREF_BRANCH "webgl.msaa";
+ break;
+ case nsIGfxInfo::FEATURE_STAGEFRIGHT:
+ name = BLACKLIST_PREF_BRANCH "stagefright";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION:
+ name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE:
+ name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration.encode";
+ break;
+ case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE:
+ name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration.decode";
+ break;
+ case nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION:
+ name = BLACKLIST_PREF_BRANCH "canvas2d.acceleration";
+ break;
+ case nsIGfxInfo::FEATURE_VP8_HW_DECODE:
+ case nsIGfxInfo::FEATURE_VP9_HW_DECODE:
+ case nsIGfxInfo::FEATURE_DX_INTEROP2:
+ // We don't provide prefs for these features.
+ 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";
+ nsAdoptingCString failureValue = Preferences::GetCString(failureprefname.get());
+ if (failureValue) {
+ aFailureId = failureValue.get();
+ } else {
+ aFailureId = "FEATURE_FAILURE_BLACKLIST_PREF";
+ }
+
+ return true;
+}
+
+static void
+SetPrefValueForFeature(int32_t aFeature, int32_t aValue, const nsACString& aFailureId)
+{
+ const char *prefname = GetPrefNameForFeature(aFeature);
+ if (!prefname)
+ return;
+
+ 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;
+
+ 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
+BlacklistOSToOperatingSystem(const nsAString& os)
+{
+ if (os.EqualsLiteral("WINNT 5.1"))
+ return OperatingSystem::WindowsXP;
+ else if (os.EqualsLiteral("WINNT 5.2"))
+ return OperatingSystem::WindowsServer2003;
+ else if (os.EqualsLiteral("WINNT 6.0"))
+ return OperatingSystem::WindowsVista;
+ else 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("Android"))
+ return OperatingSystem::Android;
+ // For historical reasons, "All" in blocklist means "All Windows"
+ else if (os.EqualsLiteral("All"))
+ return OperatingSystem::Windows;
+
+ return OperatingSystem::Unknown;
+}
+
+static GfxDeviceFamily*
+BlacklistDevicesToDeviceFamily(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->AppendElement(NS_ConvertUTF8toUTF16(devices[i]));
+ }
+
+ return deviceIds;
+}
+
+static int32_t
+BlacklistFeatureToGfxFeature(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::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"))
+ return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION;
+ else if (aFeature.EqualsLiteral("CANVAS2D_ACCELERATION"))
+ return nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION;
+
+ // 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
+BlacklistFeatureStatusToGfxFeatureStatus(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;
+
+ // 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
+BlacklistComparatorToComparisonOp(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 Blacklist 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
+BlacklistEntryToDriverInfo(nsCString& aBlacklistEntry,
+ 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();
+ }
+
+ nsTArray<nsCString> keyValues;
+ ParseString(aBlacklistEntry, '\t', keyValues);
+
+ aDriverInfo.mRuleId = NS_LITERAL_CSTRING("FEATURE_FAILURE_DL_BLACKLIST_NO_ID");
+
+ for (uint32_t i = 0; i < keyValues.Length(); ++i) {
+ nsCString keyValue = keyValues[i];
+ 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 " << keyValue.get();
+ return false;
+ }
+ nsCString key = splitted[0];
+ 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 = NS_LITERAL_CSTRING("FEATURE_FAILURE_DL_BLACKLIST_") + value;
+ aDriverInfo.mRuleId = blockIdStr.get();
+ } else if (key.EqualsLiteral("os")) {
+ aDriverInfo.mOperatingSystem = BlacklistOSToOperatingSystem(dataValue);
+ } else if (key.EqualsLiteral("osversion")) {
+ aDriverInfo.mOperatingSystemVersion = strtoul(value.get(), nullptr, 10);
+ } else if (key.EqualsLiteral("vendor")) {
+ aDriverInfo.mAdapterVendor = dataValue;
+ } else if (key.EqualsLiteral("feature")) {
+ aDriverInfo.mFeature = BlacklistFeatureToGfxFeature(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 = BlacklistFeatureStatusToGfxFeatureStatus(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 = BlacklistComparatorToComparisonOp(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;
+ }
+ nsCString minValue = versionRange[0];
+ 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 = BlacklistDevicesToDeviceFamily(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;
+}
+
+static void
+BlacklistEntriesToDriverInfo(nsTArray<nsCString>& aBlacklistEntries,
+ nsTArray<GfxDriverInfo>& aDriverInfo)
+{
+ aDriverInfo.Clear();
+ aDriverInfo.SetLength(aBlacklistEntries.Length());
+
+ for (uint32_t i = 0; i < aBlacklistEntries.Length(); ++i) {
+ nsCString blacklistEntry = aBlacklistEntries[i];
+ GfxDriverInfo di;
+ if (BlacklistEntryToDriverInfo(blacklistEntry, di)) {
+ aDriverInfo[i] = di;
+ // Prevent di falling out of scope from destroying the devices.
+ di.mDeleteDevices = false;
+ }
+ }
+}
+
+NS_IMETHODIMP
+GfxInfoBase::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (strcmp(aTopic, "blocklist-data-gfxItems") == 0) {
+ nsTArray<GfxDriverInfo> driverInfo;
+ nsTArray<nsCString> blacklistEntries;
+ nsCString utf8Data = NS_ConvertUTF16toUTF8(aData);
+ if (utf8Data.Length() > 0) {
+ ParseString(utf8Data, '\n', blacklistEntries);
+ }
+ BlacklistEntriesToDriverInfo(blacklistEntries, driverInfo);
+ EvaluateDownloadedBlacklist(driverInfo);
+ }
+
+ return NS_OK;
+}
+
+GfxInfoBase::GfxInfoBase()
+ : mMutex("GfxInfoBase")
+{
+}
+
+GfxInfoBase::~GfxInfoBase()
+{
+}
+
+nsresult
+GfxInfoBase::Init()
+{
+ InitGfxDriverInfoShutdownObserver();
+ gfxPrefs::GetSingleton();
+ MediaPrefs::GetSingleton();
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "blocklist-data-gfxItems", true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfoBase::GetFeatureStatus(int32_t aFeature, nsACString& aFailureId, int32_t* aStatus)
+{
+ int32_t blocklistAll = gfxPrefs::BlocklistAll();
+ 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()) {
+ // Delegate to the parent process.
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+ bool success;
+ nsCString remoteFailureId;
+ cc->SendGetGraphicsFeatureStatus(aFeature, aStatus, &remoteFailureId, &success);
+ aFailureId = remoteFailureId;
+ return success ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ nsString version;
+ nsTArray<GfxDriverInfo> driverInfo;
+ nsresult rv = GetFeatureStatusImpl(aFeature, aStatus, version, driverInfo, aFailureId);
+ return rv;
+}
+
+// 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)
+{
+ 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;
+ }
+#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;
+}
+
+int32_t
+GfxInfoBase::FindBlocklistedDeviceInList(const nsTArray<GfxDriverInfo>& info,
+ nsAString& aSuggestedVersion,
+ int32_t aFeature,
+ nsACString& aFailureId,
+ OperatingSystem os)
+{
+ int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+
+ uint32_t i = 0;
+ for (; i < info.Length(); i++) {
+ // 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)) {
+ continue;
+ }
+
+ if (info[i].mOperatingSystemVersion && info[i].mOperatingSystemVersion != OperatingSystemVersion()) {
+ continue;
+ }
+
+ // XXX: it would be better not to do this everytime round the loop
+ nsAutoString adapterVendorID;
+ nsAutoString adapterDeviceID;
+ nsAutoString adapterDriverVersionString;
+ if (info[i].mGpu2) {
+ if (NS_FAILED(GetAdapterVendorID2(adapterVendorID)) ||
+ NS_FAILED(GetAdapterDeviceID2(adapterDeviceID)) ||
+ NS_FAILED(GetAdapterDriverVersion2(adapterDriverVersionString)))
+ {
+ return 0;
+ }
+ } else {
+ if (NS_FAILED(GetAdapterVendorID(adapterVendorID)) ||
+ NS_FAILED(GetAdapterDeviceID(adapterDeviceID)) ||
+ NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString)))
+ {
+ return 0;
+ }
+ }
+
+#if defined(XP_WIN) || defined(ANDROID)
+ uint64_t driverVersion;
+ ParseDriverVersion(adapterDriverVersionString, &driverVersion);
+#endif
+
+ if (!info[i].mAdapterVendor.Equals(GfxDriverInfo::GetDeviceVendor(VendorAll), nsCaseInsensitiveStringComparator()) &&
+ !info[i].mAdapterVendor.Equals(adapterVendorID, nsCaseInsensitiveStringComparator())) {
+ continue;
+ }
+
+ if (info[i].mDevices != GfxDriverInfo::allDevices && info[i].mDevices->Length()) {
+ bool deviceMatches = false;
+ for (uint32_t j = 0; j < info[i].mDevices->Length(); j++) {
+ if ((*info[i].mDevices)[j].Equals(adapterDeviceID, nsCaseInsensitiveStringComparator())) {
+ deviceMatches = true;
+ break;
+ }
+ }
+
+ if (!deviceMatches) {
+ continue;
+ }
+ }
+
+ 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)
+ switch (info[i].mComparisonOp) {
+ case DRIVER_LESS_THAN:
+ match = driverVersion < info[i].mDriverVersion;
+ break;
+ case DRIVER_BUILD_ID_LESS_THAN:
+ match = (driverVersion & 0xFFFF) < info[i].mDriverVersion;
+ break;
+ case DRIVER_LESS_THAN_OR_EQUAL:
+ match = driverVersion <= info[i].mDriverVersion;
+ break;
+ case DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL:
+ match = (driverVersion & 0xFFFF) <= info[i].mDriverVersion;
+ break;
+ case DRIVER_GREATER_THAN:
+ match = driverVersion > info[i].mDriverVersion;
+ break;
+ case DRIVER_GREATER_THAN_OR_EQUAL:
+ match = driverVersion >= info[i].mDriverVersion;
+ break;
+ case DRIVER_EQUAL:
+ match = driverVersion == info[i].mDriverVersion;
+ break;
+ case DRIVER_NOT_EQUAL:
+ match = driverVersion != info[i].mDriverVersion;
+ break;
+ case DRIVER_BETWEEN_EXCLUSIVE:
+ match = driverVersion > info[i].mDriverVersion && driverVersion < info[i].mDriverVersionMax;
+ break;
+ case DRIVER_BETWEEN_INCLUSIVE:
+ match = driverVersion >= info[i].mDriverVersion && driverVersion <= info[i].mDriverVersionMax;
+ break;
+ case DRIVER_BETWEEN_INCLUSIVE_START:
+ match = driverVersion >= info[i].mDriverVersion && driverVersion < 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 ||
+ info[i].mFeature == aFeature)
+ {
+ status = info[i].mFeatureStatus;
+ if (!info[i].mRuleId.IsEmpty()) {
+ aFailureId = info[i].mRuleId.get();
+ } else {
+ aFailureId = "FEATURE_FAILURE_DL_BLACKLIST_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)) {
+ nsAutoString adapterVendorID2;
+ nsAutoString adapterDeviceID2;
+ if ((!NS_FAILED(GetAdapterVendorID2(adapterVendorID2))) &&
+ (!NS_FAILED(GetAdapterDeviceID2(adapterDeviceID2))))
+ {
+ nsAString &nvVendorID = (nsAString &)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA);
+ const nsString nv310mDeviceId = NS_LITERAL_STRING("0x0A70");
+ if (nvVendorID.Equals(adapterVendorID2, nsCaseInsensitiveStringComparator()) &&
+ nv310mDeviceId.Equals(adapterDeviceID2, 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;
+}
+
+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 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);
+ } else {
+ if (!mDriverInfo) {
+ mDriverInfo = new nsTArray<GfxDriverInfo>();
+ }
+ status = FindBlocklistedDeviceInList(GetGfxDriverInfo(), aSuggestedVersion, aFeature, aFailureId, os);
+ }
+
+ // It's now done being processed. It's safe to set the status to STATUS_OK.
+ if (status == nsIGfxInfo::FEATURE_STATUS_UNKNOWN) {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ } else {
+ *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);
+}
+
+
+NS_IMETHODIMP
+GfxInfoBase::GetWebGLParameter(const nsAString& aParam,
+ nsAString& aResult)
+{
+ return GfxInfoWebGL::GetWebGLParameter(aParam, aResult);
+}
+
+void
+GfxInfoBase::EvaluateDownloadedBlacklist(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::FEATURE_WEBGL_MSAA,
+ nsIGfxInfo::FEATURE_STAGEFRIGHT,
+ nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION,
+ nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION,
+ 0
+ };
+
+ // For every feature we know about, we evaluate whether this blacklist 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 blacklist
+ // 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();
+ }
+ MOZ_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();
+}
+
+/* XPConnect method of returning arrays is very ugly. Would not recommend. */
+NS_IMETHODIMP GfxInfoBase::GetFailures(uint32_t* failureCount,
+ int32_t** indices,
+ char ***failures)
+{
+ MutexAutoLock lock(mMutex);
+
+ NS_ENSURE_ARG_POINTER(failureCount);
+ NS_ENSURE_ARG_POINTER(failures);
+
+ *failures = nullptr;
+ *failureCount = 0;
+
+ // indices is "allowed" to be null, the caller may not care about them,
+ // although calling from JS doesn't seem to get us there.
+ if (indices) *indices = nullptr;
+
+ LogForwarder* logForwarder = Factory::GetLogForwarder();
+ if (!logForwarder) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // There are two stirng 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 Clone() calls. Technically,
+ // we don't need the mutex lock after the StringVectorCopy() call.
+ LoggingRecord loggedStrings = logForwarder->LoggingRecordCopy();
+ *failureCount = loggedStrings.size();
+
+ if (*failureCount != 0) {
+ *failures = (char**)moz_xmalloc(*failureCount * sizeof(char*));
+ if (!(*failures)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (indices) {
+ *indices = (int32_t*)moz_xmalloc(*failureCount * sizeof(int32_t));
+ if (!(*indices)) {
+ free(*failures);
+ *failures = nullptr;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ /* copy over the failure messages into the array we just allocated */
+ LoggingRecord::const_iterator it;
+ uint32_t i=0;
+ for(it = loggedStrings.begin() ; it != loggedStrings.end(); ++it, i++) {
+ (*failures)[i] = (char*)nsMemory::Clone(Get<1>(*it).c_str(), Get<1>(*it).size() + 1);
+ if (indices) (*indices)[i] = Get<0>(*it);
+
+ if (!(*failures)[i]) {
+ /* <sarcasm> I'm too afraid to use an inline function... </sarcasm> */
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, (*failures));
+ *failureCount = i;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ 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;
+ }
+}
+
+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 const char*
+GetLayersBackendName(layers::LayersBackend aBackend)
+{
+ switch (aBackend) {
+ case layers::LayersBackend::LAYERS_NONE:
+ return "none";
+ case layers::LayersBackend::LAYERS_OPENGL:
+ return "opengl";
+ case layers::LayersBackend::LAYERS_D3D9:
+ return "d3d9";
+ case layers::LayersBackend::LAYERS_D3D11:
+ return "d3d11";
+ case layers::LayersBackend::LAYERS_CLIENT:
+ return "client";
+ case layers::LayersBackend::LAYERS_BASIC:
+ return "basic";
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown layers backend");
+ return "unknown";
+ }
+}
+
+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 = 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) -> 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)
+{
+}
+
+bool
+GfxInfoBase::InitFeatureObject(JSContext* aCx,
+ JS::Handle<JSObject*> aContainer,
+ const char* aName,
+ int32_t aFeature,
+ Maybe<mozilla::gfx::FeatureStatus> aFeatureStatus,
+ JS::MutableHandle<JSObject*> aOutObj)
+{
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return false;
+ }
+
+ nsCString failureId = NS_LITERAL_CSTRING("OK");
+ int32_t unused;
+ if (!NS_SUCCEEDED(GetFeatureStatus(aFeature, failureId, &unused))) {
+ return false;
+ }
+
+ // Set "status".
+ if (aFeatureStatus) {
+ const char* status = FeatureStatusToString(aFeatureStatus.value());
+
+ JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, status));
+ 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::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;
+}
+
+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 000000000..6d30f1b71
--- /dev/null
+++ b/widget/GfxInfoBase.h
@@ -0,0 +1,136 @@
+/* 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 "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;
+ // using GfxInfoBase::GetWebGLParameter;
+ // 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 GetWebGLParameter(const nsAString & aParam, nsAString & _retval) override;
+
+ NS_IMETHOD GetMonitors(JSContext* cx, JS::MutableHandleValue _retval) override;
+ NS_IMETHOD GetFailures(uint32_t *failureCount, int32_t** indices, char ***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;
+
+ // 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();
+
+ // only useful on X11
+ NS_IMETHOD_(void) GetData() override { }
+
+ static void AddCollector(GfxInfoCollectorBase* collector);
+ static void RemoveCollector(GfxInfoCollectorBase* collector);
+
+ static nsTArray<GfxDriverInfo>* mDriverInfo;
+ static bool mDriverInfoObserverInitialized;
+
+ virtual nsString Model() { return EmptyString(); }
+ virtual nsString Hardware() { return EmptyString(); }
+ virtual nsString Product() { return EmptyString(); }
+ virtual nsString Manufacturer() { return EmptyString(); }
+ virtual uint32_t OperatingSystemVersion() { return 0; }
+
+ // Convenience to get the application version
+ static const nsCString& GetApplicationVersion();
+
+ virtual nsresult FindMonitors(JSContext* cx, JS::HandleObject array) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+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 InitFeatureObject(
+ JSContext* aCx,
+ JS::Handle<JSObject*> aContainer,
+ const char* aName,
+ int32_t aFeature,
+ Maybe<mozilla::gfx::FeatureStatus> aKnownStatus,
+ JS::MutableHandle<JSObject*> aOutObj);
+
+private:
+ virtual int32_t FindBlocklistedDeviceInList(const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsAString& aSuggestedVersion,
+ int32_t aFeature,
+ nsACString &aFailureId,
+ OperatingSystem os);
+
+ void EvaluateDownloadedBlacklist(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 000000000..59c842a77
--- /dev/null
+++ b/widget/GfxInfoCollector.cpp
@@ -0,0 +1,54 @@
+/* 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 000000000..9436b2333
--- /dev/null
+++ b/widget/GfxInfoCollector.h
@@ -0,0 +1,94 @@
+/* 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/GfxInfoWebGL.cpp b/widget/GfxInfoWebGL.cpp
new file mode 100644
index 000000000..6583ee475
--- /dev/null
+++ b/widget/GfxInfoWebGL.cpp
@@ -0,0 +1,69 @@
+/* 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 "GfxInfoWebGL.h"
+
+#include "nsServiceManagerUtils.h"
+
+#include "GLDefs.h"
+#include "nsIDOMWebGLRenderingContext.h"
+#include "nsICanvasRenderingContextInternal.h"
+
+using namespace mozilla::widget;
+
+nsresult
+GfxInfoWebGL::GetWebGLParameter(const nsAString& aParam, nsAString& aResult)
+{
+ GLenum param;
+
+ if (aParam.EqualsLiteral("vendor")) param = LOCAL_GL_VENDOR;
+ else if (aParam.EqualsLiteral("renderer")) param = LOCAL_GL_RENDERER;
+ else if (aParam.EqualsLiteral("version")) param = LOCAL_GL_VERSION;
+ else if (aParam.EqualsLiteral("shading_language_version")) param = LOCAL_GL_SHADING_LANGUAGE_VERSION;
+ else if (aParam.EqualsLiteral("extensions")) param = LOCAL_GL_EXTENSIONS;
+ else if (aParam.EqualsLiteral("full-renderer")) param = 0;
+ else return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIDOMWebGLRenderingContext> webgl =
+ do_CreateInstance("@mozilla.org/content/canvas-rendering-context;1?id=webgl");
+ if (!webgl)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsICanvasRenderingContextInternal> webglInternal =
+ do_QueryInterface(webgl);
+ if (!webglInternal)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = webglInternal->SetDimensions(16, 16);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (param)
+ return webgl->MozGetUnderlyingParamString(param, aResult);
+
+ // this is the "full renderer" string, which is vendor + renderer + version
+
+ nsAutoString str;
+
+ rv = webgl->MozGetUnderlyingParamString(LOCAL_GL_VENDOR, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.Append(str);
+ aResult.AppendLiteral(" -- ");
+
+ rv = webgl->MozGetUnderlyingParamString(LOCAL_GL_RENDERER, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.Append(str);
+ aResult.AppendLiteral(" -- ");
+
+ rv = webgl->MozGetUnderlyingParamString(LOCAL_GL_VERSION, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult.Append(str);
+
+ return NS_OK;
+}
diff --git a/widget/GfxInfoWebGL.h b/widget/GfxInfoWebGL.h
new file mode 100644
index 000000000..f18be14f0
--- /dev/null
+++ b/widget/GfxInfoWebGL.h
@@ -0,0 +1,27 @@
+/* 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_GfxInfoWebGL_h__
+#define __mozilla_widget_GfxInfoWebGL_h__
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfoWebGL {
+public:
+ static nsresult GetWebGLParameter(const nsAString& aParam, nsAString& aResult);
+
+protected:
+ GfxInfoWebGL() { }
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/GfxInfoX11.cpp b/widget/GfxInfoX11.cpp
new file mode 100644
index 000000000..f490bed52
--- /dev/null
+++ b/widget/GfxInfoX11.cpp
@@ -0,0 +1,564 @@
+/* -*- 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 "nsCRTGlue.h"
+#include "prenv.h"
+
+#include "GfxInfoX11.h"
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#include "nsICrashReporter.h"
+#endif
+
+namespace mozilla {
+namespace 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 = 0;
+pid_t glxtest_pid = 0;
+
+nsresult
+GfxInfo::Init()
+{
+ mGLMajorVersion = 0;
+ mMajorVersion = 0;
+ mMinorVersion = 0;
+ mRevisionVersion = 0;
+ mIsMesa = false;
+ mIsNVIDIA = false;
+ mIsFGLRX = false;
+ mIsNouveau = false;
+ mIsIntel = false;
+ mIsOldSwrast = false;
+ mIsLlvmpipe = false;
+ mHasTextureFromPixmap = false;
+ return GfxInfoBase::Init();
+}
+
+void
+GfxInfo::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 == 0, that means that we already read the information
+ if (!glxtest_pipe)
+ return;
+
+ enum { buf_size = 1024 };
+ 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 = 0;
+
+ // bytesread < 0 would mean that the above read() call failed.
+ // This should never happen. If it did, the outcome would be to blacklist anyway.
+ if (bytesread < 0)
+ bytesread = 0;
+
+ // 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 blacklist anyway.
+ waiting_for_glxtest_process_failed = (waitpid_errno != ECHILD);
+ }
+ }
+ }
+
+ bool exited_with_error_code = !waiting_for_glxtest_process_failed &&
+ WIFEXITED(glxtest_status) &&
+ WEXITSTATUS(glxtest_status) != 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;
+
+ nsCString textureFromPixmap;
+ nsCString *stringToFill = nullptr;
+ char *bufptr = buf;
+ if (!error) {
+ while(true) {
+ char *line = NS_strtok("\n", &bufptr);
+ if (!line)
+ break;
+ if (stringToFill) {
+ stringToFill->Assign(line);
+ stringToFill = nullptr;
+ }
+ else if(!strcmp(line, "VENDOR"))
+ stringToFill = &mVendor;
+ else if(!strcmp(line, "RENDERER"))
+ stringToFill = &mRenderer;
+ else if(!strcmp(line, "VERSION"))
+ stringToFill = &mVersion;
+ else if(!strcmp(line, "TFP"))
+ stringToFill = &textureFromPixmap;
+ }
+ }
+
+ 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)
+ mVendor.Assign(spoofedVendor);
+ const char *spoofedRenderer = PR_GetEnv("MOZ_GFX_SPOOF_GL_RENDERER");
+ if (spoofedRenderer)
+ mRenderer.Assign(spoofedRenderer);
+ const char *spoofedVersion = PR_GetEnv("MOZ_GFX_SPOOF_GL_VERSION");
+ if (spoofedVersion)
+ mVersion.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);
+
+ if (error ||
+ mVendor.IsEmpty() ||
+ mRenderer.IsEmpty() ||
+ mVersion.IsEmpty() ||
+ mOS.IsEmpty() ||
+ mOSRelease.IsEmpty())
+ {
+ mAdapterDescription.AppendLiteral("GLXtest process failed");
+ if (waiting_for_glxtest_process_failed)
+ mAdapterDescription.AppendPrintf(" (waitpid failed with errno=%d for pid %d)", waitpid_errno, glxtest_pid);
+ if (exited_with_error_code)
+ mAdapterDescription.AppendPrintf(" (exited with status %d)", WEXITSTATUS(glxtest_status));
+ if (received_signal)
+ mAdapterDescription.AppendPrintf(" (received signal %d)", WTERMSIG(glxtest_status));
+ if (bytesread) {
+ mAdapterDescription.AppendLiteral(": ");
+ mAdapterDescription.Append(nsDependentCString(buf));
+ mAdapterDescription.Append('\n');
+ }
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::AppendAppNotesToCrashReport(mAdapterDescription);
+#endif
+ return;
+ }
+
+ mAdapterDescription.Append(mVendor);
+ mAdapterDescription.AppendLiteral(" -- ");
+ mAdapterDescription.Append(mRenderer);
+
+ nsAutoCString note;
+ note.AppendLiteral("OpenGL: ");
+ note.Append(mAdapterDescription);
+ note.AppendLiteral(" -- ");
+ note.Append(mVersion);
+ if (mHasTextureFromPixmap)
+ note.AppendLiteral(" -- texture_from_pixmap");
+ note.Append('\n');
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::AppendAppNotesToCrashReport(note);
+#endif
+
+ // determine the major OpenGL version. That's the first integer in the version string.
+ mGLMajorVersion = strtol(mVersion.get(), 0, 10);
+
+ // determine driver type (vendor) and where in the version string
+ // the actual driver version numbers should be expected to be found (whereToReadVersionNumbers)
+ const char *whereToReadVersionNumbers = nullptr;
+ const char *Mesa_in_version_string = strstr(mVersion.get(), "Mesa");
+ if (Mesa_in_version_string) {
+ mIsMesa = true;
+ // with Mesa, the version string contains "Mesa major.minor" and that's all the version information we get:
+ // there is no actual driver version info.
+ whereToReadVersionNumbers = Mesa_in_version_string + strlen("Mesa");
+ if (strcasestr(mVendor.get(), "nouveau"))
+ mIsNouveau = true;
+ if (strcasestr(mRenderer.get(), "intel")) // yes, intel is in the renderer string
+ mIsIntel = true;
+ if (strcasestr(mRenderer.get(), "llvmpipe"))
+ mIsLlvmpipe = true;
+ if (strcasestr(mRenderer.get(), "software rasterizer"))
+ mIsOldSwrast = true;
+ } else if (strstr(mVendor.get(), "NVIDIA Corporation")) {
+ mIsNVIDIA = true;
+ // with the NVIDIA driver, the version string contains "NVIDIA major.minor"
+ // note that here the vendor and version strings behave differently, that's why we don't put this above
+ // alongside Mesa_in_version_string.
+ const char *NVIDIA_in_version_string = strstr(mVersion.get(), "NVIDIA");
+ if (NVIDIA_in_version_string)
+ whereToReadVersionNumbers = NVIDIA_in_version_string + strlen("NVIDIA");
+ } else if (strstr(mVendor.get(), "ATI Technologies Inc")) {
+ mIsFGLRX = true;
+ // with the FGLRX driver, the version string only gives a OpenGL version :/ so let's return that.
+ // that can at least give a rough idea of how old the driver is.
+ whereToReadVersionNumbers = mVersion.get();
+ }
+
+ // read major.minor version numbers of the driver (not to be confused with the OpenGL version)
+ if (whereToReadVersionNumbers) {
+ // copy into writable buffer, for tokenization
+ strncpy(buf, whereToReadVersionNumbers, buf_size);
+ bufptr = buf;
+
+ // now try to read major.minor version numbers. In case of failure, gracefully exit: these numbers have
+ // been initialized as 0 anyways
+ char *token = NS_strtok(".", &bufptr);
+ if (token) {
+ mMajorVersion = strtol(token, 0, 10);
+ token = NS_strtok(".", &bufptr);
+ if (token) {
+ mMinorVersion = strtol(token, 0, 10);
+ token = NS_strtok(".", &bufptr);
+ if (token)
+ mRevisionVersion = strtol(token, 0, 10);
+ }
+ }
+ }
+}
+
+static inline uint64_t version(uint32_t major, uint32_t minor, uint32_t revision = 0)
+{
+ return (uint64_t(major) << 32) + (uint64_t(minor) << 16) + uint64_t(revision);
+}
+
+const nsTArray<GfxDriverInfo>&
+GfxInfo::GetGfxDriverInfo()
+{
+ // Nothing here yet.
+ //if (!mDriverInfo->Length()) {
+ //
+ //}
+ return *mDriverInfo;
+}
+
+nsresult
+GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */)
+
+{
+ GetData();
+
+ NS_ENSURE_ARG_POINTER(aStatus);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ aSuggestedDriverVersion.SetIsVoid(true);
+ OperatingSystem os = OperatingSystem::Linux;
+ if (aOS)
+ *aOS = os;
+
+ 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;
+ }
+
+ // Don't evaluate any special cases if we're checking the downloaded blocklist.
+ if (!aDriverInfo.Length()) {
+ // Blacklist software GL implementations from using layers acceleration.
+ // On the test infrastructure, we'll force-enable layers acceleration.
+ if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS &&
+ (mIsLlvmpipe || mIsOldSwrast) &&
+ !PR_GetEnv("MOZ_LAYERS_ALLOW_SOFTWARE_GL"))
+ {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_SOFTWARE_GL";
+ return NS_OK;
+ }
+
+ // Only check features relevant to Linux.
+ if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS ||
+ aFeature == nsIGfxInfo::FEATURE_WEBGL_OPENGL ||
+ aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA) {
+
+ // whitelist the linux test slaves' current configuration.
+ // this is necessary as they're still using the slightly outdated 190.42 driver.
+ // this isn't a huge risk, as at least this is the exact setting in which we do continuous testing,
+ // and this only affects GeForce 9400 cards on linux on this precise driver version, which is very few users.
+ // We do the same thing on Windows XP, see in widget/windows/GfxInfo.cpp
+ if (mIsNVIDIA &&
+ !strcmp(mRenderer.get(), "GeForce 9400/PCI/SSE2") &&
+ !strcmp(mVersion.get(), "3.2.0 NVIDIA 190.42"))
+ {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ return NS_OK;
+ }
+
+ if (mIsMesa) {
+ if (mIsNouveau && version(mMajorVersion, mMinorVersion) < version(8,0)) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
+ aFailureId = "FEATURE_FAILURE_MESA_1";
+ aSuggestedDriverVersion.AssignLiteral("Mesa 8.0");
+ }
+ else if (version(mMajorVersion, mMinorVersion, mRevisionVersion) < version(7,10,3)) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
+ aFailureId = "FEATURE_FAILURE_MESA_2";
+ aSuggestedDriverVersion.AssignLiteral("Mesa 7.10.3");
+ }
+ else if (mIsOldSwrast) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
+ aFailureId = "FEATURE_FAILURE_SW_RAST";
+ }
+ else if (mIsLlvmpipe && version(mMajorVersion, mMinorVersion) < version(9, 1)) {
+ // bug 791905, Mesa bug 57733, fixed in Mesa 9.1 according to
+ // https://bugs.freedesktop.org/show_bug.cgi?id=57733#c3
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
+ aFailureId = "FEATURE_FAILURE_MESA_3";
+ }
+ else if (aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA)
+ {
+ if (mIsIntel && version(mMajorVersion, mMinorVersion) < version(8,1)) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
+ aFailureId = "FEATURE_FAILURE_MESA_4";
+ aSuggestedDriverVersion.AssignLiteral("Mesa 8.1");
+ }
+ }
+
+ } else if (mIsNVIDIA) {
+ if (version(mMajorVersion, mMinorVersion, mRevisionVersion) < version(257,21)) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
+ aFailureId = "FEATURE_FAILURE_OLD_NV";
+ aSuggestedDriverVersion.AssignLiteral("NVIDIA 257.21");
+ }
+ } else if (mIsFGLRX) {
+ // FGLRX does not report a driver version number, so we have the OpenGL version instead.
+ // by requiring OpenGL 3, we effectively require recent drivers.
+ if (version(mMajorVersion, mMinorVersion, mRevisionVersion) < version(3, 0)) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION;
+ aFailureId = "FEATURE_FAILURE_OLD_FGLRX";
+ aSuggestedDriverVersion.AssignLiteral("<Something recent>");
+ }
+ // Bug 724640: FGLRX + Linux 2.6.32 is a crashy combo
+ bool unknownOS = mOS.IsEmpty() || mOSRelease.IsEmpty();
+ bool badOS = mOS.Find("Linux", true) != -1 &&
+ mOSRelease.Find("2.6.32") != -1;
+ if (unknownOS || badOS) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
+ aFailureId = "FEATURE_FAILURE_OLD_OS";
+ }
+ } else {
+ // like on windows, let's block unknown vendors. Think of virtual machines.
+ // Also, this case is hit whenever the GLXtest probe failed to get driver info or crashed.
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ }
+ }
+ }
+
+ 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::GetCleartypeParameters(nsAString & aCleartypeParams)
+{
+ return NS_ERROR_FAILURE;
+}
+
+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(nsAString & aAdapterRAM)
+{
+ aAdapterRAM.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & 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::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion)
+{
+ GetData();
+ CopyASCIItoUTF16(mVersion, 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(mVendor, aAdapterVendorID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID)
+{
+ GetData();
+ CopyUTF8toUTF16(mRenderer, aAdapterDeviceID);
+ 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;
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+// We don't support spoofing anything on Linux
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString & aVendorID)
+{
+ CopyUTF16toUTF8(aVendorID, mVendor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString & aDeviceID)
+{
+ CopyUTF16toUTF8(aDeviceID, mRenderer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString & aDriverVersion)
+{
+ CopyUTF16toUTF8(aDriverVersion, mVersion);
+ 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;
+}
+
+#endif
+
+} // end namespace widget
+} // end namespace mozilla
diff --git a/widget/GfxInfoX11.h b/widget/GfxInfoX11.h
new file mode 100644
index 000000000..0fd036f46
--- /dev/null
+++ b/widget/GfxInfoX11.h
@@ -0,0 +1,84 @@
+/* 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"
+
+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 GetCleartypeParameters(nsAString & aCleartypeParams) override;
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active) override;
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+ virtual nsresult Init() override;
+
+ NS_IMETHOD_(void) GetData() override;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+protected:
+ ~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:
+ nsCString mVendor;
+ nsCString mRenderer;
+ nsCString mVersion;
+ nsCString mAdapterDescription;
+ nsCString mOS;
+ nsCString mOSRelease;
+ bool mIsMesa, mIsNVIDIA, mIsFGLRX, mIsNouveau, mIsIntel, mIsOldSwrast, mIsLlvmpipe;
+ bool mHasTextureFromPixmap;
+ int mGLMajorVersion, mMajorVersion, mMinorVersion, mRevisionVersion;
+
+ void AddCrashReportAnnotations();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __GfxInfoX11_h__ */
diff --git a/widget/IMEData.h b/widget/IMEData.h
new file mode 100644
index 000000000..b12497be3
--- /dev/null
+++ b/widget/IMEData.h
@@ -0,0 +1,882 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_IMEData_h_
+#define mozilla_widget_IMEData_h_
+
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsStringGlue.h"
+
+class nsIWidget;
+
+namespace mozilla {
+
+class WritingMode;
+
+} // namespace mozilla
+
+/**
+ * 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 nsIMEUpdatePreference 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
+ };
+
+ nsIMEUpdatePreference()
+ : mWantUpdates(NOTIFY_NOTHING)
+ {
+ }
+
+ explicit nsIMEUpdatePreference(Notifications aWantUpdates)
+ : mWantUpdates(aWantUpdates)
+ {
+ }
+
+ nsIMEUpdatePreference operator|(const nsIMEUpdatePreference& aOther) const
+ {
+ return nsIMEUpdatePreference(aOther.mWantUpdates | 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;
+};
+
+/**
+ * Contains IMEStatus plus information about the current
+ * input context that the IME can use as hints if desired.
+ */
+
+namespace mozilla {
+namespace widget {
+
+struct IMEState final
+{
+ /**
+ * IME enabled states, the mEnabled value of
+ * SetInputContext()/GetInputContext() should be one value of following
+ * values.
+ *
+ * WARNING: If you change these values, you also need to edit:
+ * nsIDOMWindowUtils.idl
+ * nsContentUtils::GetWidgetStatusFromIMEStatus
+ */
+ enum Enabled
+ {
+ /**
+ * '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,
+ /**
+ * This state is used when a plugin is focused.
+ * When a plug-in is focused content, we should send native events
+ * directly. Because we don't process some native events, but they may
+ * be needed by the plug-in.
+ */
+ PLUGIN,
+ /**
+ * 'Unknown' is useful when you cache this enum. So, this shouldn't be
+ * used with nsIWidget::SetInputContext().
+ */
+ UNKNOWN
+ };
+ Enabled 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(ENABLED)
+ , mOpen(DONT_CHANGE_OPEN_STATE)
+ {
+ }
+
+ explicit IMEState(Enabled 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 == ENABLED || mEnabled == PASSWORD;
+ }
+ // Returns true if the user might be able to input characters.
+ // This means that a plain text editor, an HTML editor, a password editor,
+ // a plain text editor whose ime-mode is "disabled" or a windowless plugin
+ // has focus.
+ bool MaybeEditable() const
+ {
+ return IsEditable() || mEnabled == PLUGIN;
+ }
+};
+
+// 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()
+ {
+ Init(nullptr);
+ }
+
+ explicit NativeIMEContext(nsIWidget* aWidget)
+ {
+ 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)
+ {
+ }
+
+ bool IsPasswordEditor() const
+ {
+ return mHTMLInputType.LowerCaseEqualsLiteral("password");
+ }
+
+ 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;
+
+ /**
+ * 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;
+
+ bool IsOriginMainProcess() const
+ {
+ return mOrigin == ORIGIN_MAIN;
+ }
+
+ bool IsOriginContentProcess() const
+ {
+ return mOrigin == ORIGIN_CONTENT;
+ }
+
+ bool IsOriginCurrentProcess() const
+ {
+ if (XRE_IsParentProcess()) {
+ return IsOriginMainProcess();
+ }
+ return IsOriginContentProcess();
+ }
+};
+
+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
+ };
+ 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
+ };
+ FocusChange mFocusChange;
+
+ bool ContentGotFocusByTrustedCause() const
+ {
+ return (mFocusChange == GOT_FOCUS &&
+ mCause != CAUSE_UNKNOWN);
+ }
+
+ bool UserMightRequestOpenVKB() const
+ {
+ return (mFocusChange == FOCUS_NOT_CHANGED &&
+ (mCause == CAUSE_MOUSE || mCause == CAUSE_TOUCH));
+ }
+
+ static bool IsUserAction(Cause aCause)
+ {
+ switch (aCause) {
+ case CAUSE_KEY:
+ case CAUSE_MOUSE:
+ case CAUSE_TOUCH:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ 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)
+ {
+ }
+
+ IMENotification(const IMENotification& aOther)
+ : mMessage(NOTIFY_IME_OF_NOTHING)
+ {
+ Assign(aOther);
+ }
+
+ ~IMENotification()
+ {
+ Clear();
+ }
+
+ MOZ_IMPLICIT IMENotification(IMEMessage aMessage)
+ : mMessage(aMessage)
+ {
+ 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)
+ {
+ mX = aRect.x;
+ mY = aRect.y;
+ mWidth = aRect.width;
+ mHeight = aRect.height;
+ }
+ 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;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef mozilla_widget_IMEData_h_
diff --git a/widget/InProcessCompositorWidget.cpp b/widget/InProcessCompositorWidget.cpp
new file mode 100644
index 000000000..9b301fc48
--- /dev/null
+++ b/widget/InProcessCompositorWidget.cpp
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "InProcessCompositorWidget.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, nsIWidget* aWidget)
+{
+ MOZ_ASSERT(aWidget);
+#ifdef MOZ_WIDGET_ANDROID
+ return new AndroidCompositorWidget(static_cast<nsBaseWidget*>(aWidget));
+#else
+ return new InProcessCompositorWidget(static_cast<nsBaseWidget*>(aWidget));
+#endif
+}
+#endif
+
+InProcessCompositorWidget::InProcessCompositorWidget(nsBaseWidget* aWidget)
+ : mWidget(aWidget)
+{
+}
+
+bool
+InProcessCompositorWidget::PreRender(WidgetRenderingContext* aContext)
+{
+ return mWidget->PreRender(aContext);
+}
+
+void
+InProcessCompositorWidget::PostRender(WidgetRenderingContext* aContext)
+{
+ mWidget->PostRender(aContext);
+}
+
+void
+InProcessCompositorWidget::DrawWindowUnderlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+{
+ mWidget->DrawWindowUnderlay(aContext, aRect);
+}
+
+void
+InProcessCompositorWidget::DrawWindowOverlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+{
+ mWidget->DrawWindowOverlay(aContext, aRect);
+}
+
+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,
+ 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();
+}
+
+layers::Composer2D*
+InProcessCompositorWidget::GetComposer2D()
+{
+ return mWidget->GetComposer2D();
+}
+
+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 000000000..ae9e51b3b
--- /dev/null
+++ b/widget/InProcessCompositorWidget.h
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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(nsBaseWidget* aWidget);
+
+ virtual bool PreRender(WidgetRenderingContext* aManager) override;
+ virtual void PostRender(WidgetRenderingContext* aManager) override;
+ virtual void DrawWindowUnderlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect) override;
+ virtual void DrawWindowOverlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect) 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,
+ 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 layers::Composer2D* GetComposer2D() 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;
+
+private:
+ nsBaseWidget* mWidget;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/InputData.cpp b/widget/InputData.cpp
new file mode 100644
index 000000000..70072778d
--- /dev/null
+++ b/widget/InputData.cpp
@@ -0,0 +1,777 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/Touch.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()
+{
+}
+
+InputData::InputData(InputType aInputType)
+ : mInputType(aInputType)
+ , mTime(0)
+ , modifiers(0)
+{
+}
+
+InputData::InputData(InputType aInputType, uint32_t aTime, TimeStamp aTimeStamp,
+ Modifiers aModifiers)
+ : mInputType(aInputType)
+ , mTime(aTime)
+ , mTimeStamp(aTimeStamp)
+ , 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()
+{
+}
+
+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)
+ , mHandledByAPZ(false)
+{
+}
+
+MultiTouchInput::MultiTouchInput(const MultiTouchInput& aOther)
+ : InputData(MULTITOUCH_INPUT, aOther.mTime, aOther.mTimeStamp, aOther.modifiers)
+ , mType(aOther.mType)
+ , 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;
+ }
+
+ 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();
+ int32_t radiusY = domTouch->RadiusY();
+ float rotationAngle = domTouch->RotationAngle();
+ float force = domTouch->Force();
+
+ SingleTouchData data(identifier,
+ ViewAs<ScreenPixel>(domTouch->mRefPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent),
+ ScreenSize(radiusX, radiusY),
+ rotationAngle,
+ force);
+
+ mTouches.AppendElement(data);
+ }
+}
+
+MultiTouchInput::MultiTouchInput(const WidgetMouseEvent& aMouseEvent)
+ : InputData(MULTITOUCH_INPUT, aMouseEvent.mTime, aMouseEvent.mTimeStamp,
+ aMouseEvent.mModifiers)
+ , mHandledByAPZ(aMouseEvent.mFlags.mHandledByAPZ)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can only copy from WidgetMouseEvent on main thread");
+ switch (aMouseEvent.mMessage) {
+ case eMouseDown:
+ mType = MULTITOUCH_START;
+ break;
+ case eMouseMove:
+ mType = MULTITOUCH_MOVE;
+ break;
+ case eMouseUp:
+ mType = MULTITOUCH_END;
+ break;
+ // The mouse pointer has been interrupted in an implementation-specific
+ // manner, such as a synchronous event or action cancelling the touch, or a
+ // touch point leaving the document window and going into a non-document
+ // area capable of handling user interactions.
+ case eMouseExitFromWidget:
+ mType = MULTITOUCH_CANCEL;
+ break;
+ default:
+ NS_WARNING("Did not assign a type to a MultiTouchInput");
+ break;
+ }
+
+ mTouches.AppendElement(SingleTouchData(0,
+ ViewAs<ScreenPixel>(aMouseEvent.mRefPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent),
+ ScreenSize(1, 1),
+ 180.0f,
+ 1.0f));
+}
+
+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;
+
+ for (size_t i = 0; i < mTouches.Length(); i++) {
+ *event.mTouches.AppendElement() = mTouches[i].ToNewDOMTouch();
+ }
+
+ return event;
+}
+
+WidgetMouseEvent
+MultiTouchInput::ToWidgetMouseEvent(nsIWidget* aWidget) const
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can only convert To WidgetMouseEvent on main thread");
+
+ EventMessage mouseEventMessage = eVoidEvent;
+ switch (mType) {
+ case MultiTouchInput::MULTITOUCH_START:
+ mouseEventMessage = eMouseDown;
+ break;
+ case MultiTouchInput::MULTITOUCH_MOVE:
+ mouseEventMessage = eMouseMove;
+ break;
+ case MultiTouchInput::MULTITOUCH_CANCEL:
+ case MultiTouchInput::MULTITOUCH_END:
+ mouseEventMessage = eMouseUp;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Did not assign a type to WidgetMouseEvent");
+ break;
+ }
+
+ WidgetMouseEvent event(true, mouseEventMessage, aWidget,
+ WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
+
+ const SingleTouchData& firstTouch = mTouches[0];
+ event.mRefPoint.x = firstTouch.mScreenPoint.x;
+ event.mRefPoint.y = firstTouch.mScreenPoint.y;
+
+ event.mTime = mTime;
+ event.button = WidgetMouseEvent::eLeftButton;
+ event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+ event.mModifiers = modifiers;
+ event.mFlags.mHandledByAPZ = mHandledByAPZ;
+
+ if (mouseEventMessage != eMouseMove) {
+ event.mClickCount = 1;
+ }
+
+ 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 (size_t i = 0; i < mTouches.Length(); i++) {
+ Maybe<ParentLayerIntPoint> point = UntransformBy(aTransform, mTouches[i].mScreenPoint);
+ if (!point) {
+ return false;
+ }
+ mTouches[i].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.inputSource)
+ , mButtons(aMouseEvent.buttons)
+ , mHandledByAPZ(aMouseEvent.mFlags.mHandledByAPZ)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can only copy from WidgetTouchEvent on main thread");
+
+ mButtonType = NONE;
+
+ switch (aMouseEvent.button) {
+ case WidgetMouseEventBase::eLeftButton:
+ mButtonType = MouseInput::LEFT_BUTTON;
+ break;
+ case WidgetMouseEventBase::eMiddleButton:
+ mButtonType = MouseInput::MIDDLE_BUTTON;
+ break;
+ case WidgetMouseEventBase::eRightButton:
+ mButtonType = MouseInput::RIGHT_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;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Mouse event type not supported");
+ break;
+ }
+
+ mOrigin =
+ ScreenPoint(ViewAs<ScreenPixel>(aMouseEvent.mRefPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+}
+
+bool
+MouseInput::IsLeftButton() const
+{
+ return mButtonType == LEFT_BUTTON;
+}
+
+bool
+MouseInput::TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform)
+{
+ Maybe<ParentLayerPoint> point = UntransformBy(aTransform, mOrigin);
+ if (!point) {
+ return false;
+ }
+ mLocalOrigin = *point;
+
+ return true;
+}
+
+WidgetMouseEvent
+MouseInput::ToWidgetMouseEvent(nsIWidget* aWidget) const
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can only convert To WidgetTouchEvent on main thread");
+
+ EventMessage msg = eVoidEvent;
+ uint32_t clickCount = 0;
+ 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;
+ 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::LEFT_BUTTON:
+ event.button = WidgetMouseEventBase::eLeftButton;
+ break;
+ case MouseInput::MIDDLE_BUTTON:
+ event.button = WidgetMouseEventBase::eMiddleButton;
+ break;
+ case MouseInput::RIGHT_BUTTON:
+ event.button = WidgetMouseEventBase::eRightButton;
+ break;
+ case MouseInput::NONE:
+ default:
+ break;
+ }
+
+ event.buttons = mButtons;
+ event.mModifiers = modifiers;
+ event.mTime = mTime;
+ event.mTimeStamp = mTimeStamp;
+ event.mFlags.mHandledByAPZ = mHandledByAPZ;
+ event.mRefPoint =
+ RoundedToInt(ViewAs<LayoutDevicePixel>(mOrigin,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+ event.mClickCount = clickCount;
+ event.inputSource = mInputSource;
+ event.mIgnoreRootScrollFrame = true;
+
+ return event;
+}
+
+PanGestureInput::PanGestureInput()
+ : InputData(PANGESTURE_INPUT)
+ , mLineOrPageDeltaX(0)
+ , mLineOrPageDeltaY(0)
+ , mUserDeltaMultiplierX(1.0)
+ , mUserDeltaMultiplierY(1.0)
+ , mHandledByAPZ(false)
+ , mFollowedByMomentum(false)
+ , mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(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)
+{
+}
+
+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::ToWidgetWheelEvent(nsIWidget* aWidget) const
+{
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ wheelEvent.mModifiers = this->modifiers;
+ wheelEvent.mTime = mTime;
+ wheelEvent.mTimeStamp = mTimeStamp;
+ wheelEvent.mRefPoint =
+ RoundedToInt(ViewAs<LayoutDevicePixel>(mPanStartPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+ wheelEvent.buttons = 0;
+ wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_PIXEL;
+ 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;
+ return wheelEvent;
+}
+
+bool
+PanGestureInput::TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform)
+{
+ Maybe<ParentLayerPoint> panStartPoint = UntransformBy(aTransform, mPanStartPoint);
+ if (!panStartPoint) {
+ return false;
+ }
+ mLocalPanStartPoint = *panStartPoint;
+
+ 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);
+}
+
+PinchGestureInput::PinchGestureInput()
+ : InputData(PINCHGESTURE_INPUT)
+{
+}
+
+PinchGestureInput::PinchGestureInput(PinchGestureType aType, uint32_t aTime,
+ TimeStamp aTimeStamp,
+ const ParentLayerPoint& aLocalFocusPoint,
+ ParentLayerCoord aCurrentSpan,
+ ParentLayerCoord aPreviousSpan,
+ Modifiers aModifiers)
+ : InputData(PINCHGESTURE_INPUT, aTime, aTimeStamp, aModifiers)
+ , mType(aType)
+ , mLocalFocusPoint(aLocalFocusPoint)
+ , mCurrentSpan(aCurrentSpan)
+ , mPreviousSpan(aPreviousSpan)
+{
+}
+
+bool
+PinchGestureInput::TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform)
+{
+ Maybe<ParentLayerPoint> point = UntransformBy(aTransform, mFocusPoint);
+ if (!point) {
+ return false;
+ }
+ mLocalFocusPoint = *point;
+ return true;
+}
+
+TapGestureInput::TapGestureInput()
+ : InputData(TAPGESTURE_INPUT)
+{
+}
+
+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)
+ , mHandledByAPZ(false)
+ , mLineOrPageDeltaX(0)
+ , mLineOrPageDeltaY(0)
+ , mScrollSeriesNumber(0)
+ , mUserDeltaMultiplierX(1.0)
+ , mUserDeltaMultiplierY(1.0)
+ , mMayHaveMomentum(false)
+ , mIsMomentum(false)
+{
+}
+
+ScrollWheelInput::ScrollWheelInput(uint32_t aTime, TimeStamp aTimeStamp,
+ Modifiers aModifiers, ScrollMode aScrollMode,
+ ScrollDeltaType aDeltaType,
+ const ScreenPoint& aOrigin, double aDeltaX,
+ double aDeltaY,
+ bool aAllowToOverrideSystemScrollSpeed)
+ : 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)
+{
+}
+
+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)
+{
+ mOrigin =
+ ScreenPoint(ViewAs<ScreenPixel>(aWheelEvent.mRefPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+}
+
+ScrollWheelInput::ScrollDeltaType
+ScrollWheelInput::DeltaTypeForDeltaMode(uint32_t aDeltaMode)
+{
+ switch (aDeltaMode) {
+ case nsIDOMWheelEvent::DOM_DELTA_LINE:
+ return SCROLLDELTA_LINE;
+ case nsIDOMWheelEvent::DOM_DELTA_PAGE:
+ return SCROLLDELTA_PAGE;
+ case nsIDOMWheelEvent::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 nsIDOMWheelEvent::DOM_DELTA_LINE;
+ case ScrollWheelInput::SCROLLDELTA_PAGE:
+ return nsIDOMWheelEvent::DOM_DELTA_PAGE;
+ case ScrollWheelInput::SCROLLDELTA_PIXEL:
+ default:
+ return nsIDOMWheelEvent::DOM_DELTA_PIXEL;
+ }
+}
+
+nsIScrollableFrame::ScrollUnit
+ScrollWheelInput::ScrollUnitForDeltaType(ScrollDeltaType aDeltaType)
+{
+ switch (aDeltaType) {
+ case SCROLLDELTA_LINE:
+ return nsIScrollableFrame::LINES;
+ case SCROLLDELTA_PAGE:
+ return nsIScrollableFrame::PAGES;
+ case SCROLLDELTA_PIXEL:
+ return nsIScrollableFrame::DEVICE_PIXELS;
+ default:
+ MOZ_CRASH();
+ }
+ return nsIScrollableFrame::LINES;
+}
+
+WidgetWheelEvent
+ScrollWheelInput::ToWidgetWheelEvent(nsIWidget* aWidget) const
+{
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ wheelEvent.mModifiers = this->modifiers;
+ wheelEvent.mTime = mTime;
+ wheelEvent.mTimeStamp = mTimeStamp;
+ wheelEvent.mRefPoint =
+ RoundedToInt(ViewAs<LayoutDevicePixel>(mOrigin,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
+ wheelEvent.buttons = 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;
+ 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;
+}
+
+} // namespace mozilla
diff --git a/widget/InputData.h b/widget/InputData.h
new file mode 100644
index 000000000..347653e97
--- /dev/null
+++ b/widget/InputData.h
@@ -0,0 +1,617 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIDOMWheelEvent.h"
+#include "nsIScrollableFrame.h"
+#include "nsPoint.h"
+#include "nsTArray.h"
+#include "Units.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/gfx/MatrixFwd.h"
+
+template<class E> struct already_AddRefed;
+class nsIWidget;
+
+namespace mozilla {
+
+namespace layers {
+class PAPZCTreeManagerParent;
+class APZCTreeManagerChild;
+}
+
+namespace dom {
+class Touch;
+} // namespace dom
+
+enum InputType
+{
+ // Warning, this enum is serialized and sent over IPC. If you reorder, add,
+ // or remove a value, you need to update its ParamTraits<> in nsGUIEventIPC.h
+ MULTITOUCH_INPUT,
+ MOUSE_INPUT,
+ PANGESTURE_INPUT,
+ PINCHGESTURE_INPUT,
+ TAPGESTURE_INPUT,
+ SCROLLWHEEL_INPUT,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ SENTINEL_INPUT,
+};
+
+class MultiTouchInput;
+class MouseInput;
+class PanGestureInput;
+class PinchGestureInput;
+class TapGestureInput;
+class ScrollWheelInput;
+
+// 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;
+
+ 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)
+
+ 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
+
+ // 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:
+ enum MultiTouchType
+ {
+ // Warning, this enum is serialized and sent over IPC. If you reorder, add,
+ // or remove a value, you need to update its ParamTraits<> in nsGUIEventIPC.h
+ MULTITOUCH_START,
+ MULTITOUCH_MOVE,
+ MULTITOUCH_END,
+ MULTITOUCH_CANCEL,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ MULTITOUCH_SENTINEL,
+ };
+
+ MultiTouchInput(MultiTouchType aType, uint32_t aTime, TimeStamp aTimeStamp,
+ Modifiers aModifiers);
+ MultiTouchInput();
+ MultiTouchInput(const MultiTouchInput& aOther);
+ explicit MultiTouchInput(const WidgetTouchEvent& aTouchEvent);
+ // This conversion from WidgetMouseEvent to MultiTouchInput is needed because
+ // on the B2G emulator we can only receive mouse events, but we need to be
+ // able to pan correctly. To do this, we convert the events into a format that
+ // the panning code can handle. This code is very limited and only supports
+ // SingleTouchData. It also sends garbage for the identifier, radius, force
+ // and rotation angle.
+ explicit MultiTouchInput(const WidgetMouseEvent& aMouseEvent);
+
+ WidgetTouchEvent ToWidgetTouchEvent(nsIWidget* aWidget) const;
+ WidgetMouseEvent ToWidgetMouseEvent(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;
+ nsTArray<SingleTouchData> mTouches;
+ bool mHandledByAPZ;
+};
+
+class MouseInput : public InputData
+{
+protected:
+ friend mozilla::layers::PAPZCTreeManagerParent;
+ friend mozilla::layers::APZCTreeManagerChild;
+
+ MouseInput();
+
+public:
+ enum MouseType
+ {
+ // Warning, this enum is serialized and sent over IPC. If you reorder, add,
+ // or remove a value, you need to update its ParamTraits<> in nsGUIEventIPC.h
+ MOUSE_NONE,
+ MOUSE_MOVE,
+ MOUSE_DOWN,
+ MOUSE_UP,
+ MOUSE_DRAG_START,
+ MOUSE_DRAG_END,
+ MOUSE_WIDGET_ENTER,
+ MOUSE_WIDGET_EXIT,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ MOUSE_SENTINEL,
+ };
+
+ enum ButtonType
+ {
+ // Warning, this enum is serialized and sent over IPC. If you reorder, add,
+ // or remove a value, you need to update its ParamTraits<> in nsGUIEventIPC.h
+ LEFT_BUTTON,
+ MIDDLE_BUTTON,
+ RIGHT_BUTTON,
+ NONE,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ BUTTON_SENTINEL,
+ };
+
+ 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 ToWidgetMouseEvent(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::PAPZCTreeManagerParent;
+ friend mozilla::layers::APZCTreeManagerChild;
+
+ PanGestureInput();
+
+public:
+ enum PanGestureType
+ {
+ // Warning, this enum is serialized and sent over IPC. If you reorder, add,
+ // or remove a value, you need to update its ParamTraits<> in nsGUIEventIPC.h
+
+ // 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,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ PANGESTURE_SENTINEL,
+ };
+
+ PanGestureInput(PanGestureType aType,
+ uint32_t aTime,
+ TimeStamp aTimeStamp,
+ const ScreenPoint& aPanStartPoint,
+ const ScreenPoint& aPanDisplacement,
+ Modifiers aModifiers);
+
+ bool IsMomentum() const;
+
+ WidgetWheelEvent ToWidgetWheelEvent(nsIWidget* aWidget) const;
+
+ bool TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform);
+
+ ScreenPoint UserMultipliedPanDisplacement() const;
+ ParentLayerPoint UserMultipliedLocalPanDisplacement() const;
+
+ // 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;
+
+ bool mHandledByAPZ;
+
+ // true if this is a PANGESTURE_END event that will be followed by a
+ // PANGESTURE_MOMENTUMSTART event.
+ bool mFollowedByMomentum;
+
+ // 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;
+};
+
+/**
+ * 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::PAPZCTreeManagerParent;
+ friend mozilla::layers::APZCTreeManagerChild;
+
+ PinchGestureInput();
+
+public:
+ enum PinchGestureType
+ {
+ // Warning, this enum is serialized and sent over IPC. If you reorder, add,
+ // or remove a value, you need to update its ParamTraits<> in nsGUIEventIPC.h
+ PINCHGESTURE_START,
+ PINCHGESTURE_SCALE,
+ PINCHGESTURE_END,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ PINCHGESTURE_SENTINEL,
+ };
+
+ // Construct a pinch gesture from a ParentLayer point.
+ // mFocusPoint remains (0,0) unless it's set later.
+ PinchGestureInput(PinchGestureType aType, uint32_t aTime, TimeStamp aTimeStamp,
+ const ParentLayerPoint& aLocalFocusPoint,
+ ParentLayerCoord aCurrentSpan,
+ ParentLayerCoord aPreviousSpan, 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
+ PinchGestureType mType;
+
+ // 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 instead will hold the coordinates of
+ // the remaining finger, if there is one. If there isn't one then it will
+ // store -1, -1.
+ ScreenPoint mFocusPoint;
+
+ // |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.
+ ParentLayerCoord 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.
+ ParentLayerCoord mPreviousSpan;
+};
+
+/**
+ * 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::PAPZCTreeManagerParent;
+ friend mozilla::layers::APZCTreeManagerChild;
+
+ TapGestureInput();
+
+public:
+ enum TapGestureType
+ {
+ // Warning, this enum is serialized and sent over IPC. If you reorder, add,
+ // or remove a value, you need to update its ParamTraits<> in nsGUIEventIPC.h
+ TAPGESTURE_LONG,
+ TAPGESTURE_LONG_UP,
+ TAPGESTURE_UP,
+ TAPGESTURE_CONFIRMED,
+ TAPGESTURE_DOUBLE,
+ TAPGESTURE_SECOND, // See GeckoContentController::TapType::eSecondTap
+ TAPGESTURE_CANCEL,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ TAPGESTURE_SENTINEL,
+ };
+
+ // 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::PAPZCTreeManagerParent;
+ friend mozilla::layers::APZCTreeManagerChild;
+
+ ScrollWheelInput();
+
+public:
+ enum ScrollDeltaType
+ {
+ // Warning, this enum is serialized and sent over IPC. If you reorder, add,
+ // or remove a value, you need to update its ParamTraits<> in nsGUIEventIPC.h
+
+ // There are three kinds of scroll delta modes in Gecko: "page", "line" and
+ // "pixel".
+ SCROLLDELTA_LINE,
+ SCROLLDELTA_PAGE,
+ SCROLLDELTA_PIXEL,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ SCROLLDELTA_SENTINEL,
+ };
+
+ enum ScrollMode
+ {
+ // Warning, this enum is serialized and sent over IPC. If you reorder, add,
+ // or remove a value, you need to update its ParamTraits<> in nsGUIEventIPC.h
+
+ SCROLLMODE_INSTANT,
+ SCROLLMODE_SMOOTH,
+
+ // Used as an upper bound for ContiguousEnumSerializer
+ SCROLLMODE_SENTINEL,
+ };
+
+ ScrollWheelInput(uint32_t aTime, TimeStamp aTimeStamp, Modifiers aModifiers,
+ ScrollMode aScrollMode, ScrollDeltaType aDeltaType,
+ const ScreenPoint& aOrigin, double aDeltaX, double aDeltaY,
+ bool aAllowToOverrideSystemScrollSpeed);
+ explicit ScrollWheelInput(const WidgetWheelEvent& aEvent);
+
+ static ScrollDeltaType DeltaTypeForDeltaMode(uint32_t aDeltaMode);
+ static uint32_t DeltaModeForDeltaType(ScrollDeltaType aDeltaType);
+ static nsIScrollableFrame::ScrollUnit ScrollUnitForDeltaType(ScrollDeltaType aDeltaType);
+
+ WidgetWheelEvent ToWidgetWheelEvent(nsIWidget* aWidget) const;
+ bool TransformToLocal(const ScreenToParentLayerMatrix4x4& aTransform);
+
+ bool IsCustomizedByUserPrefs() const;
+
+ // 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;
+};
+
+} // namespace mozilla
+
+#endif // InputData_h__
diff --git a/widget/LSBUtils.cpp b/widget/LSBUtils.cpp
new file mode 100644
index 000000000..fdb954425
--- /dev/null
+++ b/widget/LSBUtils.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 "LSBUtils.h"
+
+#include <unistd.h>
+#include "base/process_util.h"
+
+namespace mozilla {
+namespace widget {
+namespace 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"
+ };
+
+ std::vector<std::pair<int, int>> fdMap = {
+ { pipefd[1], STDOUT_FILENO }
+ };
+
+ base::ProcessHandle process;
+ base::LaunchApp(argv, fdMap, true, &process);
+ close(pipefd[1]);
+ if (!process) {
+ NS_WARNING("Failed to spawn lsb_release!");
+ close(pipefd[0]);
+ return false;
+ }
+
+ FILE* 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!");
+ fclose(stream);
+ close(pipefd[0]);
+ return false;
+ }
+ fclose(stream);
+ close(pipefd[0]);
+
+ aDistributor.Assign(dist);
+ aDescription.Assign(desc);
+ aRelease.Assign(release);
+ aCodename.Assign(codename);
+ return true;
+}
+
+} // namespace lsb
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/LSBUtils.h b/widget/LSBUtils.h
new file mode 100644
index 000000000..1f1614936
--- /dev/null
+++ b/widget/LSBUtils.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _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 000000000..3a4929c9f
--- /dev/null
+++ b/widget/LookAndFeel.h
@@ -0,0 +1,640 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsTArray.h"
+
+struct gfxFontStyle;
+
+struct LookAndFeelInt
+{
+ int32_t id;
+ int32_t value;
+};
+
+namespace mozilla {
+
+class LookAndFeel
+{
+public:
+ // When modifying this list, also modify nsXPLookAndFeel::sColorPrefs
+ // in widget/xpwidgts/nsXPLookAndFeel.cpp.
+ enum ColorID : uint8_t {
+
+ // WARNING : NO NEGATIVE VALUE IN THIS ENUMERATION
+ // see patch in bug 57757 for more information
+
+ eColorID_WindowBackground,
+ eColorID_WindowForeground,
+ eColorID_WidgetBackground,
+ eColorID_WidgetForeground,
+ eColorID_WidgetSelectBackground,
+ eColorID_WidgetSelectForeground,
+ eColorID_Widget3DHighlight,
+ eColorID_Widget3DShadow,
+ eColorID_TextBackground,
+ eColorID_TextForeground,
+ eColorID_TextSelectBackground,
+ eColorID_TextSelectForeground,
+ eColorID_TextSelectForegroundCustom,
+ eColorID_TextSelectBackgroundDisabled,
+ eColorID_TextSelectBackgroundAttention,
+ eColorID_TextHighlightBackground,
+ eColorID_TextHighlightForeground,
+
+ eColorID_IMERawInputBackground,
+ eColorID_IMERawInputForeground,
+ eColorID_IMERawInputUnderline,
+ eColorID_IMESelectedRawTextBackground,
+ eColorID_IMESelectedRawTextForeground,
+ eColorID_IMESelectedRawTextUnderline,
+ eColorID_IMEConvertedTextBackground,
+ eColorID_IMEConvertedTextForeground,
+ eColorID_IMEConvertedTextUnderline,
+ eColorID_IMESelectedConvertedTextBackground,
+ eColorID_IMESelectedConvertedTextForeground,
+ eColorID_IMESelectedConvertedTextUnderline,
+
+ eColorID_SpellCheckerUnderline,
+
+ // New CSS 2 color definitions
+ eColorID_activeborder,
+ eColorID_activecaption,
+ eColorID_appworkspace,
+ eColorID_background,
+ eColorID_buttonface,
+ eColorID_buttonhighlight,
+ eColorID_buttonshadow,
+ eColorID_buttontext,
+ eColorID_captiontext,
+ eColorID_graytext,
+ eColorID_highlight,
+ eColorID_highlighttext,
+ eColorID_inactiveborder,
+ eColorID_inactivecaption,
+ eColorID_inactivecaptiontext,
+ eColorID_infobackground,
+ eColorID_infotext,
+ eColorID_menu,
+ eColorID_menutext,
+ eColorID_scrollbar,
+ eColorID_threeddarkshadow,
+ eColorID_threedface,
+ eColorID_threedhighlight,
+ eColorID_threedlightshadow,
+ eColorID_threedshadow,
+ eColorID_window,
+ eColorID_windowframe,
+ eColorID_windowtext,
+
+ eColorID__moz_buttondefault,
+ // Colors which will hopefully become CSS3
+ eColorID__moz_field,
+ eColorID__moz_fieldtext,
+ eColorID__moz_dialog,
+ eColorID__moz_dialogtext,
+ // used to highlight valid regions to drop something onto
+ eColorID__moz_dragtargetzone,
+
+ // used to cell text background, selected but not focus
+ eColorID__moz_cellhighlight,
+ // used to cell text, selected but not focus
+ eColorID__moz_cellhighlighttext,
+ // used to html select cell text background, selected but not focus
+ eColorID__moz_html_cellhighlight,
+ // used to html select cell text, selected but not focus
+ eColorID__moz_html_cellhighlighttext,
+ // used to button text background, when mouse is over
+ eColorID__moz_buttonhoverface,
+ // used to button text, when mouse is over
+ eColorID__moz_buttonhovertext,
+ // used to menu item background, when mouse is over
+ eColorID__moz_menuhover,
+ // used to menu item text, when mouse is over
+ eColorID__moz_menuhovertext,
+ // used to menu bar item text
+ eColorID__moz_menubartext,
+ // used to menu bar item text, when mouse is over
+ eColorID__moz_menubarhovertext,
+ // On platforms where these colors are the same as
+ // -moz-field, use -moz-fieldtext as foreground color
+ eColorID__moz_eventreerow,
+ eColorID__moz_oddtreerow,
+
+ // colors needed by the Mac OS X theme
+
+ // foreground color of :hover:active buttons
+ eColorID__moz_mac_buttonactivetext,
+ // background color of chrome toolbars in active windows
+ eColorID__moz_mac_chrome_active,
+ // background color of chrome toolbars in inactive windows
+ eColorID__moz_mac_chrome_inactive,
+ // foreground color of default buttons
+ eColorID__moz_mac_defaultbuttontext,
+ //ring around text fields and lists
+ eColorID__moz_mac_focusring,
+ //colour used when mouse is over a menu item
+ eColorID__moz_mac_menuselect,
+ //colour used to do shadows on menu items
+ eColorID__moz_mac_menushadow,
+ // color used to display text for disabled menu items
+ eColorID__moz_mac_menutextdisable,
+ //colour used to display text while mouse is over a menu item
+ eColorID__moz_mac_menutextselect,
+ // text color of disabled text on toolbars
+ eColorID__moz_mac_disabledtoolbartext,
+ //inactive light hightlight
+ eColorID__moz_mac_secondaryhighlight,
+
+ // vista rebars
+
+ // media rebar text
+ eColorID__moz_win_mediatext,
+ // communications rebar text
+ eColorID__moz_win_communicationstext,
+
+ // Hyperlink color extracted from the system, not affected by the
+ // browser.anchor_color user pref.
+ // There is no OS-specified safe background color for this text,
+ // but it is used regularly within Windows and the Gnome DE on Dialog and
+ // Window colors.
+ eColorID__moz_nativehyperlinktext,
+
+ // Combo box widgets
+ eColorID__moz_comboboxtext,
+ eColorID__moz_combobox,
+
+ // GtkInfoBar
+ eColorID__moz_gtk_info_bar_text,
+
+ // keep this one last, please
+ eColorID_LAST_COLOR
+ };
+
+ // When modifying this list, also modify nsXPLookAndFeel::sIntPrefs
+ // in widget/xpwidgts/nsXPLookAndFeel.cpp.
+ enum IntID {
+ // default, may be overriden by OS
+ eIntID_CaretBlinkTime,
+ // pixel width of caret
+ eIntID_CaretWidth,
+ // show the caret when text is selected?
+ eIntID_ShowCaretDuringSelection,
+ // select textfields when focused via tab/accesskey?
+ eIntID_SelectTextfieldsOnKeyFocus,
+ // delay before submenus open
+ eIntID_SubmenuDelay,
+ // can popups overlap menu/task bar?
+ eIntID_MenusCanOverlapOSBar,
+ // should overlay scrollbars be used?
+ eIntID_UseOverlayScrollbars,
+ // allow H and V overlay scrollbars to overlap?
+ eIntID_AllowOverlayScrollbarsOverlap,
+ // show/hide scrollbars based on activity
+ eIntID_ShowHideScrollbars,
+ // skip navigating to disabled menu item?
+ eIntID_SkipNavigatingDisabledMenuItem,
+ // begin a drag if the mouse is moved further than the threshold while the
+ // button is down
+ eIntID_DragThresholdX,
+ eIntID_DragThresholdY,
+ // Accessibility theme being used?
+ eIntID_UseAccessibilityTheme,
+
+ // position of scroll arrows in a scrollbar
+ eIntID_ScrollArrowStyle,
+ // is scroll thumb proportional or fixed?
+ eIntID_ScrollSliderStyle,
+
+ // each button can take one of four values:
+ eIntID_ScrollButtonLeftMouseButtonAction,
+ // 0 - scrolls one line, 1 - scrolls one page
+ eIntID_ScrollButtonMiddleMouseButtonAction,
+ // 2 - scrolls to end, 3 - button ignored
+ eIntID_ScrollButtonRightMouseButtonAction,
+
+ // delay for opening spring loaded folders
+ eIntID_TreeOpenDelay,
+ // delay for closing spring loaded folders
+ eIntID_TreeCloseDelay,
+ // delay for triggering the tree scrolling
+ eIntID_TreeLazyScrollDelay,
+ // delay for scrolling the tree
+ eIntID_TreeScrollDelay,
+ // the maximum number of lines to be scrolled at ones
+ eIntID_TreeScrollLinesMax,
+ // What type of tab-order to use
+ eIntID_TabFocusModel,
+ // Should menu items blink when they're chosen?
+ eIntID_ChosenMenuItemsShouldBlink,
+
+ /*
+ * 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.
+ */
+ eIntID_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.
+ */
+ eIntID_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).
+ */
+ eIntID_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).
+ */
+ eIntID_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.
+ */
+ eIntID_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.
+ */
+ eIntID_MacGraphiteTheme,
+
+ /*
+ * A Boolean value to determine whether the Mac OS X Yosemite-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.
+ */
+ eIntID_MacYosemiteTheme,
+
+ /*
+ * eIntID_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
+ */
+ eIntID_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.
+ */
+ eIntID_ScrollToClick,
+
+ /**
+ * IME and spell checker underline styles, the values should be
+ * NS_DECORATION_LINE_STYLE_*. They are defined below.
+ */
+ eIntID_IMERawInputUnderlineStyle,
+ eIntID_IMESelectedRawTextUnderlineStyle,
+ eIntID_IMEConvertedTextUnderlineStyle,
+ eIntID_IMESelectedConvertedTextUnderline,
+ eIntID_SpellCheckerUnderlineStyle,
+
+ /**
+ * If this metric != 0, support window dragging on the menubar.
+ */
+ eIntID_MenuBarDrag,
+ /**
+ * Return the appropriate WindowsThemeIdentifier for the current theme.
+ */
+ eIntID_WindowsThemeIdentifier,
+ /**
+ * Return an appropriate os version identifier.
+ */
+ eIntID_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.
+ */
+ eIntID_ScrollbarButtonAutoRepeatBehavior,
+ /**
+ * Delay before showing a tooltip.
+ */
+ eIntID_TooltipDelay,
+ /*
+ * A Boolean value to determine whether Mac OS X Lion style swipe animations
+ * should be used.
+ */
+ eIntID_SwipeAnimationEnabled,
+
+ /*
+ * A Boolean value to determine whether we have a color picker available
+ * for <input type="color"> to hook into.
+ *
+ * This lets us selectively enable the style for <input type="color">
+ * based on whether it's functional or not.
+ */
+ eIntID_ColorPickerAvailable,
+
+ /*
+ * A boolean value indicating whether or not the device has a hardware
+ * home button. Used on gaia to determine whether a home button
+ * is shown.
+ */
+ eIntID_PhysicalHomeButton,
+
+ /*
+ * Controls whether overlay scrollbars display when the user moves
+ * the mouse in a scrollable frame.
+ */
+ eIntID_ScrollbarDisplayOnMouseMove,
+
+ /*
+ * Overlay scrollbar animation constants.
+ */
+ eIntID_ScrollbarFadeBeginDelay,
+ eIntID_ScrollbarFadeDuration,
+
+ /**
+ * Distance in pixels to offset the context menu from the cursor
+ * on open.
+ */
+ eIntID_ContextMenuOffsetVertical,
+ eIntID_ContextMenuOffsetHorizontal
+ };
+
+ /**
+ * 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 OperatingSystemVersion {
+ eOperatingSystemVersion_WindowsXP = 0,
+ eOperatingSystemVersion_WindowsVista,
+ eOperatingSystemVersion_Windows7,
+ eOperatingSystemVersion_Windows8,
+ eOperatingSystemVersion_Windows10,
+ eOperatingSystemVersion_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/xpwidgts/nsXPLookAndFeel.cpp.
+ enum FloatID {
+ eFloatID_IMEUnderlineRelativeSize,
+ eFloatID_SpellCheckerUnderlineRelativeSize,
+
+ // The width/height ratio of the cursor. If used, the CaretWidth int metric
+ // should be added to the calculated caret width.
+ eFloatID_CaretAspectRatio
+ };
+
+ // These constants must be kept in 1:1 correspondence with the
+ // NS_STYLE_FONT_* system font constants.
+ enum FontID {
+ eFont_Caption = 1, // css2
+ eFont_Icon,
+ eFont_Menu,
+ eFont_MessageBox,
+ eFont_SmallCaption,
+ eFont_StatusBar,
+
+ eFont_Window, // css3
+ eFont_Document,
+ eFont_Workspace,
+ eFont_Desktop,
+ eFont_Info,
+ eFont_Dialog,
+ eFont_Button,
+ eFont_PullDownMenu,
+ eFont_List,
+ eFont_Field,
+
+ eFont_Tooltips, // moz
+ eFont_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:
+ * eColorID_TextSelectForeground might return NS_DONT_CHANGE_COLOR.
+ * eColorID_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 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.
+ *
+ * @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.
+ * @param aDevPixPerCSSPixel Ratio of device pixels to CSS pixels
+ */
+ static bool GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle,
+ float aDevPixPerCSSPixel);
+
+ /**
+ * 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();
+
+ /**
+ * If the implementation is caching values, these accessors allow the
+ * cache to be exported and imported.
+ */
+ static nsTArray<LookAndFeelInt> GetIntCache();
+ static void SetIntCache(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache);
+};
+
+} // namespace mozilla
+
+// On the Mac, GetColor(eColorID_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 eColorID_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 eColorID_IME* and eColorID_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 eIntID_AlertNotificationOrigin
+// ------------------------------------------
+
+#define NS_ALERT_HORIZONTAL 1
+#define NS_ALERT_LEFT 2
+#define NS_ALERT_TOP 4
+
+#endif /* __LookAndFeel */
diff --git a/widget/MiscEvents.h b/widget/MiscEvents.h
new file mode 100644
index 000000000..2ef581d79
--- /dev/null
+++ b/widget/MiscEvents.h
@@ -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/. */
+
+#ifndef mozilla_MiscEvents_h__
+#define mozilla_MiscEvents_h__
+
+#include <stdint.h>
+
+#include "mozilla/BasicEvents.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.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; }
+
+ WidgetCommandEvent(bool aIsTrusted, nsIAtom* aEventType,
+ nsIAtom* aCommand, nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, eUnidentifiedEvent, aWidget,
+ eCommandEventClass)
+ , mCommand(aCommand)
+ {
+ mSpecifiedEventType = aEventType;
+ }
+
+ 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;
+ }
+
+ nsCOMPtr<nsIAtom> 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.
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetPluginEvent
+ *
+ * This event delivers only a native event to focused plugin.
+ ******************************************************************************/
+
+class WidgetPluginEvent : public WidgetGUIEvent
+{
+private:
+ friend class dom::PBrowserParent;
+ friend class dom::PBrowserChild;
+
+public:
+ virtual WidgetPluginEvent* AsPluginEvent() override { return this; }
+
+ WidgetPluginEvent(bool aIsTrusted, EventMessage aMessage, nsIWidget* aWidget)
+ : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, ePluginEventClass)
+ , mRetargetToFocusedDocument(false)
+ {
+ }
+
+ virtual WidgetEvent* Duplicate() const override
+ {
+ // NOTE: PluginEvent has to be dispatched to nsIFrame::HandleEvent().
+ // So, this event needs to support Duplicate().
+ MOZ_ASSERT(mClass == ePluginEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ WidgetPluginEvent* result = new WidgetPluginEvent(false, mMessage, nullptr);
+ result->AssignPluginEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ // If true, this event needs to be retargeted to focused document.
+ // Otherwise, never retargeted. Defaults to false.
+ bool mRetargetToFocusedDocument;
+
+ void AssignPluginEventData(const WidgetPluginEvent& aEvent,
+ bool aCopyTargets)
+ {
+ AssignGUIEventData(aEvent, aCopyTargets);
+
+ mRetargetToFocusedDocument = aEvent.mRetargetToFocusedDocument;
+ }
+
+protected:
+ WidgetPluginEvent()
+ {
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_MiscEvents_h__
diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h
new file mode 100644
index 000000000..643132618
--- /dev/null
+++ b/widget/MouseEvents.h
@@ -0,0 +1,724 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMWheelEvent.h"
+
+/******************************************************************************
+ * nsDragDropEventStatus
+ ******************************************************************************/
+
+enum nsDragDropEventStatus
+{
+ // The event is a enter
+ nsDragDropEventStatus_eDragEntered,
+ // The event is exit
+ nsDragDropEventStatus_eDragExited,
+ // The event is drop
+ nsDragDropEventStatus_eDrop
+};
+
+namespace mozilla {
+
+namespace dom {
+ class PBrowserParent;
+ class PBrowserChild;
+} // namespace dom
+
+/******************************************************************************
+ * mozilla::WidgetPointerHelper
+ ******************************************************************************/
+
+class WidgetPointerHelper
+{
+public:
+ bool convertToPointer;
+ uint32_t pointerId;
+ uint32_t tiltX;
+ uint32_t tiltY;
+ bool retargetedByPointerCapture;
+
+ WidgetPointerHelper() : convertToPointer(true), pointerId(0), tiltX(0), tiltY(0),
+ retargetedByPointerCapture(false) {}
+
+ void AssignPointerHelperData(const WidgetPointerHelper& aEvent)
+ {
+ convertToPointer = aEvent.convertToPointer;
+ pointerId = aEvent.pointerId;
+ tiltX = aEvent.tiltX;
+ tiltY = aEvent.tiltY;
+ retargetedByPointerCapture = aEvent.retargetedByPointerCapture;
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetMouseEventBase
+ ******************************************************************************/
+
+class WidgetMouseEventBase : public WidgetInputEvent
+{
+private:
+ friend class dom::PBrowserParent;
+ friend class dom::PBrowserChild;
+
+protected:
+ WidgetMouseEventBase()
+ : button(0)
+ , buttons(0)
+ , pressure(0)
+ , hitCluster(false)
+ , inputSource(nsIDOMMouseEvent::MOZ_SOURCE_MOUSE)
+ {
+ }
+
+ WidgetMouseEventBase(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget, EventClassID aEventClassID)
+ : WidgetInputEvent(aIsTrusted, aMessage, aWidget, aEventClassID)
+ , button(0)
+ , buttons(0)
+ , pressure(0)
+ , hitCluster(false)
+ , inputSource(nsIDOMMouseEvent::MOZ_SOURCE_MOUSE)
+ {
+ }
+
+public:
+ virtual WidgetMouseEventBase* AsMouseEventBase() override { return this; }
+
+ virtual WidgetEvent* Duplicate() const override
+ {
+ MOZ_CRASH("WidgetMouseEventBase must not be most-subclass");
+ }
+
+ /// The possible related target
+ nsCOMPtr<nsISupports> relatedTarget;
+
+ enum buttonType
+ {
+ eLeftButton = 0,
+ eMiddleButton = 1,
+ eRightButton = 2
+ };
+ // Pressed button ID of mousedown or mouseup event.
+ // This is set only when pressing a button causes the event.
+ int16_t button;
+
+ enum buttonsFlag {
+ eNoButtonFlag = 0x00,
+ eLeftButtonFlag = 0x01,
+ eRightButtonFlag = 0x02,
+ eMiddleButtonFlag = 0x04,
+ // typicall, "back" button being left side of 5-button
+ // mice, see "buttons" attribute document of DOM3 Events.
+ e4thButtonFlag = 0x08,
+ // typicall, "forward" button being right side of 5-button
+ // mice, see "buttons" attribute document of DOM3 Events.
+ e5thButtonFlag = 0x10
+ };
+
+ // Flags of all pressed buttons at the event fired.
+ // This is set at any mouse event, don't be confused with |button|.
+ int16_t buttons;
+
+ // Finger or touch pressure of event. It ranges between 0.0 and 1.0.
+ float pressure;
+ // Touch near a cluster of links (true)
+ bool hitCluster;
+
+ // Possible values at nsIDOMMouseEvent
+ uint16_t inputSource;
+
+ // ID of the canvas HitRegion
+ nsString region;
+
+ bool IsLeftButtonPressed() const { return !!(buttons & eLeftButtonFlag); }
+ bool IsRightButtonPressed() const { return !!(buttons & eRightButtonFlag); }
+ bool IsMiddleButtonPressed() const { return !!(buttons & eMiddleButtonFlag); }
+ bool Is4thButtonPressed() const { return !!(buttons & e4thButtonFlag); }
+ bool Is5thButtonPressed() const { return !!(buttons & e5thButtonFlag); }
+
+ void AssignMouseEventBaseData(const WidgetMouseEventBase& aEvent,
+ bool aCopyTargets)
+ {
+ AssignInputEventData(aEvent, aCopyTargets);
+
+ relatedTarget = aCopyTargets ? aEvent.relatedTarget : nullptr;
+ button = aEvent.button;
+ buttons = aEvent.buttons;
+ pressure = aEvent.pressure;
+ hitCluster = aEvent.hitCluster;
+ inputSource = aEvent.inputSource;
+ }
+
+ /**
+ * Returns true if left click event.
+ */
+ bool IsLeftClickEvent() const
+ {
+ return mMessage == eMouseClick && button == eLeftButton;
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetMouseEvent
+ ******************************************************************************/
+
+class WidgetMouseEvent : public WidgetMouseEventBase
+ , public WidgetPointerHelper
+{
+private:
+ friend class dom::PBrowserParent;
+ friend class dom::PBrowserChild;
+
+public:
+ typedef bool ReasonType;
+ enum Reason : ReasonType
+ {
+ eReal,
+ eSynthesized
+ };
+
+ typedef bool ContextMenuTriggerType;
+ enum ContextMenuTrigger : ContextMenuTriggerType
+ {
+ eNormal,
+ eContextMenuKey
+ };
+
+ typedef bool ExitFromType;
+ enum ExitFrom : ExitFromType
+ {
+ eChild,
+ eTopLevel
+ };
+
+protected:
+ WidgetMouseEvent()
+ : mReason(eReal)
+ , mContextMenuTrigger(eNormal)
+ , mExitFrom(eChild)
+ , mIgnoreRootScrollFrame(false)
+ , mClickCount(0)
+ {
+ }
+
+ WidgetMouseEvent(bool aIsTrusted,
+ EventMessage aMessage,
+ nsIWidget* aWidget,
+ EventClassID aEventClassID,
+ Reason aReason)
+ : WidgetMouseEventBase(aIsTrusted, aMessage, aWidget, aEventClassID)
+ , mReason(aReason)
+ , mContextMenuTrigger(eNormal)
+ , mExitFrom(eChild)
+ , mIgnoreRootScrollFrame(false)
+ , mClickCount(0)
+ {
+ }
+
+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)
+ , mExitFrom(eChild)
+ , mIgnoreRootScrollFrame(false)
+ , mClickCount(0)
+ {
+ if (aMessage == eContextMenu) {
+ button = (mContextMenuTrigger == eNormal) ? eRightButton : eLeftButton;
+ }
+ }
+
+#ifdef DEBUG
+ virtual ~WidgetMouseEvent()
+ {
+ NS_WARNING_ASSERTION(
+ mMessage != eContextMenu ||
+ button == ((mContextMenuTrigger == eNormal) ? eRightButton : eLeftButton),
+ "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;
+ }
+
+ // 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 is valid only when mMessage is eMouseExitFromWidget.
+ // This indicates if the mouse cursor exits from a top level widget or
+ // a child widget.
+ 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;
+
+ void AssignMouseEventData(const WidgetMouseEvent& aEvent, bool aCopyTargets)
+ {
+ AssignMouseEventBaseData(aEvent, aCopyTargets);
+ AssignPointerHelperData(aEvent);
+
+ mIgnoreRootScrollFrame = aEvent.mIgnoreRootScrollFrame;
+ mClickCount = aEvent.mClickCount;
+ }
+
+ /**
+ * 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;
+ }
+};
+
+/******************************************************************************
+ * 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;
+ }
+};
+
+/******************************************************************************
+ * 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
+ // nsIDOMUIEvent::SCROLL_PAGE_UP or nsIDOMUIEvent::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)
+ , mDeltaMode(nsIDOMWheelEvent::DOM_DELTA_PIXEL)
+ , mLineOrPageDeltaX(0)
+ , mLineOrPageDeltaY(0)
+ , mScrollType(SCROLL_DEFAULT)
+ , mCustomizedByUserPrefs(false)
+ , mIsMomentum(false)
+ , mIsNoLineOrPageDelta(false)
+ , mViewPortIsOverscrolled(false)
+ , mCanTriggerSwipe(false)
+ , mAllowToOverrideSystemScrollSpeed(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)
+ , mDeltaMode(nsIDOMWheelEvent::DOM_DELTA_PIXEL)
+ , mLineOrPageDeltaX(0)
+ , mLineOrPageDeltaY(0)
+ , mScrollType(SCROLL_DEFAULT)
+ , mCustomizedByUserPrefs(false)
+ , mMayHaveMomentum(false)
+ , mIsMomentum(false)
+ , mIsNoLineOrPageDelta(false)
+ , mViewPortIsOverscrolled(false)
+ , mCanTriggerSwipe(false)
+ , mAllowToOverrideSystemScrollSpeed(true)
+ {
+ }
+
+ 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
+ // nsEventStateManger. If the default action of the wheel event isn't scroll,
+ // these values always zero. Otherwise, remaning 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 nsIDOMWheelEvent::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;
+
+ 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;
+ }
+
+ // 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 000000000..cd326a0d8
--- /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 and Gonk
+#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 000000000..e060a313c
--- /dev/null
+++ b/widget/NativeKeyToDOMKeyName.h
@@ -0,0 +1,1144 @@
+/* -*- 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)
+// Android and B2G
+#define KEY_MAP_ANDROID(aCPPKeyName, aNativeKey)
+// Only for Android
+#define KEY_MAP_ANDROID_EXCEPT_B2G(aCPPKeyName, aNativeKey)
+// Only for B2G
+#define KEY_MAP_B2G(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)
+#ifndef MOZ_B2G
+#undef KEY_MAP_ANDROID_EXCEPT_B2G
+#define KEY_MAP_ANDROID_EXCEPT_B2G(aCPPKeyName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, KEY_NAME_INDEX_##aCPPKeyName)
+#else // #ifndef MOZ_B2G
+#undef KEY_MAP_B2G
+#define KEY_MAP_B2G(aCPPKeyName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, KEY_NAME_INDEX_##aCPPKeyName)
+#endif // #ifndef MOZ_B2G #else
+#endif
+
+/******************************************************************************
+ * Modifier Keys
+ ******************************************************************************/
+// Alt
+KEY_MAP_WIN (Alt, VK_MENU)
+KEY_MAP_WIN (Alt, VK_LMENU)
+KEY_MAP_WIN (Alt, VK_RMENU)
+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_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)
+
+// 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)
+
+// 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)
+
+// 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)
+KEY_MAP_ANDROID (Accept, AKEYCODE_DPAD_CENTER)
+
+// 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_ASSIST)
+
+// 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)
+
+// BrightnessUp
+KEY_MAP_GTK (BrightnessUp, GDK_MonBrightnessUp)
+
+// 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)
+
+// WakeUp
+KEY_MAP_GTK (WakeUp, GDK_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
+
+// 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)
+
+// 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)
+
+/******************************************************************************
+ * 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)
+
+// 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)
+KEY_MAP_ANDROID (BrowserBack, AKEYCODE_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
+ ******************************************************************************/
+// Call
+KEY_MAP_ANDROID (Call, AKEYCODE_CALL)
+
+// Camera
+KEY_MAP_ANDROID (Camera, AKEYCODE_CAMERA)
+
+// CameraFocus
+KEY_MAP_ANDROID_EXCEPT_B2G(CameraFocus, AKEYCODE_FOCUS)
+
+// GoHome
+KEY_MAP_ANDROID_EXCEPT_B2G(GoHome, AKEYCODE_HOME)
+KEY_MAP_B2G (HomeScreen, AKEYCODE_HOME)
+
+/******************************************************************************
+ * TV Keys
+ ******************************************************************************/
+// TV
+KEY_MAP_ANDROID (TV, AKEYCODE_TV)
+
+// TVInput
+KEY_MAP_ANDROID (TVInput, AKEYCODE_TV_INPUT)
+
+// TVPower
+KEY_MAP_ANDROID (TVPower, AKEYCODE_TV_POWER)
+
+/******************************************************************************
+ * 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)
+
+// Dimmer
+KEY_MAP_GTK (Dimmer, GDK_BrightnessAdjust)
+
+// Guide
+KEY_MAP_ANDROID (Guide, AKEYCODE_GUIDE)
+
+// Info
+KEY_MAP_ANDROID (Info, AKEYCODE_INFO)
+
+// MediaFastForward
+KEY_MAP_WIN_CMD (MediaFastForward, APPCOMMAND_MEDIA_FAST_FORWARD)
+KEY_MAP_GTK (MediaFastForward, GDK_AudioForward)
+KEY_MAP_ANDROID (MediaFastForward, AKEYCODE_MEDIA_FAST_FORWARD)
+
+// MediaLast
+
+// 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)
+KEY_MAP_ANDROID (Subtitle, AKEYCODE_CAPTIONS)
+
+// VideoModeNext
+KEY_MAP_GTK (VideoModeNext, GDK_Next_VMode)
+
+// ZoomToggle
+KEY_MAP_WIN (ZoomToggle, VK_ZOOM)
+
+/******************************************************************************
+ * 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
+#undef KEY_MAP_ANDROID_EXCEPT_B2G
+#undef KEY_MAP_B2G
diff --git a/widget/PCompositorWidget.ipdl b/widget/PCompositorWidget.ipdl
new file mode 100644
index 000000000..f8ffd8dff
--- /dev/null
+++ b/widget/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/PlatformWidgetTypes.ipdlh b/widget/PlatformWidgetTypes.ipdlh
new file mode 100644
index 000000000..26933f8de
--- /dev/null
+++ b/widget/PlatformWidgetTypes.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/. */
+
+// 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 {
+
+struct CompositorWidgetInitData
+{
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/PluginWidgetProxy.cpp b/widget/PluginWidgetProxy.cpp
new file mode 100644
index 000000000..679bf29e3
--- /dev/null
+++ b/widget/PluginWidgetProxy.cpp
@@ -0,0 +1,195 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/TabChild.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(TabChild* aTabChild,
+ mozilla::plugins::PluginWidgetChild* aActor)
+{
+ nsCOMPtr<nsIWidget> widget =
+ new mozilla::widget::PluginWidgetProxy(aTabChild, 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::TabChild* aTabChild,
+ mozilla::plugins::PluginWidgetChild* aActor) :
+ PuppetWidget(aTabChild),
+ 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;
+
+#if defined(XP_WIN)
+ PluginInstanceParent* instance =
+ PluginInstanceParent::LookupPluginInstanceByID(pluginInstanceId);
+ if (instance) {
+ Unused << NS_WARN_IF(NS_FAILED(instance->SetScrollCaptureId(scrollCaptureId)));
+ }
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PluginWidgetProxy::SetParent(nsIWidget* aNewParent)
+{
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+ nsIWidget* parent = GetParent();
+ if (parent) {
+ parent->RemoveChild(this);
+ }
+ if (aNewParent) {
+ aNewParent->AddChild(this);
+ }
+ mParent = aNewParent;
+ return NS_OK;
+}
+
+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::TabChild*>(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;
+}
+
+#if defined(XP_WIN)
+void
+PluginWidgetProxy::SetNativeData(uint32_t aDataType, uintptr_t aVal)
+{
+ if (!mActor) {
+ return;
+ }
+
+ auto tab = static_cast<mozilla::dom::TabChild*>(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.");
+ }
+}
+#endif
+
+NS_IMETHODIMP
+PluginWidgetProxy::SetFocus(bool aRaise)
+{
+ ENSURE_CHANNEL;
+ PWLOG("PluginWidgetProxy::SetFocus(%d)\n", aRaise);
+ mActor->SendSetFocus(aRaise);
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/PluginWidgetProxy.h b/widget/PluginWidgetProxy.h
new file mode 100644
index 000000000..3fb86a516
--- /dev/null
+++ b/widget/PluginWidgetProxy.h
@@ -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/. */
+
+#ifndef mozilla_widget_RemotePlugin_h__
+#define mozilla_widget_RemotePlugin_h__
+
+#include "PuppetWidget.h"
+#include "mozilla/dom/TabChild.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::TabChild* aTabChild,
+ mozilla::plugins::PluginWidgetChild* aChannel);
+
+protected:
+ virtual ~PluginWidgetProxy();
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIWidget
+ using PuppetWidget::Create; // for Create signature not overridden here
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+ virtual void Destroy() override;
+ NS_IMETHOD SetFocus(bool aRaise = false) override;
+ NS_IMETHOD SetParent(nsIWidget* aNewParent) override;
+
+ virtual nsIWidget* GetParent(void) override;
+ virtual void* GetNativeData(uint32_t aDataType) override;
+#if defined(XP_WIN)
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+#endif
+ 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/PuppetBidiKeyboard.cpp b/widget/PuppetBidiKeyboard.cpp
new file mode 100644
index 000000000..94d371631
--- /dev/null
+++ b/widget/PuppetBidiKeyboard.cpp
@@ -0,0 +1,48 @@
+/* -*- 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"
+
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS(PuppetBidiKeyboard, nsIBidiKeyboard)
+
+PuppetBidiKeyboard::PuppetBidiKeyboard() : nsIBidiKeyboard()
+{
+}
+
+PuppetBidiKeyboard::~PuppetBidiKeyboard()
+{
+}
+
+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;
+}
diff --git a/widget/PuppetBidiKeyboard.h b/widget/PuppetBidiKeyboard.h
new file mode 100644
index 000000000..403b3248a
--- /dev/null
+++ b/widget/PuppetBidiKeyboard.h
@@ -0,0 +1,36 @@
+/* -*- 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 000000000..1fa13ee6f
--- /dev/null
+++ b/widget/PuppetWidget.cpp
@@ -0,0 +1,1580 @@
+/* -*- 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 "mozilla/dom/TabChild.h"
+#include "mozilla/Hal.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/layers/APZChild.h"
+#include "mozilla/layers/PLayerTransactionChild.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextComposition.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/Unused.h"
+#include "PuppetWidget.h"
+#include "nsContentUtils.h"
+#include "nsIWidgetListener.h"
+#include "imgIContainer.h"
+#include "nsView.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(TabChild* aTabChild)
+{
+ MOZ_ASSERT(!aTabChild || nsIWidget::UsePuppetWidgets(),
+ "PuppetWidgets not allowed in this configuration");
+
+ nsCOMPtr<nsIWidget> widget = new PuppetWidget(aTabChild);
+ 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_INHERITED0(PuppetWidget, nsBaseWidget)
+
+PuppetWidget::PuppetWidget(TabChild* aTabChild)
+ : mTabChild(aTabChild)
+ , mMemoryPressureObserver(nullptr)
+ , mDPI(-1)
+ , mRounding(-1)
+ , mDefaultScale(-1)
+ , mCursorHotspotX(0)
+ , mCursorHotspotY(0)
+ , mNativeKeyCommandsValid(false)
+{
+ MOZ_COUNT_CTOR(PuppetWidget);
+
+ mSingleLineCommands.SetCapacity(4);
+ mMultiLineCommands.SetCapacity(4);
+ mRichTextCommands.SetCapacity(4);
+
+ // Setting 'Unknown' means "not yet cached".
+ mInputContext.mIMEState.mEnabled = IMEState::UNKNOWN;
+}
+
+PuppetWidget::~PuppetWidget()
+{
+ MOZ_COUNT_DTOR(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);
+ }
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ mMemoryPressureObserver = new MemoryPressureObserver(this);
+ obs->AddObserver(mMemoryPressureObserver, "memory-pressure", false);
+ }
+}
+
+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(mTabChild);
+ if (mNeedIMEStateInit) {
+ mContentCache.Clear();
+ mTabChild->SendUpdateContentCache(mContentCache);
+ mIMEPreferenceOfParent = nsIMEUpdatePreference();
+ mNeedIMEStateInit = false;
+ }
+}
+
+already_AddRefed<nsIWidget>
+PuppetWidget::CreateChild(const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData,
+ bool aForceUseIWidgetParent)
+{
+ bool isPopup = IsPopup(aInitData);
+ nsCOMPtr<nsIWidget> widget = nsIWidget::CreatePuppetWidget(mTabChild);
+ 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();
+ mPaintTask.Revoke();
+ if (mMemoryPressureObserver) {
+ mMemoryPressureObserver->Remove();
+ }
+ mMemoryPressureObserver = nullptr;
+ mChild = nullptr;
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ }
+ mLayerManager = nullptr;
+ mTabChild = nullptr;
+}
+
+NS_IMETHODIMP
+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);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetWidget::Resize(double aWidth,
+ double aHeight,
+ bool aRepaint)
+{
+ LayoutDeviceIntRect oldBounds = mBounds;
+ mBounds.SizeTo(LayoutDeviceIntSize(NSToIntRound(aWidth),
+ NSToIntRound(aHeight)));
+
+ if (mChild) {
+ return mChild->Resize(aWidth, aHeight, aRepaint);
+ }
+
+ // 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);
+ }
+
+ return NS_OK;
+}
+
+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;
+}
+
+NS_IMETHODIMP
+PuppetWidget::SetFocus(bool aRaise)
+{
+ if (aRaise && mTabChild) {
+ mTabChild->SendRequestFocus(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetWidget::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+#ifdef DEBUG
+ debug_DumpInvalidate(stderr, this, &aRect, "PuppetWidget", 0);
+#endif
+
+ if (mChild) {
+ return mChild->Invalidate(aRect);
+ }
+
+ mDirtyRegion.Or(mDirtyRegion, aRect);
+
+ if (!mDirtyRegion.IsEmpty() && !mPaintTask.IsPending()) {
+ mPaintTask = new PaintTask(this);
+ return NS_DispatchToCurrentThread(mPaintTask.get());
+ }
+
+ return NS_OK;
+}
+
+void
+PuppetWidget::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint)
+{
+ if (nullptr == aPoint) {
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ } else {
+ // use the point override if provided
+ event.mRefPoint = *aPoint;
+ }
+ event.mTime = PR_Now() / 1000;
+}
+
+NS_IMETHODIMP
+PuppetWidget::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus)
+{
+#ifdef DEBUG
+ debug_DumpEvent(stdout, event->mWidget, event, "PuppetWidget", 0);
+#endif
+
+ MOZ_ASSERT(!mChild || mChild->mWindowType == eWindowType_popup,
+ "Unexpected event dispatch!");
+
+ AutoCacheNativeKeyCommands autoCache(this);
+ if (event->mFlags.mIsSynthesizedForTests && !mNativeKeyCommandsValid) {
+ WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
+ if (keyEvent) {
+ mTabChild->RequestNativeKeyBindings(&autoCache, keyEvent);
+ }
+ }
+
+ if (event->mClass == eCompositionEventClass) {
+ // Store the latest native IME context of parent process's widget or
+ // TextEventDispatcher if it's in this process.
+ WidgetCompositionEvent* compositionEvent = event->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;
+ }
+
+ aStatus = nsEventStatus_eIgnore;
+
+ if (GetCurrentWidgetListener()) {
+ aStatus = GetCurrentWidgetListener()->HandleEvent(event, mUseAttachedEvents);
+ }
+
+ return NS_OK;
+}
+
+nsEventStatus
+PuppetWidget::DispatchInputEvent(WidgetInputEvent* aEvent)
+{
+ if (!AsyncPanZoomEnabled()) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ DispatchEvent(aEvent, status);
+ return status;
+ }
+
+ if (!mTabChild) {
+ return nsEventStatus_eIgnore;
+ }
+
+ switch (aEvent->mClass) {
+ case eWheelEventClass:
+ Unused <<
+ mTabChild->SendDispatchWheelEvent(*aEvent->AsWheelEvent());
+ break;
+ case eMouseEventClass:
+ Unused <<
+ mTabChild->SendDispatchMouseEvent(*aEvent->AsMouseEvent());
+ break;
+ case eKeyboardEventClass:
+ Unused <<
+ mTabChild->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 (!mTabChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mTabChild->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 (!mTabChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mTabChild->SendSynthesizeNativeMouseEvent(aPoint, aNativeMessage,
+ aModifierFlags, notifier.SaveObserver());
+ return NS_OK;
+}
+
+nsresult
+PuppetWidget::SynthesizeNativeMouseMove(mozilla::LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "mousemove");
+ if (!mTabChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mTabChild->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 (!mTabChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mTabChild->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 (!mTabChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mTabChild->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 (!mTabChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mTabChild->SendSynthesizeNativeTouchTap(aPoint, aLongTap,
+ notifier.SaveObserver());
+ return NS_OK;
+}
+
+nsresult
+PuppetWidget::ClearNativeTouchSequence(nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "cleartouch");
+ if (!mTabChild) {
+ return NS_ERROR_FAILURE;
+ }
+ mTabChild->SendClearNativeTouchSequence(notifier.SaveObserver());
+ return NS_OK;
+}
+
+void
+PuppetWidget::SetConfirmedTargetAPZC(uint64_t aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets) const
+{
+ if (mTabChild) {
+ mTabChild->SetTargetAPZC(aInputBlockId, aTargets);
+ }
+}
+
+void
+PuppetWidget::UpdateZoomConstraints(const uint32_t& aPresShellId,
+ const FrameMetrics::ViewID& aViewId,
+ const Maybe<ZoomConstraints>& aConstraints)
+{
+ if (mTabChild) {
+ mTabChild->DoUpdateZoomConstraints(aPresShellId, aViewId, aConstraints);
+ }
+}
+
+bool
+PuppetWidget::AsyncPanZoomEnabled() const
+{
+ return mTabChild && mTabChild->AsyncPanZoomEnabled();
+}
+
+NS_IMETHODIMP_(bool)
+PuppetWidget::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ // B2G doesn't have native key bindings.
+#ifdef MOZ_WIDGET_GONK
+ return false;
+#else // #ifdef MOZ_WIDGET_GONK
+ AutoCacheNativeKeyCommands autoCache(this);
+ if (!aEvent.mWidget && !mNativeKeyCommandsValid) {
+ MOZ_ASSERT(!aEvent.mFlags.mIsSynthesizedForTests);
+ // Abort if untrusted to avoid leaking system settings
+ if (NS_WARN_IF(!aEvent.IsTrusted())) {
+ return false;
+ }
+ mTabChild->RequestNativeKeyBindings(&autoCache, &aEvent);
+ }
+
+ MOZ_ASSERT(mNativeKeyCommandsValid);
+
+ const nsTArray<mozilla::CommandInt>* commands = nullptr;
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ commands = &mSingleLineCommands;
+ break;
+ case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ commands = &mMultiLineCommands;
+ break;
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ commands = &mRichTextCommands;
+ break;
+ default:
+ MOZ_CRASH("Invalid type");
+ break;
+ }
+
+ if (commands->IsEmpty()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < commands->Length(); i++) {
+ aCallback(static_cast<mozilla::Command>((*commands)[i]), aCallbackData);
+ }
+ return true;
+#endif
+}
+
+LayerManager*
+PuppetWidget::GetLayerManager(PLayerTransactionChild* aShadowManager,
+ LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence)
+{
+ if (!mLayerManager) {
+ mLayerManager = new ClientLayerManager(this);
+ }
+ ShadowLayerForwarder* lf = mLayerManager->AsShadowForwarder();
+ if (lf && !lf->HasShadowManager() && aShadowManager) {
+ lf->SetShadowManager(aShadowManager);
+ }
+ return mLayerManager;
+}
+
+LayerManager*
+PuppetWidget::RecreateLayerManager(PLayerTransactionChild* aShadowManager)
+{
+ mLayerManager = new ClientLayerManager(this);
+ if (ShadowLayerForwarder* lf = mLayerManager->AsShadowForwarder()) {
+ lf->SetShadowManager(aShadowManager);
+ }
+ return mLayerManager;
+}
+
+nsresult
+PuppetWidget::RequestIMEToCommitComposition(bool aCancel)
+{
+#ifdef MOZ_CROSS_PROCESS_IME
+ if (!mTabChild) {
+ 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;
+ }
+
+ 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;
+ }
+
+ bool isCommitted = false;
+ nsAutoString committedString;
+ if (NS_WARN_IF(!mTabChild->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);
+
+ // NOTE: PuppetWidget might be destroyed already.
+
+#endif // #ifdef MOZ_CROSS_PROCESS_IME
+
+ return NS_OK;
+}
+
+nsresult
+PuppetWidget::NotifyIMEInternal(const IMENotification& aIMENotification)
+{
+ 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;
+ }
+}
+
+NS_IMETHODIMP
+PuppetWidget::StartPluginIME(const mozilla::WidgetKeyboardEvent& aKeyboardEvent,
+ int32_t aPanelX, int32_t aPanelY,
+ nsString& aCommitted)
+{
+ if (!mTabChild ||
+ !mTabChild->SendStartPluginIME(aKeyboardEvent, aPanelX,
+ aPanelY, &aCommitted)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void
+PuppetWidget::SetPluginFocused(bool& aFocused)
+{
+ if (mTabChild) {
+ mTabChild->SendSetPluginFocused(aFocused);
+ }
+}
+
+void
+PuppetWidget::DefaultProcOfPluginEvent(const WidgetPluginEvent& aEvent)
+{
+ if (!mTabChild) {
+ return;
+ }
+ mTabChild->SendDefaultProcOfPluginEvent(aEvent);
+}
+
+NS_IMETHODIMP_(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;
+
+#ifndef MOZ_CROSS_PROCESS_IME
+ return;
+#endif
+
+ if (!mTabChild) {
+ return;
+ }
+ mTabChild->SendSetInputContext(
+ static_cast<int32_t>(aContext.mIMEState.mEnabled),
+ static_cast<int32_t>(aContext.mIMEState.mOpen),
+ aContext.mHTMLInputType,
+ aContext.mHTMLInputInputmode,
+ aContext.mActionHint,
+ static_cast<int32_t>(aAction.mCause),
+ static_cast<int32_t>(aAction.mFocusChange));
+}
+
+NS_IMETHODIMP_(InputContext)
+PuppetWidget::GetInputContext()
+{
+#ifndef MOZ_CROSS_PROCESS_IME
+ return InputContext();
+#endif
+
+ // XXX Currently, we don't support retrieving IME open state from child
+ // process.
+
+ // When this widget caches input context and currently managed by
+ // IMEStateManager, the cache is valid. Only in this case, we can
+ // avoid to use synchronous IPC.
+ if (mInputContext.mIMEState.mEnabled != IMEState::UNKNOWN &&
+ IMEStateManager::GetWidgetForActiveInputContext() == this) {
+ 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 (mTabChild) {
+ int32_t enabled, open;
+ mTabChild->SendGetInputContext(&enabled, &open);
+ context.mIMEState.mEnabled = static_cast<IMEState::Enabled>(enabled);
+ context.mIMEState.mOpen = static_cast<IMEState::Open>(open);
+ }
+ return context;
+}
+
+NS_IMETHODIMP_(NativeIMEContext)
+PuppetWidget::GetNativeIMEContext()
+{
+ return mNativeIMEContext;
+}
+
+nsresult
+PuppetWidget::NotifyIMEOfFocusChange(const IMENotification& aIMENotification)
+{
+#ifndef MOZ_CROSS_PROCESS_IME
+ return NS_OK;
+#endif
+
+ if (!mTabChild)
+ return NS_ERROR_FAILURE;
+
+ bool gotFocus = aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS;
+ if (gotFocus) {
+ if (mInputContext.mIMEState.mEnabled != IMEState::PLUGIN) {
+ // When IME gets focus, we should initalize all information of the
+ // content.
+ if (NS_WARN_IF(!mContentCache.CacheAll(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ // However, if a plugin has focus, only the editor rect information is
+ // available.
+ if (NS_WARN_IF(!mContentCache.CacheEditorRect(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ } else {
+ // When IME loses focus, we don't need to store anything.
+ mContentCache.Clear();
+ }
+
+ mIMEPreferenceOfParent = nsIMEUpdatePreference();
+ if (!mTabChild->SendNotifyIMEFocus(mContentCache, aIMENotification,
+ &mIMEPreferenceOfParent)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+PuppetWidget::NotifyIMEOfCompositionUpdate(
+ const IMENotification& aIMENotification)
+{
+#ifndef MOZ_CROSS_PROCESS_IME
+ return NS_OK;
+#endif
+
+ NS_ENSURE_TRUE(mTabChild, NS_ERROR_FAILURE);
+
+ if (mInputContext.mIMEState.mEnabled != IMEState::PLUGIN &&
+ NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+ mTabChild->SendNotifyIMECompositionUpdate(mContentCache, aIMENotification);
+ return NS_OK;
+}
+
+nsIMEUpdatePreference
+PuppetWidget::GetIMEUpdatePreference()
+{
+#ifdef MOZ_CROSS_PROCESS_IME
+ // e10s requires IME content cache in in the TabParent for handling query
+ // content event only with the parent process. Therefore, this process
+ // needs to receive a lot of information from the focused editor to sent
+ // the latest content to the parent process.
+ if (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
+ // But if a plugin has focus, we cannot receive text nor selection change
+ // in the plugin. Therefore, PuppetWidget needs to receive only position
+ // change event for updating the editor rect cache.
+ return nsIMEUpdatePreference(mIMEPreferenceOfParent.mWantUpdates |
+ nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE);
+ }
+ return nsIMEUpdatePreference(mIMEPreferenceOfParent.mWantUpdates |
+ nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE |
+ nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE );
+#else
+ // B2G doesn't handle IME as widget-level.
+ return nsIMEUpdatePreference();
+#endif
+}
+
+nsresult
+PuppetWidget::NotifyIMEOfTextChange(const IMENotification& aIMENotification)
+{
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
+ "Passed wrong notification");
+
+#ifndef MOZ_CROSS_PROCESS_IME
+ return NS_OK;
+#endif
+
+ if (!mTabChild)
+ return NS_ERROR_FAILURE;
+
+ // While a plugin has focus, text change notification shouldn't be available.
+ if (NS_WARN_IF(mInputContext.mIMEState.mEnabled == IMEState::PLUGIN)) {
+ 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;
+ }
+
+ // TabParent doesn't this this to cache. we don't send the notification
+ // if parent process doesn't request NOTIFY_TEXT_CHANGE.
+ if (mIMEPreferenceOfParent.WantTextChange()) {
+ mTabChild->SendNotifyIMETextChange(mContentCache, aIMENotification);
+ } else {
+ mTabChild->SendUpdateContentCache(mContentCache);
+ }
+ return NS_OK;
+}
+
+nsresult
+PuppetWidget::NotifyIMEOfSelectionChange(
+ const IMENotification& aIMENotification)
+{
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE,
+ "Passed wrong notification");
+
+#ifndef MOZ_CROSS_PROCESS_IME
+ return NS_OK;
+#endif
+
+ if (!mTabChild)
+ return NS_ERROR_FAILURE;
+
+ // While a plugin has focus, selection change notification shouldn't be
+ // available.
+ if (NS_WARN_IF(mInputContext.mIMEState.mEnabled == IMEState::PLUGIN)) {
+ 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());
+
+ mTabChild->SendNotifyIMESelection(mContentCache, aIMENotification);
+
+ return NS_OK;
+}
+
+nsresult
+PuppetWidget::NotifyIMEOfMouseButtonEvent(
+ const IMENotification& aIMENotification)
+{
+ if (!mTabChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // While a plugin has focus, mouse button event notification shouldn't be
+ // available.
+ if (NS_WARN_IF(mInputContext.mIMEState.mEnabled == IMEState::PLUGIN)) {
+ return NS_ERROR_FAILURE;
+ }
+
+
+ bool consumedByIME = false;
+ if (!mTabChild->SendNotifyIMEMouseButtonEvent(aIMENotification,
+ &consumedByIME)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return consumedByIME ? NS_SUCCESS_EVENT_CONSUMED : NS_OK;
+}
+
+nsresult
+PuppetWidget::NotifyIMEOfPositionChange(const IMENotification& aIMENotification)
+{
+#ifndef MOZ_CROSS_PROCESS_IME
+ return NS_OK;
+#endif
+ if (NS_WARN_IF(!mTabChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!mContentCache.CacheEditorRect(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+ // While a plugin has focus, selection range isn't available. So, we don't
+ // need to cache it at that time.
+ if (mInputContext.mIMEState.mEnabled != IMEState::PLUGIN &&
+ NS_WARN_IF(!mContentCache.CacheSelection(this, &aIMENotification))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (mIMEPreferenceOfParent.WantPositionChanged()) {
+ mTabChild->SendNotifyIMEPositionChange(mContentCache, aIMENotification);
+ } else {
+ mTabChild->SendUpdateContentCache(mContentCache);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetWidget::SetCursor(nsCursor aCursor)
+{
+ // Don't cache on windows, Windowless flash breaks this via async cursor updates.
+#if !defined(XP_WIN)
+ if (mCursor == aCursor && !mCustomCursor && !mUpdateCursor) {
+ return NS_OK;
+ }
+#endif
+
+ mCustomCursor = nullptr;
+
+ if (mTabChild &&
+ !mTabChild->SendSetCursor(aCursor, mUpdateCursor)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCursor = aCursor;
+ mUpdateCursor = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetWidget::SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY)
+{
+ if (!aCursor || !mTabChild) {
+ return NS_OK;
+ }
+
+#if !defined(XP_WIN)
+ if (mCustomCursor == aCursor &&
+ mCursorHotspotX == aHotspotX &&
+ mCursorHotspotY == aHotspotY &&
+ !mUpdateCursor) {
+ return NS_OK;
+ }
+#endif
+
+ RefPtr<mozilla::gfx::SourceSurface> surface =
+ aCursor->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
+ surface->GetDataSurface();
+ if (!dataSurface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ size_t length;
+ int32_t stride;
+ mozilla::UniquePtr<char[]> surfaceData =
+ nsContentUtils::GetSurfaceData(WrapNotNull(dataSurface), &length, &stride);
+
+ nsDependentCString cursorData(surfaceData.get(), length);
+ mozilla::gfx::IntSize size = dataSurface->GetSize();
+ if (!mTabChild->SendSetCustomCursor(cursorData, size.width, size.height, stride,
+ static_cast<uint8_t>(dataSurface->GetFormat()),
+ aHotspotX, aHotspotY, mUpdateCursor)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCursor = nsCursor(-1);
+ mCustomCursor = aCursor;
+ mCursorHotspotX = aHotspotX;
+ mCursorHotspotY = aHotspotY;
+ mUpdateCursor = false;
+
+ return NS_OK;
+}
+
+void
+PuppetWidget::ClearCachedCursor()
+{
+ nsBaseWidget::ClearCachedCursor();
+ mCustomCursor = nullptr;
+}
+
+nsresult
+PuppetWidget::Paint()
+{
+ MOZ_ASSERT(!mDirtyRegion.IsEmpty(), "paint event logic messed up");
+
+ if (!GetCurrentWidgetListener())
+ return NS_OK;
+
+ LayoutDeviceIntRegion region = mDirtyRegion;
+
+ // reset repaint tracking
+ mDirtyRegion.SetEmpty();
+ mPaintTask.Revoke();
+
+ RefPtr<PuppetWidget> strongThis(this);
+
+ GetCurrentWidgetListener()->WillPaintWindow(this);
+
+ if (GetCurrentWidgetListener()) {
+#ifdef DEBUG
+ debug_DumpPaintEvent(stderr, this, region.ToUnknownRegion(),
+ "PuppetWidget", 0);
+#endif
+
+ if (mozilla::layers::LayersBackend::LAYERS_CLIENT == mLayerManager->GetBackendType()) {
+ // Do nothing, the compositor will handle drawing
+ if (mTabChild) {
+ mTabChild->NotifyPainted();
+ }
+ } else if (mozilla::layers::LayersBackend::LAYERS_BASIC == mLayerManager->GetBackendType()) {
+ RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(mDrawTarget);
+ if (!ctx) {
+ gfxDevCrash(LogReason::InvalidContext) << "PuppetWidget context problem " << gfx::hexa(mDrawTarget);
+ return NS_ERROR_FAILURE;
+ }
+ ctx->Rectangle(gfxRect(0,0,0,0));
+ ctx->Clip();
+ AutoLayerManagerSetup setupLayerManager(this, ctx,
+ BufferMode::BUFFER_NONE);
+ GetCurrentWidgetListener()->PaintWindow(this, region);
+ if (mTabChild) {
+ mTabChild->NotifyPainted();
+ }
+ }
+ }
+
+ if (GetCurrentWidgetListener()) {
+ GetCurrentWidgetListener()->DidPaintWindow();
+ }
+
+ return NS_OK;
+}
+
+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;
+}
+
+NS_IMETHODIMP
+PuppetWidget::PaintTask::Run()
+{
+ if (mWidget) {
+ mWidget->Paint();
+ }
+ return NS_OK;
+}
+
+void
+PuppetWidget::PaintNowIfNeeded()
+{
+ if (IsVisible() && mPaintTask.IsPending()) {
+ Paint();
+ }
+}
+
+NS_IMPL_ISUPPORTS(PuppetWidget::MemoryPressureObserver, nsIObserver)
+
+NS_IMETHODIMP
+PuppetWidget::MemoryPressureObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (!mWidget) {
+ return NS_OK;
+ }
+
+ if (strcmp("memory-pressure", aTopic) == 0 &&
+ !NS_LITERAL_STRING("lowering-priority").Equals(aData)) {
+ if (!mWidget->mVisible && mWidget->mLayerManager &&
+ XRE_IsContentProcess()) {
+ mWidget->mLayerManager->ClearCachedResources();
+ }
+ }
+ return NS_OK;
+}
+
+void
+PuppetWidget::MemoryPressureObserver::Remove()
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "memory-pressure");
+ }
+ mWidget = nullptr;
+}
+
+bool
+PuppetWidget::NeedsPaint()
+{
+ // e10s popups are handled by the parent process, so never should be painted here
+ if (XRE_IsContentProcess() &&
+ Preferences::GetBool("browser.tabs.remote.desktopbehavior", false) &&
+ mWindowType == eWindowType_popup) {
+ NS_WARNING("Trying to paint an e10s popup in the child process!");
+ return false;
+ }
+
+ return mVisible;
+}
+
+float
+PuppetWidget::GetDPI()
+{
+ if (mDPI < 0) {
+ if (mTabChild) {
+ mTabChild->GetDPI(&mDPI);
+ } else {
+ mDPI = 96.0;
+ }
+ }
+
+ return mDPI;
+}
+
+double
+PuppetWidget::GetDefaultScaleInternal()
+{
+ if (mDefaultScale < 0) {
+ if (mTabChild) {
+ mTabChild->GetDefaultScale(&mDefaultScale);
+ } else {
+ mDefaultScale = 1;
+ }
+ }
+
+ return mDefaultScale;
+}
+
+int32_t
+PuppetWidget::RoundsWidgetCoordinatesTo()
+{
+ if (mRounding < 0) {
+ if (mTabChild) {
+ mTabChild->GetWidgetRounding(&mRounding);
+ } else {
+ mRounding = 1;
+ }
+ }
+
+ return mRounding;
+}
+
+void*
+PuppetWidget::GetNativeData(uint32_t aDataType)
+{
+ switch (aDataType) {
+ case NS_NATIVE_SHAREABLE_WINDOW: {
+ MOZ_ASSERT(mTabChild, "Need TabChild to get the nativeWindow from!");
+ mozilla::WindowsHandle nativeData = 0;
+ if (mTabChild) {
+ mTabChild->SendGetWidgetNativeData(&nativeData);
+ }
+ 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(mTabChild, "Need TabChild to send the message.");
+ if (mTabChild) {
+ mTabChild->SendSetNativeChildOfShareableWindow(aVal);
+ }
+ break;
+ default:
+ NS_WARNING("SetNativeData called with unsupported data type.");
+ }
+}
+#endif
+
+nsIntPoint
+PuppetWidget::GetChromeDimensions()
+{
+ if (!GetOwningTabChild()) {
+ NS_WARNING("PuppetWidget without Tab does not have chrome information.");
+ return nsIntPoint();
+ }
+ return GetOwningTabChild()->GetChromeDisplacement().ToUnknownPoint();
+}
+
+nsIntPoint
+PuppetWidget::GetWindowPosition()
+{
+ if (!GetOwningTabChild()) {
+ return nsIntPoint();
+ }
+
+ int32_t winX, winY, winW, winH;
+ NS_ENSURE_SUCCESS(GetOwningTabChild()->GetDimensions(0, &winX, &winY, &winW, &winH), nsIntPoint());
+ return nsIntPoint(winX, winY) + GetOwningTabChild()->GetClientOffset().ToUnknownPoint();
+}
+
+LayoutDeviceIntRect
+PuppetWidget::GetScreenBounds()
+{
+ return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
+}
+
+uint32_t PuppetWidget::GetMaxTouchPoints() const
+{
+ static uint32_t sTouchPoints = 0;
+ static bool sIsInitialized = false;
+ if (sIsInitialized) {
+ return sTouchPoints;
+ }
+ if (mTabChild) {
+ mTabChild->GetMaxTouchPoints(&sTouchPoints);
+ sIsInitialized = true;
+ }
+ return sTouchPoints;
+}
+
+void
+PuppetWidget::StartAsyncScrollbarDrag(const AsyncDragMetrics& aDragMetrics)
+{
+ mTabChild->StartScrollbarDrag(aDragMetrics);
+}
+
+PuppetScreen::PuppetScreen(void *nativeScreen)
+{
+}
+
+PuppetScreen::~PuppetScreen()
+{
+}
+
+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::GetId(uint32_t *outId)
+{
+ *outId = 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetScreen::GetRect(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight)
+{
+ nsIntRect r = ScreenConfig().rect();
+ *outLeft = r.x;
+ *outTop = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+ 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_IMETHODIMP
+PuppetScreen::GetRotation(uint32_t* aRotation)
+{
+ NS_WARNING("Attempt to get screen rotation through nsIScreen::GetRotation(). Nothing should know or care this in sandboxed contexts. If you want *orientation*, use hal.");
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+PuppetScreen::SetRotation(uint32_t aRotation)
+{
+ NS_WARNING("Attempt to set screen rotation through nsIScreen::GetRotation(). Nothing should know or care this in sandboxed contexts. If you want *orientation*, use hal.");
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMPL_ISUPPORTS(PuppetScreenManager, nsIScreenManager)
+
+PuppetScreenManager::PuppetScreenManager()
+{
+ mOneScreen = new PuppetScreen(nullptr);
+}
+
+PuppetScreenManager::~PuppetScreenManager()
+{
+}
+
+NS_IMETHODIMP
+PuppetScreenManager::ScreenForId(uint32_t aId,
+ nsIScreen** outScreen)
+{
+ NS_IF_ADDREF(*outScreen = mOneScreen.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetScreenManager::GetPrimaryScreen(nsIScreen** outScreen)
+{
+ NS_IF_ADDREF(*outScreen = mOneScreen.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetScreenManager::ScreenForRect(int32_t inLeft,
+ int32_t inTop,
+ int32_t inWidth,
+ int32_t inHeight,
+ nsIScreen** outScreen)
+{
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+PuppetScreenManager::ScreenForNativeWidget(void* aWidget,
+ nsIScreen** outScreen)
+{
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+PuppetScreenManager::GetNumberOfScreens(uint32_t* aNumberOfScreens)
+{
+ *aNumberOfScreens = 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PuppetScreenManager::GetSystemDefaultScale(float *aDefaultScale)
+{
+ *aDefaultScale = 1.0f;
+ return NS_OK;
+}
+
+nsIWidgetListener*
+PuppetWidget::GetCurrentWidgetListener()
+{
+ if (!mPreviouslyAttachedWidgetListener ||
+ !mAttachedWidgetListener) {
+ return mAttachedWidgetListener;
+ }
+
+ if (mAttachedWidgetListener->GetView()->IsPrimaryFramePaintSuppressed()) {
+ return mPreviouslyAttachedWidgetListener;
+ }
+
+ return mAttachedWidgetListener;
+}
+
+void
+PuppetWidget::SetCandidateWindowForPlugin(
+ const CandidateWindowPosition& aPosition)
+{
+ if (!mTabChild) {
+ return;
+ }
+
+ mTabChild->SendSetCandidateWindowForPlugin(aPosition);
+}
+
+void
+PuppetWidget::ZoomToRect(const uint32_t& aPresShellId,
+ const FrameMetrics::ViewID& aViewId,
+ const CSSRect& aRect,
+ const uint32_t& aFlags)
+{
+ if (!mTabChild) {
+ return;
+ }
+
+ mTabChild->ZoomToRect(aPresShellId, aViewId, aRect, aFlags);
+}
+
+void
+PuppetWidget::LookUpDictionary(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical,
+ const LayoutDeviceIntPoint& aPoint)
+{
+ if (!mTabChild) {
+ return;
+ }
+
+ mTabChild->SendLookUpDictionary(nsString(aText), aFontRangeArray, aIsVertical, aPoint);
+}
+
+bool
+PuppetWidget::HasPendingInputEvent()
+{
+ if (!mTabChild) {
+ return false;
+ }
+
+ bool ret = false;
+
+ mTabChild->GetIPCChannel()->PeekMessages(
+ [&ret](const IPC::Message& aMsg) -> bool {
+ if ((aMsg.type() & mozilla::dom::PBrowser::PBrowserStart)
+ == mozilla::dom::PBrowser::PBrowserStart) {
+ switch (aMsg.type()) {
+ case mozilla::dom::PBrowser::Msg_RealMouseMoveEvent__ID:
+ case mozilla::dom::PBrowser::Msg_SynthMouseMoveEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealMouseButtonEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealKeyEvent__ID:
+ case mozilla::dom::PBrowser::Msg_MouseWheelEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealTouchEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealTouchMoveEvent__ID:
+ case mozilla::dom::PBrowser::Msg_RealDragEvent__ID:
+ case mozilla::dom::PBrowser::Msg_UpdateDimensions__ID:
+ case mozilla::dom::PBrowser::Msg_MouseEvent__ID:
+ case mozilla::dom::PBrowser::Msg_KeyEvent__ID:
+ 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(!mTabChild)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_WARN_IF(!mTabChild->SendOnWindowedPluginKeyEvent(aKeyEventData))) {
+ return NS_ERROR_FAILURE;
+ }
+ mKeyEventInPluginCallbacks.AppendElement(aCallback);
+ return NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/PuppetWidget.h b/widget/PuppetWidget.h
new file mode 100644
index 000000000..5d0581356
--- /dev/null
+++ b/widget/PuppetWidget.h
@@ -0,0 +1,463 @@
+/* -*- 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"
+
+namespace mozilla {
+
+namespace dom {
+class TabChild;
+} // namespace dom
+
+namespace widget {
+
+struct AutoCacheNativeKeyCommands;
+
+class PuppetWidget : public nsBaseWidget
+{
+ typedef mozilla::dom::TabChild TabChild;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef nsBaseWidget Base;
+ typedef mozilla::CSSRect CSSRect;
+
+ // The width and height of the "widget" are clamped to this.
+ static const size_t kMaxDimension;
+
+public:
+ explicit PuppetWidget(TabChild* aTabChild);
+
+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;
+
+ NS_IMETHOD 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 TabChild.
+ NS_IMETHOD Move(double aX, double aY) override
+ { return NS_OK; }
+
+ NS_IMETHOD Resize(double aWidth,
+ double aHeight,
+ bool aRepaint) override;
+ NS_IMETHOD Resize(double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint) override
+ {
+ if (mBounds.x != aX || mBounds.y != aY) {
+ NotifyWindowMoved(aX, aY);
+ }
+ mBounds.x = aX;
+ mBounds.y = aY;
+ return Resize(aWidth, aHeight, aRepaint);
+ }
+
+ // XXX/cjones: copying gtk behavior here; unclear what disabling a
+ // widget is supposed to entail
+ NS_IMETHOD Enable(bool aState) override
+ { mEnabled = aState; return NS_OK; }
+ virtual bool IsEnabled() const override
+ { return mEnabled; }
+
+ NS_IMETHOD SetFocus(bool aRaise = false) override;
+
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+
+ NS_IMETHOD 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.
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override
+ { return NS_ERROR_UNEXPECTED; }
+
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override
+ { return LayoutDeviceIntPoint::FromUnknownPoint(GetWindowPosition() + GetChromeDimensions()); }
+
+ int32_t RoundsWidgetCoordinatesTo() override;
+
+ void InitEvent(WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint = nullptr);
+
+ NS_IMETHOD 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 FrameMetrics::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints) override;
+ bool AsyncPanZoomEnabled() const override;
+
+ NS_IMETHOD_(bool)
+ ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) 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 after a compositor reset.
+ LayerManager* RecreateLayerManager(PLayerTransactionChild* aShadowManager);
+
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override;
+ NS_IMETHOD_(NativeIMEContext) GetNativeIMEContext() override;
+ virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
+
+ NS_IMETHOD SetCursor(nsCursor aCursor) override;
+ NS_IMETHOD SetCursor(imgIContainer* aCursor,
+ 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 TabChild* GetOwningTabChild() override { return mTabChild; }
+
+ void UpdateBackingScaleCache(float aDpi, int32_t aRounding, double aScale)
+ {
+ mDPI = aDpi;
+ mRounding = aRounding;
+ mDefaultScale = aScale;
+ }
+
+ nsIntSize GetScreenDimensions();
+
+ // Get the size of the chrome of the window that this tab belongs to.
+ nsIntPoint GetChromeDimensions();
+
+ // Get the screen position of the application window.
+ nsIntPoint GetWindowPosition();
+
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+
+ NS_IMETHOD StartPluginIME(const mozilla::WidgetKeyboardEvent& aKeyboardEvent,
+ int32_t aPanelX, int32_t aPanelY,
+ nsString& aCommitted) override;
+
+ virtual void SetPluginFocused(bool& aFocused) override;
+ virtual void DefaultProcOfPluginEvent(
+ const mozilla::WidgetPluginEvent& aEvent) 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 SetCandidateWindowForPlugin(
+ const CandidateWindowPosition& aPosition) override;
+
+ virtual void ZoomToRect(const uint32_t& aPresShellId,
+ const FrameMetrics::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;
+
+protected:
+ virtual nsresult NotifyIMEInternal(
+ const IMENotification& aIMENotification) override;
+
+private:
+ nsresult Paint();
+
+ 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();
+
+ class PaintTask : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit PaintTask(PuppetWidget* widget) : mWidget(widget) {}
+ void Revoke() { mWidget = nullptr; }
+ private:
+ PuppetWidget* mWidget;
+ };
+
+ class MemoryPressureObserver : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ explicit MemoryPressureObserver(PuppetWidget* aWidget) : mWidget(aWidget) {}
+ void Remove();
+ private:
+ virtual ~MemoryPressureObserver() {}
+ PuppetWidget* mWidget;
+ };
+ friend class MemoryPressureObserver;
+
+ // TabChild normally holds a strong reference to this PuppetWidget
+ // or its root ancestor, but each PuppetWidget also needs a
+ // reference back to TabChild (e.g. to delegate nsIWidget IME calls
+ // to chrome) So we hold a weak reference to TabChild here. Since
+ // it's possible for TabChild to outlive the PuppetWidget, we clear
+ // this weak reference in Destroy()
+ TabChild* mTabChild;
+ // The "widget" to which we delegate events if we don't have an
+ // event handler.
+ RefPtr<PuppetWidget> mChild;
+ LayoutDeviceIntRegion mDirtyRegion;
+ nsRevocableEventPtr<PaintTask> mPaintTask;
+ RefPtr<MemoryPressureObserver> mMemoryPressureObserver;
+ // XXX/cjones: keeping this around until we teach LayerManager to do
+ // retained-content-only transactions
+ RefPtr<DrawTarget> mDrawTarget;
+ // IME
+ nsIMEUpdatePreference mIMEPreferenceOfParent;
+ 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;
+
+ // Precomputed answers for ExecuteNativeKeyBinding
+ InfallibleTArray<mozilla::CommandInt> mSingleLineCommands;
+ InfallibleTArray<mozilla::CommandInt> mMultiLineCommands;
+ InfallibleTArray<mozilla::CommandInt> mRichTextCommands;
+
+ nsCOMPtr<imgIContainer> mCustomCursor;
+ uint32_t mCursorHotspotX, mCursorHotspotY;
+
+ nsCOMArray<nsIKeyEventInPluginCallback> mKeyEventInPluginCallbacks;
+
+protected:
+ bool mEnabled;
+ bool mVisible;
+
+private:
+ bool mNeedIMEStateInit;
+ bool mNativeKeyCommandsValid;
+};
+
+struct AutoCacheNativeKeyCommands
+{
+ explicit AutoCacheNativeKeyCommands(PuppetWidget* aWidget)
+ : mWidget(aWidget)
+ {
+ mSavedValid = mWidget->mNativeKeyCommandsValid;
+ mSavedSingleLine = mWidget->mSingleLineCommands;
+ mSavedMultiLine = mWidget->mMultiLineCommands;
+ mSavedRichText = mWidget->mRichTextCommands;
+ }
+
+ void Cache(const InfallibleTArray<mozilla::CommandInt>& aSingleLineCommands,
+ const InfallibleTArray<mozilla::CommandInt>& aMultiLineCommands,
+ const InfallibleTArray<mozilla::CommandInt>& aRichTextCommands)
+ {
+ mWidget->mNativeKeyCommandsValid = true;
+ mWidget->mSingleLineCommands = aSingleLineCommands;
+ mWidget->mMultiLineCommands = aMultiLineCommands;
+ mWidget->mRichTextCommands = aRichTextCommands;
+ }
+
+ void CacheNoCommands()
+ {
+ mWidget->mNativeKeyCommandsValid = true;
+ mWidget->mSingleLineCommands.Clear();
+ mWidget->mMultiLineCommands.Clear();
+ mWidget->mRichTextCommands.Clear();
+ }
+
+ ~AutoCacheNativeKeyCommands()
+ {
+ mWidget->mNativeKeyCommandsValid = mSavedValid;
+ mWidget->mSingleLineCommands = mSavedSingleLine;
+ mWidget->mMultiLineCommands = mSavedMultiLine;
+ mWidget->mRichTextCommands = mSavedRichText;
+ }
+
+private:
+ PuppetWidget* mWidget;
+ bool mSavedValid;
+ InfallibleTArray<mozilla::CommandInt> mSavedSingleLine;
+ InfallibleTArray<mozilla::CommandInt> mSavedMultiLine;
+ InfallibleTArray<mozilla::CommandInt> mSavedRichText;
+};
+
+class PuppetScreen : public nsBaseScreen
+{
+public:
+ explicit PuppetScreen(void* nativeScreen);
+ ~PuppetScreen();
+
+ NS_IMETHOD GetId(uint32_t* aId) override;
+ 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;
+ NS_IMETHOD GetRotation(uint32_t* aRotation) override;
+ NS_IMETHOD SetRotation(uint32_t aRotation) 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/ScreenProxy.cpp b/widget/ScreenProxy.cpp
new file mode 100644
index 000000000..462dd7476
--- /dev/null
+++ b/widget/ScreenProxy.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
+/* vim: set sw=4 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 "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsIAppShell.h"
+#include "nsScreenManagerProxy.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWidgetsCID.h"
+#include "ScreenProxy.h"
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::dom;
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
+ScreenProxy::ScreenProxy(nsScreenManagerProxy* aScreenManager, ScreenDetails aDetails)
+ : mContentsScaleFactor(0)
+ , mScreenManager(aScreenManager)
+ , mId(0)
+ , mPixelDepth(0)
+ , mColorDepth(0)
+ , mCacheValid(false)
+ , mCacheWillInvalidate(false)
+{
+ PopulateByDetails(aDetails);
+}
+
+NS_IMETHODIMP
+ScreenProxy::GetId(uint32_t *outId)
+{
+ *outId = mId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScreenProxy::GetRect(int32_t *outLeft,
+ int32_t *outTop,
+ int32_t *outWidth,
+ int32_t *outHeight)
+{
+ if (!EnsureCacheIsValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *outLeft = mRect.x;
+ *outTop = mRect.y;
+ *outWidth = mRect.width;
+ *outHeight = mRect.height;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScreenProxy::GetRectDisplayPix(int32_t *outLeft,
+ int32_t *outTop,
+ int32_t *outWidth,
+ int32_t *outHeight)
+{
+ if (!EnsureCacheIsValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *outLeft = mRectDisplayPix.x;
+ *outTop = mRectDisplayPix.y;
+ *outWidth = mRectDisplayPix.width;
+ *outHeight = mRectDisplayPix.height;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScreenProxy::GetAvailRect(int32_t *outLeft,
+ int32_t *outTop,
+ int32_t *outWidth,
+ int32_t *outHeight)
+{
+ if (!EnsureCacheIsValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *outLeft = mAvailRect.x;
+ *outTop = mAvailRect.y;
+ *outWidth = mAvailRect.width;
+ *outHeight = mAvailRect.height;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScreenProxy::GetAvailRectDisplayPix(int32_t *outLeft,
+ int32_t *outTop,
+ int32_t *outWidth,
+ int32_t *outHeight)
+{
+ if (!EnsureCacheIsValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *outLeft = mAvailRectDisplayPix.x;
+ *outTop = mAvailRectDisplayPix.y;
+ *outWidth = mAvailRectDisplayPix.width;
+ *outHeight = mAvailRectDisplayPix.height;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScreenProxy::GetPixelDepth(int32_t *aPixelDepth)
+{
+ if (!EnsureCacheIsValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aPixelDepth = mPixelDepth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScreenProxy::GetColorDepth(int32_t *aColorDepth)
+{
+ if (!EnsureCacheIsValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aColorDepth = mColorDepth;
+ return NS_OK;
+}
+
+void
+ScreenProxy::PopulateByDetails(ScreenDetails aDetails)
+{
+ mId = aDetails.id();
+ mRect = nsIntRect(aDetails.rect());
+ mRectDisplayPix = nsIntRect(aDetails.rectDisplayPix());
+ mAvailRect = nsIntRect(aDetails.availRect());
+ mAvailRectDisplayPix = nsIntRect(aDetails.availRectDisplayPix());
+ mPixelDepth = aDetails.pixelDepth();
+ mColorDepth = aDetails.colorDepth();
+ mContentsScaleFactor = aDetails.contentsScaleFactor();
+}
+
+bool
+ScreenProxy::EnsureCacheIsValid()
+{
+ if (mCacheValid) {
+ return true;
+ }
+
+ bool success = false;
+ // Kick off a synchronous IPC call to the parent to get the
+ // most up-to-date information.
+ ScreenDetails details;
+ Unused << mScreenManager->SendScreenRefresh(mId, &details, &success);
+ if (!success) {
+ NS_WARNING("Updating a ScreenProxy in the child process failed on parent side.");
+ return false;
+ }
+
+ PopulateByDetails(details);
+ mCacheValid = true;
+
+ InvalidateCacheOnNextTick();
+ return true;
+}
+
+void
+ScreenProxy::InvalidateCacheOnNextTick()
+{
+ if (mCacheWillInvalidate) {
+ return;
+ }
+
+ mCacheWillInvalidate = true;
+
+ nsContentUtils::RunInStableState(NewRunnableMethod(this, &ScreenProxy::InvalidateCache));
+}
+
+void
+ScreenProxy::InvalidateCache()
+{
+ mCacheValid = false;
+ mCacheWillInvalidate = false;
+}
+
+} // namespace widget
+} // namespace mozilla
+
diff --git a/widget/ScreenProxy.h b/widget/ScreenProxy.h
new file mode 100644
index 000000000..22e78c36d
--- /dev/null
+++ b/widget/ScreenProxy.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
+/* vim: set sw=4 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/. */
+
+#ifndef mozilla_widget_ScreenProxy_h
+#define mozilla_widget_ScreenProxy_h
+
+#include "nsBaseScreen.h"
+#include "mozilla/dom/PScreenManagerChild.h"
+#include "mozilla/dom/TabChild.h"
+
+class nsScreenManagerProxy;
+
+namespace mozilla {
+namespace widget {
+
+class ScreenProxy : public nsBaseScreen
+{
+public:
+ ScreenProxy(nsScreenManagerProxy* aScreenManager,
+ mozilla::dom::ScreenDetails aDetails);
+ ~ScreenProxy() {};
+
+ NS_IMETHOD GetId(uint32_t* aId) override;
+
+ NS_IMETHOD GetRect(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 GetAvailRect(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;
+
+private:
+
+ void PopulateByDetails(mozilla::dom::ScreenDetails aDetails);
+ bool EnsureCacheIsValid();
+ void InvalidateCacheOnNextTick();
+ void InvalidateCache();
+
+ double mContentsScaleFactor;
+ RefPtr<nsScreenManagerProxy> mScreenManager;
+ uint32_t mId;
+ int32_t mPixelDepth;
+ int32_t mColorDepth;
+ nsIntRect mRect;
+ nsIntRect mRectDisplayPix;
+ nsIntRect mAvailRect;
+ nsIntRect mAvailRectDisplayPix;
+ bool mCacheValid;
+ bool mCacheWillInvalidate;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
+
diff --git a/widget/SharedWidgetUtils.cpp b/widget/SharedWidgetUtils.cpp
new file mode 100644
index 000000000..ab3513fc5
--- /dev/null
+++ b/widget/SharedWidgetUtils.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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();
+}
+
+// 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->GetParent(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 000000000..5eb201fc8
--- /dev/null
+++ b/widget/SystemTimeConverter.h
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "mozilla/TimeStamp.h"
+#include "mozilla/TypeTraits.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>
+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(!IsSigned<Time>::value, "Expected Time to be unsigned");
+ }
+
+ template <typename CurrentTimeGetter>
+ mozilla::TimeStamp
+ GetTimeStampFromSystemTime(Time aTime,
+ CurrentTimeGetter& aCurrentTimeGetter) {
+ // If the reference time is not set, use the current time value to fill
+ // it in.
+ if (mReferenceTimeStamp.IsNull()) {
+ UpdateReferenceTime(aTime, aCurrentTimeGetter);
+ }
+ TimeStamp roughlyNow = TimeStamp::Now();
+
+ // 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".
+ //
+ // We call the absolute difference between (i) and (ii), "deltaFromNow".
+ //
+ // Graphically:
+ //
+ // mReferenceTime aTime
+ // Time scale: ........+.......................*........
+ // |--------timeDelta------|
+ //
+ // mReferenceTimeStamp roughlyNow
+ // TimeStamp scale: ........+...........................*....
+ // |------timeStampDelta-------|
+ //
+ // |---|
+ // deltaFromNow
+ //
+ Time deltaFromNow;
+ bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &deltaFromNow);
+
+ // Tolerance when detecting clock skew.
+ static const Time kTolerance = 30;
+
+ // 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 (deltaFromNow <= 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 roughlyNow - TimeDuration::FromMilliseconds(deltaFromNow);
+ }
+
+ 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.
+ Time delta;
+ if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, &delta)) {
+ 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 = TimeStamp::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, Time* aDelta)
+ {
+ 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.
+ Time timeStampDelta =
+ static_cast<int64_t>((aTimeStamp - mReferenceTimeStamp).ToMilliseconds());
+
+ Time timeToTimeStamp = timeStampDelta - timeDelta;
+ bool isNewer = false;
+ if (timeToTimeStamp == 0) {
+ *aDelta = 0;
+ } else if (timeToTimeStamp < kTimeHalfRange) {
+ *aDelta = timeToTimeStamp;
+ } else {
+ isNewer = true;
+ *aDelta = timeDelta - timeStampDelta;
+ }
+
+ 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 000000000..6c54376a4
--- /dev/null
+++ b/widget/TextEventDispatcher.cpp
@@ -0,0 +1,780 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/TextEvents.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "nsIDocShell.h"
+#include "nsIFrame.h"
+#include "nsIPresShell.h"
+#include "nsIWidget.h"
+#include "nsPIDOMWindow.h"
+#include "nsView.h"
+
+namespace mozilla {
+namespace widget {
+
+/******************************************************************************
+ * TextEventDispatcher
+ *****************************************************************************/
+
+bool TextEventDispatcher::sDispatchKeyEventsDuringComposition = false;
+
+TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
+ : mWidget(aWidget)
+ , mDispatchingEvent(0)
+ , mInputTransactionType(eNoInputTransaction)
+ , mIsComposing(false)
+{
+ MOZ_RELEASE_ASSERT(mWidget, "aWidget must not be nullptr");
+
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ Preferences::AddBoolVarCache(
+ &sDispatchKeyEventsDuringComposition,
+ "dom.keyboardevent.dispatch_during_composition",
+ false);
+ sInitialized = true;
+ }
+}
+
+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) {
+ 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);
+ }
+ return NS_OK;
+}
+
+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);
+}
+
+void
+TextEventDispatcher::OnDestroyWidget()
+{
+ mWidget = nullptr;
+ 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;
+ }
+
+ mIsComposing = 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;
+ }
+
+ // 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;
+ // Don't send CRLF, replace it with LF here.
+ compositionCommitEvent.mData.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
+ NS_LITERAL_STRING("\n"));
+ }
+ 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;
+
+ // First, send the notification to current input transaction's listener.
+ nsCOMPtr<TextEventDispatcherListener> listener = do_QueryReferent(mListener);
+ if (listener) {
+ rv = listener->NotifyIME(this, aIMENotification);
+ }
+
+ if (mInputTransactionType == eNativeInputTransaction || !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 (!nativeListener) {
+ return rv;
+ }
+ switch (aIMENotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ // It's not necessary to notify native IME of requests.
+ return rv;
+ 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.
+ return rv == NS_ERROR_NOT_IMPLEMENTED ? rv2 : rv;
+ }
+ }
+}
+
+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 (!sDispatchKeyEventsDuringComposition || 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);
+
+ if (aStatus == nsEventStatus_eConsumeNoDefault) {
+ // If the key event should be dispatched as consumed event, marking it here.
+ // This is useful to prevent double action. E.g., when the key was already
+ // handled by system, our chrome shouldn't handle it.
+ keyEvent.PreventDefaultBeforeDispatch();
+ }
+
+ // 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");
+ }
+ wchar_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);
+ }
+ }
+
+ 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;
+}
+
+void
+TextEventDispatcher::PendingComposition::EnsureClauseArray()
+{
+ if (mClauses) {
+ return;
+ }
+ mClauses = new TextRangeArray();
+}
+
+nsresult
+TextEventDispatcher::PendingComposition::SetString(const nsAString& aString)
+{
+ mString = aString;
+ return NS_OK;
+}
+
+nsresult
+TextEventDispatcher::PendingComposition::AppendClause(
+ uint32_t aLength,
+ TextRangeType aTextRangeType)
+{
+ 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)
+{
+ 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();
+
+ nsAutoString str(aString);
+ // Don't expose CRLF to web contents, instead, use LF.
+ str.ReplaceSubstring(NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING("\n"));
+ nsresult rv = SetString(str);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!aRanges || aRanges->IsEmpty()) {
+ // Create dummy range if aString isn't empty.
+ if (!aString.IsEmpty()) {
+ rv = AppendClause(str.Length(), TextRangeType::eRawClause);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ return NS_OK;
+ }
+
+ // Adjust offsets in the ranges for XP linefeed character (only \n).
+ // XXX Following code is the safest approach. However, it wastes performance.
+ // For ensuring the clauses do not overlap each other, we should redesign
+ // TextRange later.
+ for (uint32_t i = 0; i < aRanges->Length(); ++i) {
+ TextRange range = aRanges->ElementAt(i);
+ TextRange nativeRange = range;
+ if (nativeRange.mStartOffset > 0) {
+ nsAutoString preText(Substring(aString, 0, nativeRange.mStartOffset));
+ preText.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
+ NS_LITERAL_STRING("\n"));
+ range.mStartOffset = preText.Length();
+ }
+ if (nativeRange.Length() == 0) {
+ range.mEndOffset = range.mStartOffset;
+ } else {
+ nsAutoString clause(
+ Substring(aString, nativeRange.mStartOffset, nativeRange.Length()));
+ clause.ReplaceSubstring(NS_LITERAL_STRING("\r\n"),
+ NS_LITERAL_STRING("\n"));
+ range.mEndOffset = range.mStartOffset + clause.Length();
+ }
+ if (range.mRangeType == TextRangeType::eCaret) {
+ mCaret = range;
+ } else {
+ EnsureClauseArray();
+ mClauses->AppendElement(range);
+ }
+ }
+ return NS_OK;
+}
+
+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);
+ }
+
+ 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 (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 000000000..1bed15a3b
--- /dev/null
+++ b/widget/TextEventDispatcher.h
@@ -0,0 +1,486 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+class nsIWidget;
+
+namespace mozilla {
+namespace widget {
+
+struct IMENotification;
+
+/**
+ * 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()
+ {
+ }
+
+ 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();
+
+ /**
+ * 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; }
+
+ /**
+ * 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().
+ */
+ bool IsComposing() const { return mIsComposing; }
+
+ /**
+ * 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;
+
+ // 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;
+
+ void EnsureClauseArray();
+ };
+ 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.
+ 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.
+ eSameProcessSyncTestInputTransaction,
+ // Input transaction for Others (must be IME on B2G). Events are fired
+ // synchronously because TextInputProcessor which is the only user of
+ // this input transaction type supports only keyboard apps on B2G.
+ // Keyboard apps on B2G doesn't want to dispatch keyboard events to
+ // chrome process. Therefore, this should dispatch key events only in
+ // the current 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;
+
+ // If this is true, keydown and keyup events are dispatched even when there
+ // is a composition.
+ static bool sDispatchKeyEventsDuringComposition;
+
+ 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);
+};
+
+} // 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 000000000..52634fc6e
--- /dev/null
+++ b/widget/TextEventDispatcherListener.h
@@ -0,0 +1,89 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_textinputdispatcherlistener_h_
+#define mozilla_textinputdispatcherlistener_h_
+
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class TextEventDispatcher;
+struct IMENotification;
+
+#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;
+
+ /**
+ * 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 000000000..6c2934114
--- /dev/null
+++ b/widget/TextEvents.h
@@ -0,0 +1,1014 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/TextRange.h"
+#include "mozilla/FontRange.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsISelectionController.h"
+#include "nsISelectionListener.h"
+#include "nsITransferable.h"
+#include "nsRect.h"
+#include "nsStringGlue.h"
+#include "nsTArray.h"
+#include "WritingModes.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 {
+
+const nsCString GetDOMKeyCodeName(uint32_t aKeyCode);
+
+namespace dom {
+ class PBrowserParent;
+ class PBrowserChild;
+} // namespace dom
+namespace plugins {
+ class PPluginInstanceChild;
+} // namespace plugins
+
+/******************************************************************************
+ * 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(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::WidgetKeyboardEvent
+ ******************************************************************************/
+
+class WidgetKeyboardEvent : public WidgetInputEvent
+{
+private:
+ friend class dom::PBrowserParent;
+ friend class dom::PBrowserChild;
+
+protected:
+ WidgetKeyboardEvent()
+ : mNativeKeyEvent(nullptr)
+ , mKeyCode(0)
+ , mCharCode(0)
+ , mPseudoCharCode(0)
+ , mLocation(nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD)
+ , mAccessKeyForwardedToChild(false)
+ , mUniqueId(0)
+#ifdef XP_MACOSX
+ , mNativeModifierFlags(0)
+ , mNativeKeyCode(0)
+#endif // #ifdef XP_MACOSX
+ , mKeyNameIndex(mozilla::KEY_NAME_INDEX_Unidentified)
+ , mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
+ , mInputMethodAppState(eNotHandled)
+ , mIsChar(false)
+ , mIsRepeat(false)
+ , mIsComposing(false)
+ , mIsReserved(false)
+ , mIsSynthesizedByTIP(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(nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD)
+ , mAccessKeyForwardedToChild(false)
+ , mUniqueId(0)
+#ifdef XP_MACOSX
+ , mNativeModifierFlags(0)
+ , mNativeKeyCode(0)
+#endif // #ifdef XP_MACOSX
+ , mKeyNameIndex(mozilla::KEY_NAME_INDEX_Unidentified)
+ , mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
+ , mInputMethodAppState(eNotHandled)
+ , mIsChar(false)
+ , mIsRepeat(false)
+ , mIsComposing(false)
+ , mIsReserved(false)
+ , mIsSynthesizedByTIP(false)
+ {
+ // If this is a keyboard event on a plugin, it shouldn't fired on content.
+ mFlags.mOnlySystemGroupDispatchInContent =
+ mFlags.mNoCrossProcessBoundaryForwarding = IsKeyEventOnPlugin();
+ }
+
+ 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);
+ }
+
+ 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->mFlags = mFlags;
+ return result;
+ }
+
+ // OS translated Unicode chars which are used for accesskey and accelkey
+ // handling. The handlers will try from first character to last character.
+ nsTArray<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;
+
+#ifdef XP_MACOSX
+ // Values given by a native NSEvent, for use with Cocoa NPAPI plugins.
+ nsString mNativeCharacters;
+ nsString mNativeCharactersIgnoringModifiers;
+ // If this is non-empty, create a text event for plugins instead of a
+ // keyboard event.
+ nsString mPluginTextEventString;
+#endif // #ifdef XP_MACOSX
+
+ // OS-specific native event can optionally be preserved
+ void* mNativeKeyEvent;
+ // A DOM keyCode value or 0. If a keypress event whose mCharCode is 0, this
+ // 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 nsIDOMKeyEvent::DOM_KEY_LOCATION_*
+ uint32_t mLocation;
+ // True if accesskey handling was forwarded to the child via
+ // TabParent::HandleAccessKey. In this case, parent process menu access key
+ // handling should be delayed until it is determined that there exists no
+ // overriding access key in the content process.
+ bool mAccessKeyForwardedToChild;
+ // Unique id associated with a keydown / keypress event. Used in identifing
+ // keypress events for removal from async event dispatch queue in metrofx
+ // after preventDefault is called on keydown events. It's ok if this wraps
+ // over long periods.
+ uint32_t mUniqueId;
+
+#ifdef XP_MACOSX
+ // Values given by a native NSEvent, for use with Cocoa NPAPI plugins.
+ uint32_t mNativeModifierFlags;
+ uint16_t mNativeKeyCode;
+#endif // #ifdef XP_MACOSX
+
+ // DOM KeyboardEvent.key
+ KeyNameIndex mKeyNameIndex;
+ // DOM KeyboardEvent.code
+ CodeNameIndex mCodeNameIndex;
+ // Indicates that the event is being handled by input method app
+ typedef uint8_t InputMethodAppStateType;
+ enum InputMethodAppState : InputMethodAppStateType
+ {
+ eNotHandled, // not yet handled by intput method app
+ eHandling, // being handled by intput method app
+ eHandled // handled by input method app
+ };
+ InputMethodAppState mInputMethodAppState;
+
+ // Indicates whether the event signifies a printable character
+ bool mIsChar;
+ // 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 if the key combination is reserved by chrome. This is set by
+ // nsXBLWindowKeyHandler at capturing phase of the default event group.
+ bool mIsReserved;
+ // Indicates whether the event is synthesized from Text Input Processor
+ // or an actual event from nsAppShell.
+ bool mIsSynthesizedByTIP;
+
+ // 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);
+ }
+
+ 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);
+
+ /**
+ * 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);
+
+ static void Shutdown();
+
+ /**
+ * ComputeLocationFromCodeValue() returns one of .mLocation value
+ * (nsIDOMKeyEvent::DOM_KEY_LOCATION_*) which is the most preferred value
+ * for the specified 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);
+
+ /**
+ * GetModifierForKeyName() returns a value of Modifier which is activated
+ * by the aKeyNameIndex.
+ */
+ static Modifier GetModifierForKeyName(KeyNameIndex aKeyNameIndex);
+
+ /**
+ * 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;
+ mIsChar = aEvent.mIsChar;
+ mIsRepeat = aEvent.mIsRepeat;
+ mIsComposing = aEvent.mIsComposing;
+ mIsReserved = aEvent.mIsReserved;
+ mAccessKeyForwardedToChild = aEvent.mAccessKeyForwardedToChild;
+ 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;
+#ifdef XP_MACOSX
+ mNativeKeyCode = aEvent.mNativeKeyCode;
+ mNativeModifierFlags = aEvent.mNativeModifierFlags;
+ mNativeCharacters.Assign(aEvent.mNativeCharacters);
+ mNativeCharactersIgnoringModifiers.
+ Assign(aEvent.mNativeCharactersIgnoringModifiers);
+ mPluginTextEventString.Assign(aEvent.mPluginTextEventString);
+#endif
+ mInputMethodAppState = aEvent.mInputMethodAppState;
+ mIsSynthesizedByTIP = aEvent.mIsSynthesizedByTIP;
+ }
+
+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;
+};
+
+
+/******************************************************************************
+ * mozilla::InternalBeforeAfterKeyboardEvent
+ *
+ * This is extended from WidgetKeyboardEvent and is mapped to DOM event
+ * "BeforeAfterKeyboardEvent".
+ *
+ * Event mMessage: eBeforeKeyDown
+ * eBeforeKeyUp
+ * eAfterKeyDown
+ * eAfterKeyUp
+ ******************************************************************************/
+class InternalBeforeAfterKeyboardEvent : public WidgetKeyboardEvent
+{
+private:
+ friend class dom::PBrowserParent;
+ friend class dom::PBrowserChild;
+
+ InternalBeforeAfterKeyboardEvent()
+ {
+ }
+
+public:
+ // Extra member for InternalBeforeAfterKeyboardEvent. Indicates whether
+ // default actions of keydown/keyup event is prevented.
+ Nullable<bool> mEmbeddedCancelled;
+
+ virtual InternalBeforeAfterKeyboardEvent* AsBeforeAfterKeyboardEvent() override
+ {
+ return this;
+ }
+
+ InternalBeforeAfterKeyboardEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : WidgetKeyboardEvent(aIsTrusted, aMessage, aWidget,
+ eBeforeAfterKeyboardEventClass)
+ {
+ }
+
+ virtual WidgetEvent* Duplicate() const override
+ {
+ MOZ_ASSERT(mClass == eBeforeAfterKeyboardEventClass,
+ "Duplicate() must be overridden by sub class");
+ // Not copying widget, it is a weak reference.
+ InternalBeforeAfterKeyboardEvent* result =
+ new InternalBeforeAfterKeyboardEvent(false, mMessage, nullptr);
+ result->AssignBeforeAfterKeyEventData(*this, true);
+ result->mFlags = mFlags;
+ return result;
+ }
+
+ void AssignBeforeAfterKeyEventData(
+ const InternalBeforeAfterKeyboardEvent& aEvent,
+ bool aCopyTargets)
+ {
+ AssignKeyEventData(aEvent, aCopyTargets);
+ mEmbeddedCancelled = aEvent.mEmbeddedCancelled;
+ }
+
+ void AssignBeforeAfterKeyEventData(
+ const WidgetKeyboardEvent& aEvent,
+ bool aCopyTargets)
+ {
+ AssignKeyEventData(aEvent, aCopyTargets);
+ }
+};
+
+/******************************************************************************
+ * mozilla::WidgetCompositionEvent
+ ******************************************************************************/
+
+class WidgetCompositionEvent : public WidgetGUIEvent
+{
+private:
+ friend class mozilla::dom::PBrowserParent;
+ friend class mozilla::dom::PBrowserChild;
+
+ WidgetCompositionEvent()
+ {
+ }
+
+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()
+ : mSucceeded(false)
+ , mUseNativeLineBreak(true)
+ , mWithFontRanges(false)
+ {
+ 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)
+ , mSucceeded(false)
+ , mUseNativeLineBreak(true)
+ , mWithFontRanges(false)
+ {
+ }
+
+ WidgetQueryContentEvent(EventMessage aMessage,
+ const WidgetQueryContentEvent& aOtherEvent)
+ : WidgetGUIEvent(aOtherEvent.IsTrusted(), aMessage,
+ const_cast<nsIWidget*>(aOtherEvent.mWidget.get()),
+ eQueryContentEventClass)
+ , mSucceeded(false)
+ , mUseNativeLineBreak(aOtherEvent.mUseNativeLineBreak)
+ , mWithFontRanges(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()");
+ }
+
+ 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);
+ }
+
+ void RequestFontRanges()
+ {
+ NS_ASSERTION(mMessage == eQueryTextContent,
+ "not querying text content");
+ mWithFontRanges = true;
+ }
+
+ uint32_t GetSelectionStart(void) const
+ {
+ NS_ASSERTION(mMessage == eQuerySelectedText,
+ "not querying selection");
+ return mReply.mOffset + (mReply.mReversed ? mReply.mString.Length() : 0);
+ }
+
+ uint32_t GetSelectionEnd(void) const
+ {
+ NS_ASSERTION(mMessage == eQuerySelectedText,
+ "not querying selection");
+ return mReply.mOffset + (mReply.mReversed ? 0 : mReply.mString.Length());
+ }
+
+ mozilla::WritingMode GetWritingMode(void) const
+ {
+ NS_ASSERTION(mMessage == eQuerySelectedText ||
+ mMessage == eQueryCaretRect ||
+ mMessage == eQueryTextRect,
+ "not querying selection or text rect");
+ return mReply.mWritingMode;
+ }
+
+ bool mSucceeded;
+ bool mUseNativeLineBreak;
+ bool mWithFontRanges;
+ 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
+ {
+ void* mContentsRoot;
+ uint32_t mOffset;
+ // mTentativeCaretOffset is used by only eQueryCharacterAtPoint.
+ // This is the offset where caret would be if user clicked at the mRefPoint.
+ uint32_t mTentativeCaretOffset;
+ nsString mString;
+ // 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
+ AutoTArray<mozilla::FontRange, 1> mFontRanges;
+ // Used by eQueryTextRectArray
+ nsTArray<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()
+ : mContentsRoot(nullptr)
+ , mOffset(NOT_FOUND)
+ , mTentativeCaretOffset(NOT_FOUND)
+ , mFocusedWidget(nullptr)
+ , mReversed(false)
+ , mHasSelection(false)
+ , mWidgetIsHit(false)
+ {
+ }
+ } mReply;
+
+ enum
+ {
+ NOT_FOUND = UINT32_MAX
+ };
+
+ // 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)
+ {
+ }
+
+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()
+ : mIsComposing(false)
+ {
+ }
+
+public:
+ virtual InternalEditorInputEvent* AsEditorInputEvent() override
+ {
+ return this;
+ }
+
+ InternalEditorInputEvent(bool aIsTrusted, EventMessage aMessage,
+ nsIWidget* aWidget)
+ : InternalUIEvent(aIsTrusted, aMessage, aWidget, eEditorInputEventClass)
+ , mIsComposing(false)
+ {
+ }
+
+ 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;
+ }
+
+ bool mIsComposing;
+
+ void AssignEditorInputEventData(const InternalEditorInputEvent& aEvent,
+ bool aCopyTargets)
+ {
+ AssignUIEventData(aEvent, aCopyTargets);
+
+ mIsComposing = aEvent.mIsComposing;
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TextEvents_h__
diff --git a/widget/TextRange.h b/widget/TextRange.h
new file mode 100644
index 000000000..7934e9e23
--- /dev/null
+++ b/widget/TextRange.h
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsStyleConsts.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+/******************************************************************************
+ * mozilla::TextRangeStyle
+ ******************************************************************************/
+
+struct TextRangeStyle
+{
+ enum
+ {
+ LINESTYLE_NONE = NS_STYLE_TEXT_DECORATION_STYLE_NONE,
+ LINESTYLE_SOLID = NS_STYLE_TEXT_DECORATION_STYLE_SOLID,
+ LINESTYLE_DOTTED = NS_STYLE_TEXT_DECORATION_STYLE_DOTTED,
+ LINESTYLE_DASHED = NS_STYLE_TEXT_DECORATION_STYLE_DASHED,
+ LINESTYLE_DOUBLE = NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE,
+ LINESTYLE_WAVY = NS_STYLE_TEXT_DECORATION_STYLE_WAVY
+ };
+
+ 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.
+ 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;
+ uint8_t 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);
+
+inline RawSelectionType ToRawSelectionType(TextRangeType aTextRangeType)
+{
+ return ToRawSelectionType(ToSelectionType(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() {}
+
+ 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/TouchEvents.h b/widget/TouchEvents.h
new file mode 100644
index 000000000..8bc18a963
--- /dev/null
+++ b/widget/TouchEvents.h
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIDOMSimpleGestureEvent.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 nsIDOMSimpleGestureEvent for values
+ uint32_t mAllowedDirections;
+ // See nsIDOMSimpleGestureEvent 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;
+
+ virtual WidgetTouchEvent* AsTouchEvent() override { return this; }
+
+ WidgetTouchEvent()
+ {
+ MOZ_COUNT_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;
+ }
+
+ virtual ~WidgetTouchEvent()
+ {
+ MOZ_COUNT_DTOR(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/VsyncDispatcher.cpp b/widget/VsyncDispatcher.cpp
new file mode 100644
index 000000000..5979466e8
--- /dev/null
+++ b/widget/VsyncDispatcher.cpp
@@ -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/. */
+
+#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"
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "GeckoProfiler.h"
+#include "ProfilerMarkers.h"
+#endif
+
+namespace mozilla {
+
+CompositorVsyncDispatcher::CompositorVsyncDispatcher()
+ : mCompositorObserverLock("CompositorObserverLock")
+ , mDidShutdown(false)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+CompositorVsyncDispatcher::~CompositorVsyncDispatcher()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // We auto remove this vsync dispatcher from the vsync source in the nsBaseWidget
+}
+
+void
+CompositorVsyncDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
+{
+ // In vsync thread
+#ifdef MOZ_ENABLE_PROFILER_SPS
+ layers::CompositorBridgeParent::PostInsertVsyncProfilerMarker(aVsyncTimestamp);
+#endif
+
+ MutexAutoLock lock(mCompositorObserverLock);
+ if (mCompositorVsyncObserver) {
+ mCompositorVsyncObserver->NotifyVsync(aVsyncTimestamp);
+ }
+}
+
+void
+CompositorVsyncDispatcher::ObserveVsync(bool aEnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (mDidShutdown) {
+ return;
+ }
+
+ if (aEnable) {
+ gfxPlatform::GetPlatform()->GetHardwareVsync()->AddCompositorVsyncDispatcher(this);
+ } else {
+ gfxPlatform::GetPlatform()->GetHardwareVsync()->RemoveCompositorVsyncDispatcher(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>(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());
+ ObserveVsync(false);
+ mDidShutdown = true;
+ { // scope lock
+ MutexAutoLock lock(mCompositorObserverLock);
+ mCompositorVsyncObserver = nullptr;
+ }
+}
+
+RefreshTimerVsyncDispatcher::RefreshTimerVsyncDispatcher()
+ : mRefreshTimersLock("RefreshTimers lock")
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+RefreshTimerVsyncDispatcher::~RefreshTimerVsyncDispatcher()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+void
+RefreshTimerVsyncDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
+{
+ MutexAutoLock lock(mRefreshTimersLock);
+
+ for (size_t i = 0; i < mChildRefreshTimers.Length(); i++) {
+ mChildRefreshTimers[i]->NotifyVsync(aVsyncTimestamp);
+ }
+
+ if (mParentRefreshTimer) {
+ mParentRefreshTimer->NotifyVsync(aVsyncTimestamp);
+ }
+}
+
+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(this,
+ &RefreshTimerVsyncDispatcher::UpdateVsyncStatus));
+ return;
+ }
+
+ gfx::VsyncSource::Display& display = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay();
+ display.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 000000000..8850952ac
--- /dev/null
+++ b/widget/VsyncDispatcher.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 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"
+
+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(TimeStamp aVsyncTimestamp) = 0;
+
+protected:
+ VsyncObserver() {}
+ virtual ~VsyncObserver() {}
+}; // 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();
+
+ // Called on the vsync thread when a hardware vsync occurs
+ void NotifyVsync(TimeStamp aVsyncTimestamp);
+
+ // Compositor vsync observers must be added/removed on the compositor thread
+ void SetCompositorVsyncObserver(VsyncObserver* aVsyncObserver);
+ void Shutdown();
+
+private:
+ virtual ~CompositorVsyncDispatcher();
+ void ObserveVsync(bool aEnable);
+
+ 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:
+ RefreshTimerVsyncDispatcher();
+
+ // Please check CompositorVsyncDispatcher::NotifyVsync().
+ void NotifyVsync(TimeStamp aVsyncTimestamp);
+
+ // 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();
+
+ 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 000000000..52e2b9b40
--- /dev/null
+++ b/widget/WidgetEventImpl.cpp
@@ -0,0 +1,1075 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gfxPrefs.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "nsPrintfCString.h"
+
+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 NS_LITERAL_CSTRING("USE_STRING");
+ }
+ nsAutoString keyName;
+ WidgetKeyboardEvent::GetDOMKeyName(aKeyNameIndex, keyName);
+ return NS_ConvertUTF16toUTF8(keyName);
+}
+
+const nsCString
+ToString(CodeNameIndex aCodeNameIndex)
+{
+ if (aCodeNameIndex == CODE_NAME_INDEX_USE_STRING) {
+ return NS_LITERAL_CSTRING("USE_STRING");
+ }
+ nsAutoString codeName;
+ WidgetKeyboardEvent::GetDOMCodeName(aCodeNameIndex, codeName);
+ return NS_ConvertUTF16toUTF8(codeName);
+}
+
+const nsCString
+GetDOMKeyCodeName(uint32_t aKeyCode)
+{
+ switch (aKeyCode) {
+#define NS_DISALLOW_SAME_KEYCODE
+#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
+ case aDOMKeyCode: \
+ return NS_LITERAL_CSTRING(#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;
+ }
+}
+
+/******************************************************************************
+ * 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;
+}
+
+bool
+WidgetEvent::IsNativeEventDelivererForPlugin() const
+{
+ return mClass == ePluginEventClass;
+}
+
+
+/******************************************************************************
+ * mozilla::WidgetEvent
+ *
+ * Event message checking methods.
+ ******************************************************************************/
+
+bool
+WidgetEvent::HasMouseEventMessage() const
+{
+ switch (mMessage) {
+ case eMouseDown:
+ case eMouseUp:
+ case eMouseClick:
+ case eMouseDoubleClick:
+ 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;
+ }
+}
+
+bool
+WidgetEvent::HasKeyEventMessage() const
+{
+ switch (mMessage) {
+ case eKeyDown:
+ case eKeyPress:
+ case eKeyUp:
+ case eKeyDownOnPlugin:
+ case eKeyUpOnPlugin:
+ case eBeforeKeyDown:
+ case eBeforeKeyUp:
+ case eAfterKeyDown:
+ case eAfterKeyUp:
+ 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::IsRetargetedNativeEventDelivererForPlugin() const
+{
+ const WidgetPluginEvent* pluginEvent = AsPluginEvent();
+ return pluginEvent && pluginEvent->mRetargetToFocusedDocument;
+}
+
+bool
+WidgetEvent::IsNonRetargetedNativeEventDelivererForPlugin() const
+{
+ const WidgetPluginEvent* pluginEvent = AsPluginEvent();
+ return pluginEvent && !pluginEvent->mRetargetToFocusedDocument;
+}
+
+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() &&
+ !IsNativeEventDelivererForPlugin() &&
+ !IsContentCommandEvent();
+}
+
+bool
+WidgetEvent::IsTargetedAtFocusedWindow() const
+{
+ const WidgetMouseEvent* mouseEvent = AsMouseEvent();
+ if (mouseEvent) {
+ return mouseEvent->IsContextMenuKeyEvent();
+ }
+ return HasKeyEventMessage() || IsIMERelatedEvent() ||
+ IsContentCommandEvent() ||
+ IsRetargetedNativeEventDelivererForPlugin();
+}
+
+bool
+WidgetEvent::IsTargetedAtFocusedContent() const
+{
+ const WidgetMouseEvent* mouseEvent = AsMouseEvent();
+ if (mouseEvent) {
+ return mouseEvent->IsContextMenuKeyEvent();
+ }
+ return HasKeyEventMessage() || IsIMERelatedEvent() ||
+ IsRetargetedNativeEventDelivererForPlugin();
+}
+
+bool
+WidgetEvent::IsAllowedToDispatchDOMEvent() const
+{
+ switch (mClass) {
+ case eMouseEventClass:
+ if (mMessage == eMouseTouchDrag) {
+ return false;
+ }
+ MOZ_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;
+ }
+
+ // 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;
+ }
+}
+
+/******************************************************************************
+ * 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 nsIDOMKeyEvent::DOM_VK_META:
+ sAccelModifier = MODIFIER_META;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_WIN:
+ sAccelModifier = MODIFIER_OS;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_ALT:
+ sAccelModifier = MODIFIER_ALT;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_CONTROL:
+ sAccelModifier = MODIFIER_CONTROL;
+ break;
+ default:
+#ifdef XP_MACOSX
+ sAccelModifier = MODIFIER_META;
+#else
+ sAccelModifier = MODIFIER_CONTROL;
+#endif
+ break;
+ }
+ }
+ return sAccelModifier;
+}
+
+/******************************************************************************
+ * mozilla::WidgetWheelEvent (MouseEvents.h)
+ ******************************************************************************/
+
+/* static */ double
+WidgetWheelEvent::ComputeOverriddenDelta(double aDelta, bool aIsForVertical)
+{
+ if (!gfxPrefs::MouseWheelHasRootScrollDeltaOverride()) {
+ return aDelta;
+ }
+ int32_t intFactor = aIsForVertical
+ ? gfxPrefs::MouseWheelRootScrollVerticalFactor()
+ : gfxPrefs::MouseWheelRootScrollHorizontalFactor();
+ // 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;
+
+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)
+{
+ 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)
+{
+ 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],...
+ if (mCharCode) {
+ uint32_t ch = mCharCode;
+ 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 mCharCode 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 && mCharCode != ' ') {
+ aCandidates.AppendElement(' ');
+ }
+ return;
+}
+
+/* static */ void
+WidgetKeyboardEvent::Shutdown()
+{
+ delete sKeyNameIndexHashtable;
+ sKeyNameIndexHashtable = nullptr;
+ delete sCodeNameIndexHashtable;
+ sCodeNameIndexHashtable = 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 */ const char*
+WidgetKeyboardEvent::GetCommandStr(Command aCommand)
+{
+#define NS_DEFINE_COMMAND(aName, aCommandStr) , #aCommandStr
+ static const char* const kCommands[] = {
+ "" // CommandDoNothing
+#include "mozilla/CommandList.h"
+ };
+#undef NS_DEFINE_COMMAND
+
+ MOZ_RELEASE_ASSERT(static_cast<size_t>(aCommand) < ArrayLength(kCommands),
+ "Illegal command enumeration value");
+ return kCommands[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 nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+ case CODE_NAME_INDEX_AltRight:
+ case CODE_NAME_INDEX_ControlRight:
+ case CODE_NAME_INDEX_OSRight:
+ case CODE_NAME_INDEX_ShiftRight:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+ 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 nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+ default:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ }
+}
+
+/* static */ uint32_t
+WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex(KeyNameIndex aKeyNameIndex)
+{
+ switch (aKeyNameIndex) {
+ case KEY_NAME_INDEX_Cancel:
+ return nsIDOMKeyEvent::DOM_VK_CANCEL;
+ case KEY_NAME_INDEX_Help:
+ return nsIDOMKeyEvent::DOM_VK_HELP;
+ case KEY_NAME_INDEX_Backspace:
+ return nsIDOMKeyEvent::DOM_VK_BACK_SPACE;
+ case KEY_NAME_INDEX_Tab:
+ return nsIDOMKeyEvent::DOM_VK_TAB;
+ case KEY_NAME_INDEX_Clear:
+ return nsIDOMKeyEvent::DOM_VK_CLEAR;
+ case KEY_NAME_INDEX_Enter:
+ return nsIDOMKeyEvent::DOM_VK_RETURN;
+ case KEY_NAME_INDEX_Shift:
+ return nsIDOMKeyEvent::DOM_VK_SHIFT;
+ case KEY_NAME_INDEX_Control:
+ return nsIDOMKeyEvent::DOM_VK_CONTROL;
+ case KEY_NAME_INDEX_Alt:
+ return nsIDOMKeyEvent::DOM_VK_ALT;
+ case KEY_NAME_INDEX_Pause:
+ return nsIDOMKeyEvent::DOM_VK_PAUSE;
+ case KEY_NAME_INDEX_CapsLock:
+ return nsIDOMKeyEvent::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 nsIDOMKeyEvent::DOM_VK_KANA;
+ case KEY_NAME_INDEX_HangulMode:
+ return nsIDOMKeyEvent::DOM_VK_HANGUL;
+ case KEY_NAME_INDEX_Eisu:
+ return nsIDOMKeyEvent::DOM_VK_EISU;
+ case KEY_NAME_INDEX_JunjaMode:
+ return nsIDOMKeyEvent::DOM_VK_JUNJA;
+ case KEY_NAME_INDEX_FinalMode:
+ return nsIDOMKeyEvent::DOM_VK_FINAL;
+ case KEY_NAME_INDEX_HanjaMode:
+ return nsIDOMKeyEvent::DOM_VK_HANJA;
+ case KEY_NAME_INDEX_KanjiMode:
+ return nsIDOMKeyEvent::DOM_VK_KANJI;
+ case KEY_NAME_INDEX_Escape:
+ return nsIDOMKeyEvent::DOM_VK_ESCAPE;
+ case KEY_NAME_INDEX_Convert:
+ return nsIDOMKeyEvent::DOM_VK_CONVERT;
+ case KEY_NAME_INDEX_NonConvert:
+ return nsIDOMKeyEvent::DOM_VK_NONCONVERT;
+ case KEY_NAME_INDEX_Accept:
+ return nsIDOMKeyEvent::DOM_VK_ACCEPT;
+ case KEY_NAME_INDEX_ModeChange:
+ return nsIDOMKeyEvent::DOM_VK_MODECHANGE;
+ case KEY_NAME_INDEX_PageUp:
+ return nsIDOMKeyEvent::DOM_VK_PAGE_UP;
+ case KEY_NAME_INDEX_PageDown:
+ return nsIDOMKeyEvent::DOM_VK_PAGE_DOWN;
+ case KEY_NAME_INDEX_End:
+ return nsIDOMKeyEvent::DOM_VK_END;
+ case KEY_NAME_INDEX_Home:
+ return nsIDOMKeyEvent::DOM_VK_HOME;
+ case KEY_NAME_INDEX_ArrowLeft:
+ return nsIDOMKeyEvent::DOM_VK_LEFT;
+ case KEY_NAME_INDEX_ArrowUp:
+ return nsIDOMKeyEvent::DOM_VK_UP;
+ case KEY_NAME_INDEX_ArrowRight:
+ return nsIDOMKeyEvent::DOM_VK_RIGHT;
+ case KEY_NAME_INDEX_ArrowDown:
+ return nsIDOMKeyEvent::DOM_VK_DOWN;
+ case KEY_NAME_INDEX_Select:
+ return nsIDOMKeyEvent::DOM_VK_SELECT;
+ case KEY_NAME_INDEX_Print:
+ return nsIDOMKeyEvent::DOM_VK_PRINT;
+ case KEY_NAME_INDEX_Execute:
+ return nsIDOMKeyEvent::DOM_VK_EXECUTE;
+ case KEY_NAME_INDEX_PrintScreen:
+ return nsIDOMKeyEvent::DOM_VK_PRINTSCREEN;
+ case KEY_NAME_INDEX_Insert:
+ return nsIDOMKeyEvent::DOM_VK_INSERT;
+ case KEY_NAME_INDEX_Delete:
+ return nsIDOMKeyEvent::DOM_VK_DELETE;
+ case KEY_NAME_INDEX_OS:
+ // case KEY_NAME_INDEX_Super:
+ // case KEY_NAME_INDEX_Hyper:
+ return nsIDOMKeyEvent::DOM_VK_WIN;
+ case KEY_NAME_INDEX_ContextMenu:
+ return nsIDOMKeyEvent::DOM_VK_CONTEXT_MENU;
+ case KEY_NAME_INDEX_Standby:
+ return nsIDOMKeyEvent::DOM_VK_SLEEP;
+ case KEY_NAME_INDEX_F1:
+ return nsIDOMKeyEvent::DOM_VK_F1;
+ case KEY_NAME_INDEX_F2:
+ return nsIDOMKeyEvent::DOM_VK_F2;
+ case KEY_NAME_INDEX_F3:
+ return nsIDOMKeyEvent::DOM_VK_F3;
+ case KEY_NAME_INDEX_F4:
+ return nsIDOMKeyEvent::DOM_VK_F4;
+ case KEY_NAME_INDEX_F5:
+ return nsIDOMKeyEvent::DOM_VK_F5;
+ case KEY_NAME_INDEX_F6:
+ return nsIDOMKeyEvent::DOM_VK_F6;
+ case KEY_NAME_INDEX_F7:
+ return nsIDOMKeyEvent::DOM_VK_F7;
+ case KEY_NAME_INDEX_F8:
+ return nsIDOMKeyEvent::DOM_VK_F8;
+ case KEY_NAME_INDEX_F9:
+ return nsIDOMKeyEvent::DOM_VK_F9;
+ case KEY_NAME_INDEX_F10:
+ return nsIDOMKeyEvent::DOM_VK_F10;
+ case KEY_NAME_INDEX_F11:
+ return nsIDOMKeyEvent::DOM_VK_F11;
+ case KEY_NAME_INDEX_F12:
+ return nsIDOMKeyEvent::DOM_VK_F12;
+ case KEY_NAME_INDEX_F13:
+ return nsIDOMKeyEvent::DOM_VK_F13;
+ case KEY_NAME_INDEX_F14:
+ return nsIDOMKeyEvent::DOM_VK_F14;
+ case KEY_NAME_INDEX_F15:
+ return nsIDOMKeyEvent::DOM_VK_F15;
+ case KEY_NAME_INDEX_F16:
+ return nsIDOMKeyEvent::DOM_VK_F16;
+ case KEY_NAME_INDEX_F17:
+ return nsIDOMKeyEvent::DOM_VK_F17;
+ case KEY_NAME_INDEX_F18:
+ return nsIDOMKeyEvent::DOM_VK_F18;
+ case KEY_NAME_INDEX_F19:
+ return nsIDOMKeyEvent::DOM_VK_F19;
+ case KEY_NAME_INDEX_F20:
+ return nsIDOMKeyEvent::DOM_VK_F20;
+ case KEY_NAME_INDEX_F21:
+ return nsIDOMKeyEvent::DOM_VK_F21;
+ case KEY_NAME_INDEX_F22:
+ return nsIDOMKeyEvent::DOM_VK_F22;
+ case KEY_NAME_INDEX_F23:
+ return nsIDOMKeyEvent::DOM_VK_F23;
+ case KEY_NAME_INDEX_F24:
+ return nsIDOMKeyEvent::DOM_VK_F24;
+ case KEY_NAME_INDEX_NumLock:
+ return nsIDOMKeyEvent::DOM_VK_NUM_LOCK;
+ case KEY_NAME_INDEX_ScrollLock:
+ return nsIDOMKeyEvent::DOM_VK_SCROLL_LOCK;
+ case KEY_NAME_INDEX_AudioVolumeMute:
+ return nsIDOMKeyEvent::DOM_VK_VOLUME_MUTE;
+ case KEY_NAME_INDEX_AudioVolumeDown:
+ return nsIDOMKeyEvent::DOM_VK_VOLUME_DOWN;
+ case KEY_NAME_INDEX_AudioVolumeUp:
+ return nsIDOMKeyEvent::DOM_VK_VOLUME_UP;
+ case KEY_NAME_INDEX_Meta:
+ return nsIDOMKeyEvent::DOM_VK_META;
+ case KEY_NAME_INDEX_AltGraph:
+ return nsIDOMKeyEvent::DOM_VK_ALTGR;
+ case KEY_NAME_INDEX_Attn:
+ return nsIDOMKeyEvent::DOM_VK_ATTN;
+ case KEY_NAME_INDEX_CrSel:
+ return nsIDOMKeyEvent::DOM_VK_CRSEL;
+ case KEY_NAME_INDEX_ExSel:
+ return nsIDOMKeyEvent::DOM_VK_EXSEL;
+ case KEY_NAME_INDEX_EraseEof:
+ return nsIDOMKeyEvent::DOM_VK_EREOF;
+ case KEY_NAME_INDEX_Play:
+ return nsIDOMKeyEvent::DOM_VK_PLAY;
+ case KEY_NAME_INDEX_ZoomToggle:
+ case KEY_NAME_INDEX_ZoomIn:
+ case KEY_NAME_INDEX_ZoomOut:
+ return nsIDOMKeyEvent::DOM_VK_ZOOM;
+ default:
+ return 0;
+ }
+}
+
+/* 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;
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/WidgetMessageUtils.h b/widget/WidgetMessageUtils.h
new file mode 100644
index 000000000..8ab831a20
--- /dev/null
+++ b/widget/WidgetMessageUtils.h
@@ -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/. */
+
+#ifndef mozilla_WidgetMessageUtils_h
+#define mozilla_WidgetMessageUtils_h
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/LookAndFeel.h"
+
+namespace IPC {
+
+template<>
+struct ParamTraits<LookAndFeelInt>
+{
+ typedef LookAndFeelInt paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.id);
+ WriteParam(aMsg, aParam.value);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ int32_t id, value;
+ if (ReadParam(aMsg, aIter, &id) &&
+ ReadParam(aMsg, aIter, &value)) {
+ aResult->id = id;
+ aResult->value = value;
+ return true;
+ }
+ return false;
+ }
+};
+
+} // namespace IPC
+
+#endif // WidgetMessageUtils_h
diff --git a/widget/WidgetTraceEvent.h b/widget/WidgetTraceEvent.h
new file mode 100644
index 000000000..d6e8f2c97
--- /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 000000000..4352f837c
--- /dev/null
+++ b/widget/WidgetUtils.cpp
@@ -0,0 +1,141 @@
+/* -*- 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/Unused.h"
+#include "nsContentUtils.h"
+#include "nsIBidiKeyboard.h"
+#include "nsTArray.h"
+#ifdef XP_WIN
+#include "WinUtils.h"
+#endif
+#if MOZ_WIDGET_GTK == 3
+#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.x - aRect.width,
+ aRect.height, aRect.width);
+ case ROTATION_180:
+ return nsIntRect(aBounds.width - aRect.x - aRect.width,
+ aBounds.height - aRect.y - aRect.height,
+ aRect.width, aRect.height);
+ case ROTATION_270:
+ return nsIntRect(aBounds.height - aRect.y - aRect.height,
+ aRect.x,
+ aRect.height, aRect.width);
+ default:
+ MOZ_CRASH("Unknown rotation");
+ }
+}
+
+namespace widget {
+
+uint32_t
+WidgetUtils::IsTouchDeviceSupportPresent()
+{
+#ifdef XP_WIN
+ return WinUtils::IsTouchDeviceSupportPresent();
+#elif MOZ_WIDGET_GTK == 3
+ 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);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/WidgetUtils.h b/widget/WidgetUtils.h
new file mode 100644
index 000000000..c61873e47
--- /dev/null
+++ b/widget/WidgetUtils.h
@@ -0,0 +1,99 @@
+/* -*- 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/EventForwards.h"
+#include "mozilla/gfx/Matrix.h"
+#include "nsCOMPtr.h"
+#include "nsIWidget.h"
+#include "nsRect.h"
+
+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();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_WidgetUtils_h
diff --git a/widget/WindowSurface.h b/widget/WindowSurface.h
new file mode 100644
index 000000000..3c6ed923e
--- /dev/null
+++ b/widget/WindowSurface.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _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() {}
+
+ // 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 000000000..37834756f
--- /dev/null
+++ b/widget/WindowSurfaceX11SHM.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowSurfaceX11SHM.h"
+
+namespace mozilla {
+namespace 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 widget
+} // namespace mozilla
diff --git a/widget/WindowSurfaceX11SHM.h b/widget/WindowSurfaceX11SHM.h
new file mode 100644
index 000000000..323f99e3b
--- /dev/null
+++ b/widget/WindowSurfaceX11SHM.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _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/ANRReporter.cpp b/widget/android/ANRReporter.cpp
new file mode 100644
index 000000000..30d9b3d76
--- /dev/null
+++ b/widget/android/ANRReporter.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "ANRReporter.h"
+#include "GeckoProfiler.h"
+
+#include <unistd.h>
+
+namespace mozilla {
+
+bool
+ANRReporter::RequestNativeStack(bool aUnwind)
+{
+ if (profiler_is_active()) {
+ // Don't proceed if profiler is already running
+ return false;
+ }
+ // WARNING: we are on the ANR reporter thread at this point and it is
+ // generally unsafe to use the profiler from off the main thread. However,
+ // the risk here is limited because for most users, the profiler is not run
+ // elsewhere. See the discussion in Bug 863777, comment 13
+ const char *NATIVE_STACK_FEATURES[] =
+ {"leaf", "threads", "privacy"};
+ const char *NATIVE_STACK_UNWIND_FEATURES[] =
+ {"leaf", "threads", "privacy", "stackwalk"};
+
+ const char **features = NATIVE_STACK_FEATURES;
+ size_t features_size = sizeof(NATIVE_STACK_FEATURES);
+ if (aUnwind) {
+ features = NATIVE_STACK_UNWIND_FEATURES;
+ features_size = sizeof(NATIVE_STACK_UNWIND_FEATURES);
+ // We want the new unwinder if the unwind mode has not been set yet
+ putenv("MOZ_PROFILER_NEW=1");
+ }
+
+ const char *NATIVE_STACK_THREADS[] =
+ {"GeckoMain", "Compositor"};
+ // Buffer one sample and let the profiler wait a long time
+ profiler_start(100, 10000, features, features_size / sizeof(char*),
+ NATIVE_STACK_THREADS, sizeof(NATIVE_STACK_THREADS) / sizeof(char*));
+ return true;
+}
+
+jni::String::LocalRef
+ANRReporter::GetNativeStack()
+{
+ if (!profiler_is_active()) {
+ // Maybe profiler support is disabled?
+ return nullptr;
+ }
+
+ // Timeout if we don't get a profiler sample after 5 seconds.
+ const PRIntervalTime timeout = PR_SecondsToInterval(5);
+ const PRIntervalTime startTime = PR_IntervalNow();
+
+ // Pointer to a profile JSON string
+ typedef mozilla::UniquePtr<char[]> ProfilePtr;
+
+ ProfilePtr profile(profiler_get_profile());
+
+ while (profile && !strstr(profile.get(), "\"samples\":[{")) {
+ // no sample yet?
+ if (PR_IntervalNow() - startTime >= timeout) {
+ return nullptr;
+ }
+ usleep(100000ul); // Sleep for 100ms
+ profile = ProfilePtr(profiler_get_profile());
+ }
+
+ if (profile) {
+ return jni::String::Param(profile.get());
+ }
+ return nullptr;
+}
+
+void
+ANRReporter::ReleaseNativeStack()
+{
+ if (!profiler_is_active()) {
+ // Maybe profiler support is disabled?
+ return;
+ }
+ profiler_stop();
+}
+
+} // namespace
+
diff --git a/widget/android/ANRReporter.h b/widget/android/ANRReporter.h
new file mode 100644
index 000000000..d3e5cc7e8
--- /dev/null
+++ b/widget/android/ANRReporter.h
@@ -0,0 +1,26 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 ANRReporter_h__
+#define ANRReporter_h__
+
+#include "FennecJNINatives.h"
+
+namespace mozilla {
+
+class ANRReporter : public java::ANRReporter::Natives<ANRReporter>
+{
+private:
+ ANRReporter();
+
+public:
+ static bool RequestNativeStack(bool aUnwind);
+ static jni::String::LocalRef GetNativeStack();
+ static void ReleaseNativeStack();
+};
+
+} // namespace
+
+#endif // ANRReporter_h__
diff --git a/widget/android/AndroidAlerts.cpp b/widget/android/AndroidAlerts.cpp
new file mode 100644
index 000000000..8d5074672
--- /dev/null
+++ b/widget/android/AndroidAlerts.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: c++; tab-width: 40; 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 "AndroidAlerts.h"
+#include "GeneratedJNIWrappers.h"
+#include "nsAlertsUtils.h"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(AndroidAlerts, nsIAlertsService)
+
+StaticAutoPtr<AndroidAlerts::ListenerMap> AndroidAlerts::sListenerMap;
+
+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(EmptyString(), 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);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = aAlert->GetPrincipal(getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsAutoString host;
+ nsAlertsUtils::GetSourceHostPort(principal, host);
+
+ if (aPersistentData.IsEmpty() && aAlertListener) {
+ if (!sListenerMap) {
+ sListenerMap = new ListenerMap();
+ }
+ // This will remove any observers already registered for this name.
+ sListenerMap->Put(name, aAlertListener);
+ }
+
+ java::GeckoAppShell::ShowNotification(
+ name, cookie, title, text, host, imageUrl,
+ !aPersistentData.IsEmpty() ? jni::StringParam(aPersistentData)
+ : jni::StringParam(nullptr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidAlerts::CloseAlert(const nsAString& aAlertName,
+ nsIPrincipal* aPrincipal)
+{
+ // We delete the entry in sListenerMap later, when CloseNotification calls
+ // NotifyListener.
+ java::GeckoAppShell::CloseNotification(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 (NS_LITERAL_CSTRING("alertfinished").Equals(aTopic)) {
+ sListenerMap->Remove(aName);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/AndroidAlerts.h b/widget/android/AndroidAlerts.h
new file mode 100644
index 000000000..16af15ce0
--- /dev/null
+++ b/widget/android/AndroidAlerts.h
@@ -0,0 +1,44 @@
+/* -*- Mode: c++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_AndroidAlerts_h__
+#define mozilla_widget_AndroidAlerts_h__
+
+#include "nsInterfaceHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIAlertsService.h"
+#include "nsIObserver.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);
+
+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 000000000..dd2cce39a
--- /dev/null
+++ b/widget/android/AndroidBridge.cpp
@@ -0,0 +1,1126 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "nsXPCOMStrings.h"
+#include "AndroidBridge.h"
+#include "AndroidJNIWrapper.h"
+#include "AndroidBridgeUtilities.h"
+#include "nsAlertsUtils.h"
+#include "nsAppShell.h"
+#include "nsOSHelperAppService.h"
+#include "nsWindow.h"
+#include "mozilla/Preferences.h"
+#include "nsThreadUtils.h"
+#include "nsIThreadManager.h"
+#include "gfxPlatform.h"
+#include "gfxContext.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxUtils.h"
+#include "nsPresContext.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/ScreenOrientation.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDOMClientRect.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsPrintfCString.h"
+#include "NativeJSContainer.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+#include "nsIHttpChannel.h"
+
+#include "MediaCodec.h"
+#include "SurfaceTexture.h"
+#include "GLContextProvider.h"
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsIObserverService.h"
+#include "nsISupportsPrimitives.h"
+#include "MediaPrefs.h"
+#include "WidgetUtils.h"
+
+#include "FennecJNIWrappers.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::jni;
+using namespace mozilla::java;
+
+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("NSS_DISABLE_UNLOAD=1");
+
+ MOZ_ASSERT(!sBridge);
+ sBridge = new AndroidBridge();
+
+ MediaPrefs::GetSingleton();
+}
+
+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()
+ : mUiTaskQueueLock("UiTaskQueue")
+{
+ ALOG_BRIDGE("AndroidBridge::Init");
+
+ JNIEnv* const jEnv = jni::GetGeckoThreadEnv();
+ AutoLocalJNIFrame jniFrame(jEnv);
+
+ mMessageQueue = java::GeckoThread::MsgQueue();
+ auto msgQueueClass = 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();
+
+ if (!GetStaticIntField("android/os/Build$VERSION", "SDK_INT", &mAPIVersion, jEnv)) {
+ ALOG_BRIDGE("Failed to find API version");
+ }
+
+ 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");
+}
+
+// Raw JNIEnv variants.
+jstring AndroidBridge::NewJavaString(JNIEnv* env, const char16_t* string, uint32_t len) {
+ jstring ret = env->NewString(reinterpret_cast<const jchar*>(string), len);
+ if (env->ExceptionCheck()) {
+ ALOG_BRIDGE("Exceptional exit of: %s", __PRETTY_FUNCTION__);
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return nullptr;
+ }
+ return ret;
+}
+
+jstring AndroidBridge::NewJavaString(JNIEnv* env, const nsAString& string) {
+ return NewJavaString(env, string.BeginReading(), string.Length());
+}
+
+jstring AndroidBridge::NewJavaString(JNIEnv* env, const char* string) {
+ return NewJavaString(env, NS_ConvertUTF8toUTF16(string));
+}
+
+jstring AndroidBridge::NewJavaString(JNIEnv* env, const nsACString& string) {
+ return NewJavaString(env, NS_ConvertUTF8toUTF16(string));
+}
+
+// AutoLocalJNIFrame variants..
+jstring AndroidBridge::NewJavaString(AutoLocalJNIFrame* frame, const char16_t* string, uint32_t len) {
+ return NewJavaString(frame->GetEnv(), string, len);
+}
+
+jstring AndroidBridge::NewJavaString(AutoLocalJNIFrame* frame, const nsAString& string) {
+ return NewJavaString(frame, string.BeginReading(), string.Length());
+}
+
+jstring AndroidBridge::NewJavaString(AutoLocalJNIFrame* frame, const char* string) {
+ return NewJavaString(frame, NS_ConvertUTF8toUTF16(string));
+}
+
+jstring AndroidBridge::NewJavaString(AutoLocalJNIFrame* frame, const nsACString& string) {
+ return NewJavaString(frame, NS_ConvertUTF8toUTF16(string));
+}
+
+static void
+getHandlersFromStringArray(JNIEnv *aJNIEnv, jobjectArray jArr, jsize aLen,
+ nsIMutableArray *aHandlersArray,
+ nsIHandlerApp **aDefaultApp,
+ const nsAString& aAction = EmptyString(),
+ const nsACString& aMimeType = EmptyCString())
+{
+ nsString empty = EmptyString();
+ for (jsize i = 0; i < aLen; i+=4) {
+
+ AutoLocalJNIFrame jniFrame(aJNIEnv, 4);
+ nsJNIString name(
+ static_cast<jstring>(aJNIEnv->GetObjectArrayElement(jArr, i)), aJNIEnv);
+ nsJNIString isDefault(
+ static_cast<jstring>(aJNIEnv->GetObjectArrayElement(jArr, i + 1)), aJNIEnv);
+ nsJNIString packageName(
+ static_cast<jstring>(aJNIEnv->GetObjectArrayElement(jArr, i + 2)), aJNIEnv);
+ nsJNIString className(
+ static_cast<jstring>(aJNIEnv->GetObjectArrayElement(jArr, i + 3)), aJNIEnv);
+ nsIHandlerApp* app = nsOSHelperAppService::
+ CreateAndroidHandlerApp(name, className, packageName,
+ className, aMimeType, aAction);
+
+ aHandlersArray->AppendElement(app, false);
+ 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 = GeckoAppShell::GetHandlersForMimeType(aMimeType, aAction);
+ if (!arr)
+ return false;
+
+ JNIEnv* const env = arr.Env();
+ jsize len = env->GetArrayLength(arr.Get());
+
+ if (!aHandlersArray)
+ return len > 0;
+
+ getHandlersFromStringArray(env, arr.Get(), len, aHandlersArray,
+ aDefaultApp, aAction,
+ NS_ConvertUTF16toUTF8(aMimeType));
+ return true;
+}
+
+bool
+AndroidBridge::GetHWEncoderCapability()
+{
+ ALOG_BRIDGE("AndroidBridge::GetHWEncoderCapability");
+
+ bool value = GeckoAppShell::GetHWEncoderCapability();
+
+ return value;
+}
+
+
+bool
+AndroidBridge::GetHWDecoderCapability()
+{
+ ALOG_BRIDGE("AndroidBridge::GetHWDecoderCapability");
+
+ bool value = GeckoAppShell::GetHWDecoderCapability();
+
+ return value;
+}
+
+bool
+AndroidBridge::GetHandlersForURL(const nsAString& aURL,
+ nsIMutableArray* aHandlersArray,
+ nsIHandlerApp **aDefaultApp,
+ const nsAString& aAction)
+{
+ ALOG_BRIDGE("AndroidBridge::GetHandlersForURL");
+
+ auto arr = GeckoAppShell::GetHandlersForURL(aURL, aAction);
+ if (!arr)
+ return false;
+
+ JNIEnv* const env = arr.Env();
+ jsize len = env->GetArrayLength(arr.Get());
+
+ if (!aHandlersArray)
+ return len > 0;
+
+ getHandlersFromStringArray(env, arr.Get(), len, aHandlersArray,
+ aDefaultApp, aAction);
+ return true;
+}
+
+void
+AndroidBridge::GetMimeTypeFromExtensions(const nsACString& aFileExt, nsCString& aMimeType)
+{
+ ALOG_BRIDGE("AndroidBridge::GetMimeTypeFromExtensions");
+
+ auto jstrType = GeckoAppShell::GetMimeTypeFromExtensions(aFileExt);
+
+ if (jstrType) {
+ aMimeType = jstrType->ToCString();
+ }
+}
+
+void
+AndroidBridge::GetExtensionFromMimeType(const nsACString& aMimeType, nsACString& aFileExt)
+{
+ ALOG_BRIDGE("AndroidBridge::GetExtensionFromMimeType");
+
+ auto jstrExt = GeckoAppShell::GetExtensionFromMimeType(aMimeType);
+
+ if (jstrExt) {
+ aFileExt = jstrExt->ToCString();
+ }
+}
+
+bool
+AndroidBridge::GetClipboardText(nsAString& aText)
+{
+ ALOG_BRIDGE("AndroidBridge::GetClipboardText");
+
+ auto text = Clipboard::GetText();
+
+ if (text) {
+ aText = text->ToString();
+ }
+ return !!text;
+}
+
+int
+AndroidBridge::GetDPI()
+{
+ static int sDPI = 0;
+ if (sDPI)
+ return sDPI;
+
+ const int DEFAULT_DPI = 160;
+
+ sDPI = GeckoAppShell::GetDpi();
+ if (!sDPI) {
+ return DEFAULT_DPI;
+ }
+
+ return sDPI;
+}
+
+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 = 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;
+ }
+ 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);
+
+ GeckoAppShell::Vibrate(LongArray::Ref::From(array), -1 /* don't repeat */);
+}
+
+void
+AndroidBridge::GetSystemColors(AndroidSystemColors *aColors)
+{
+
+ NS_ASSERTION(aColors != nullptr, "AndroidBridge::GetSystemColors: aColors is null!");
+ if (!aColors)
+ return;
+
+ auto arr = GeckoAppShell::GetSystemColors();
+ if (!arr)
+ return;
+
+ 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*)aColors;
+
+ 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);
+}
+
+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 = 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(nsJNIString(jstr, jEnv));
+ return true;
+}
+
+namespace mozilla {
+ class TracerRunnable : public Runnable{
+ public:
+ 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();
+ }
+
+}
+
+
+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 = 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::HandleGeckoMessage(JSContext* cx, JS::HandleObject object)
+{
+ ALOG_BRIDGE("%s", __PRETTY_FUNCTION__);
+
+ auto message = widget::CreateNativeJSContainer(cx, object);
+ GeckoAppShell::HandleGeckoMessage(message);
+}
+
+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 = 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() {
+ if (sGlobalContext) {
+ return sGlobalContext;
+ }
+
+ JNIEnv* const env = GetEnvForThread();
+ AutoLocalJNIFrame jniFrame(env, 4);
+
+ auto context = GeckoAppShell::GetContext();
+ if (!context) {
+ ALOG_BRIDGE("%s: Could not GetContext()", __FUNCTION__);
+ return 0;
+ }
+ jclass contextClass = env->FindClass("android/content/Context");
+ if (!contextClass) {
+ ALOG_BRIDGE("%s: Could not find Context class.", __FUNCTION__);
+ return 0;
+ }
+ jmethodID mid = env->GetMethodID(contextClass, "getApplicationContext",
+ "()Landroid/content/Context;");
+ if (!mid) {
+ ALOG_BRIDGE("%s: Could not find getApplicationContext.", __FUNCTION__);
+ return 0;
+ }
+ jobject appContext = env->CallObjectMethod(context.Get(), mid);
+ if (!appContext) {
+ ALOG_BRIDGE("%s: getApplicationContext failed.", __FUNCTION__);
+ return 0;
+ }
+
+ sGlobalContext = env->NewGlobalRef(appContext);
+ MOZ_ASSERT(sGlobalContext);
+ return sGlobalContext;
+}
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsAndroidBridge, nsIAndroidBridge)
+
+nsAndroidBridge::nsAndroidBridge()
+{
+ AddObservers();
+}
+
+nsAndroidBridge::~nsAndroidBridge()
+{
+ RemoveObservers();
+}
+
+NS_IMETHODIMP nsAndroidBridge::HandleGeckoMessage(JS::HandleValue val,
+ JSContext *cx)
+{
+ if (val.isObject()) {
+ JS::RootedObject object(cx, &val.toObject());
+ AndroidBridge::Bridge()->HandleGeckoMessage(cx, object);
+ return NS_OK;
+ }
+
+ // Now handle legacy JSON messages.
+ if (!val.isString()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ JS::RootedString jsonStr(cx, val.toString());
+
+ JS::RootedValue jsonVal(cx);
+ if (!JS_ParseJSON(cx, jsonStr, &jsonVal) || !jsonVal.isObject()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Spit out a warning before sending the message.
+ nsContentUtils::ReportToConsoleNonLocalized(
+ NS_LITERAL_STRING("Use of JSON is deprecated. "
+ "Please pass Javascript objects directly to handleGeckoMessage."),
+ nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("nsIAndroidBridge"),
+ nullptr);
+
+ JS::RootedObject object(cx, &jsonVal.toObject());
+ AndroidBridge::Bridge()->HandleGeckoMessage(cx, object);
+ return NS_OK;
+}
+
+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;
+}
+
+NS_IMETHODIMP
+nsAndroidBridge::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ RemoveObservers();
+ } else if (!strcmp(aTopic, "media-playback")) {
+ ALOG_BRIDGE("nsAndroidBridge::Observe, get media-playback event.");
+
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ if (!wrapper) {
+ return NS_OK;
+ }
+
+ uint64_t windowId = 0;
+ nsresult rv = wrapper->GetData(&windowId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString activeStr(aData);
+ bool isPlaying = activeStr.EqualsLiteral("active");
+ UpdateAudioPlayingWindows(windowId, isPlaying);
+ }
+ return NS_OK;
+}
+
+void
+nsAndroidBridge::UpdateAudioPlayingWindows(uint64_t aWindowId,
+ bool aPlaying)
+{
+ // Request audio focus for the first audio playing window and abandon focus
+ // for the last audio playing window.
+ if (aPlaying && !mAudioPlayingWindows.Contains(aWindowId)) {
+ mAudioPlayingWindows.AppendElement(aWindowId);
+ if (mAudioPlayingWindows.Length() == 1) {
+ ALOG_BRIDGE("nsAndroidBridge, request audio focus.");
+ AudioFocusAgent::NotifyStartedPlaying();
+ }
+ } else if (!aPlaying && mAudioPlayingWindows.Contains(aWindowId)) {
+ mAudioPlayingWindows.RemoveElement(aWindowId);
+ if (mAudioPlayingWindows.Length() == 0) {
+ ALOG_BRIDGE("nsAndroidBridge, abandon audio focus.");
+ AudioFocusAgent::NotifyStoppedPlaying();
+ }
+ }
+}
+
+void
+nsAndroidBridge::AddObservers()
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ if (jni::IsFennec()) { // No AudioFocusAgent in non-Fennec environment.
+ obs->AddObserver(this, "media-playback", false);
+ }
+ }
+}
+
+void
+nsAndroidBridge::RemoveObservers()
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ if (jni::IsFennec()) { // No AudioFocusAgent in non-Fennec environment.
+ obs->RemoveObserver(this, "media-playback");
+ }
+ }
+}
+
+uint32_t
+AndroidBridge::GetScreenOrientation()
+{
+ ALOG_BRIDGE("AndroidBridge::GetScreenOrientation");
+
+ int16_t orientation = GeckoAppShell::GetScreenOrientation();
+
+ if (!orientation)
+ return dom::eScreenOrientation_None;
+
+ return static_cast<dom::ScreenOrientationInternal>(orientation);
+}
+
+uint16_t
+AndroidBridge::GetScreenAngle()
+{
+ return 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 = 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 = 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 = Object::LocalRef::Adopt(
+ env, env->CallObjectMethod(mMessageQueue.Get(), mMessageQueueNext));
+ if (!msg) {
+ return false;
+ }
+
+ return GeckoThread::PumpMessageLoop(msg);
+}
+
+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);
+
+static jni::DependentRef<java::GeckoLayerClient>
+GetJavaLayerClient(mozIDOMWindowProxy* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = nsPIDOMWindowOuter::From(aWindow);
+ nsCOMPtr<nsIWidget> widget =
+ widget::WidgetUtils::DOMWindowToWidget(domWindow);
+ MOZ_ASSERT(widget);
+
+ return static_cast<nsWindow*>(widget.get())->GetLayerClient();
+}
+
+void
+AndroidBridge::ContentDocumentChanged(mozIDOMWindowProxy* aWindow)
+{
+ auto layerClient = GetJavaLayerClient(aWindow);
+ if (!layerClient) {
+ return;
+ }
+ layerClient->ContentDocumentChanged();
+}
+
+bool
+AndroidBridge::IsContentDocumentDisplayed(mozIDOMWindowProxy* aWindow)
+{
+ auto layerClient = GetJavaLayerClient(aWindow);
+ if (!layerClient) {
+ return false;
+ }
+ return layerClient->IsContentDocumentDisplayed();
+}
+
+class AndroidBridge::DelayedTask
+{
+ using TimeStamp = mozilla::TimeStamp;
+ using TimeDuration = mozilla::TimeDuration;
+
+public:
+ DelayedTask(already_AddRefed<Runnable> aTask)
+ : mTask(aTask)
+ , mRunTime() // Null timestamp representing no delay.
+ {}
+
+ DelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs)
+ : mTask(aTask)
+ , mRunTime(TimeStamp::Now() + TimeDuration::FromMilliseconds(aDelayMs))
+ {}
+
+ bool IsEarlierThan(const DelayedTask& 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<Runnable> TakeTask()
+ {
+ return mTask.forget();
+ }
+
+private:
+ RefPtr<Runnable> mTask;
+ const TimeStamp mRunTime;
+};
+
+
+void
+AndroidBridge::PostTaskToUiThread(already_AddRefed<Runnable> aTask, int aDelayMs)
+{
+ // add the new task into the mUiTaskQueue, sorted with
+ // the earliest task first in the queue
+ size_t i;
+ DelayedTask newTask(aDelayMs ? DelayedTask(mozilla::Move(aTask), aDelayMs)
+ : DelayedTask(mozilla::Move(aTask)));
+
+ {
+ MutexAutoLock lock(mUiTaskQueueLock);
+
+ for (i = 0; i < mUiTaskQueue.Length(); i++) {
+ if (newTask.IsEarlierThan(mUiTaskQueue[i])) {
+ mUiTaskQueue.InsertElementAt(i, mozilla::Move(newTask));
+ break;
+ }
+ }
+
+ if (i == mUiTaskQueue.Length()) {
+ // We didn't insert the task, which means we should append it.
+ mUiTaskQueue.AppendElement(mozilla::Move(newTask));
+ }
+ }
+
+ if (i == 0) {
+ // 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
+ GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs));
+ }
+}
+
+int64_t
+AndroidBridge::RunDelayedUiThreadTasks()
+{
+ MutexAutoLock lock(mUiTaskQueueLock);
+
+ while (!mUiTaskQueue.IsEmpty()) {
+ const int64_t timeLeft = mUiTaskQueue[0].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.
+ RefPtr<Runnable> nextTask(mUiTaskQueue[0].TakeTask());
+ mUiTaskQueue.RemoveElementAt(0);
+
+ // Unlock to allow posting new tasks reentrantly.
+ MutexAutoUnlock unlock(mUiTaskQueueLock);
+ nextTask->Run();
+ }
+ return -1;
+}
+
+Object::LocalRef AndroidBridge::ChannelCreate(Object::Param stream) {
+ JNIEnv* const env = GetEnvForThread();
+ auto rv = Object::LocalRef::Adopt(env, env->CallStaticObjectMethod(
+ sBridge->jChannels, sBridge->jChannelCreate, stream.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return rv;
+}
+
+void AndroidBridge::InputStreamClose(Object::Param obj) {
+ JNIEnv* const env = GetEnvForThread();
+ env->CallVoidMethod(obj.Get(), sBridge->jClose);
+ MOZ_CATCH_JNI_EXCEPTION(env);
+}
+
+uint32_t AndroidBridge::InputStreamAvailable(Object::Param obj) {
+ JNIEnv* const env = GetEnvForThread();
+ auto rv = env->CallIntMethod(obj.Get(), sBridge->jAvailable);
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return rv;
+}
+
+nsresult AndroidBridge::InputStreamRead(Object::Param obj, char *aBuf, uint32_t aCount, uint32_t *aRead) {
+ JNIEnv* const env = GetEnvForThread();
+ auto arr = 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 000000000..73dc1b5ff
--- /dev/null
+++ b/widget/android/AndroidBridge.h
@@ -0,0 +1,419 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 <pthread.h>
+
+#include "APKOpen.h"
+
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+
+#include "GeneratedJNIWrappers.h"
+
+#include "nsIMutableArray.h"
+#include "nsIMIMEInfo.h"
+#include "nsColor.h"
+#include "gfxRect.h"
+
+#include "nsIAndroidBridge.h"
+#include "nsIDOMDOMCursor.h"
+
+#include "mozilla/Likely.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Types.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/jni/Utils.h"
+#include "nsIObserver.h"
+#include "nsDataHashtable.h"
+
+#include "Units.h"
+
+// Some debug #defines
+// #define DEBUG_ANDROID_EVENTS
+// #define DEBUG_ANDROID_WIDGET
+
+class nsPIDOMWindowOuter;
+
+namespace base {
+class Thread;
+} // end namespace base
+
+typedef void* EGLSurface;
+
+namespace mozilla {
+
+class AutoLocalJNIFrame;
+class Runnable;
+
+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 MessageCursorContinueCallback : public nsICursorContinueCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICURSORCONTINUECALLBACK
+
+ MessageCursorContinueCallback(int aRequestId)
+ : mRequestId(aRequestId)
+ {
+ }
+private:
+ virtual ~MessageCursorContinueCallback()
+ {
+ }
+
+ int mRequestId;
+};
+
+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 pthread_equal(pthread_self(), ::getJavaUiThread());
+ }
+
+ 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 = EmptyString());
+
+ bool GetHandlersForMimeType(const nsAString& aMimeType,
+ nsIMutableArray* handlersArray = nullptr,
+ nsIHandlerApp **aDefaultApp = nullptr,
+ const nsAString& aAction = EmptyString());
+
+ bool GetHWEncoderCapability();
+ bool GetHWDecoderCapability();
+
+ void GetMimeTypeFromExtensions(const nsACString& aFileExt, nsCString& aMimeType);
+ void GetExtensionFromMimeType(const nsACString& aMimeType, nsACString& aFileExt);
+
+ bool GetClipboardText(nsAString& aText);
+
+ int GetDPI();
+ int GetScreenDepth();
+
+ void Vibrate(const nsTArray<uint32_t>& aPattern);
+
+ void GetSystemColors(AndroidSystemColors *aColors);
+
+ 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 HandleGeckoMessage(JSContext* cx, JS::HandleObject message);
+
+ 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 jstring NewJavaString(JNIEnv* env, const char16_t* string, uint32_t len);
+ static jstring NewJavaString(JNIEnv* env, const nsAString& string);
+ static jstring NewJavaString(JNIEnv* env, const char* string);
+ static jstring NewJavaString(JNIEnv* env, const nsACString& string);
+
+ static jstring NewJavaString(AutoLocalJNIFrame* frame, const char16_t* string, uint32_t len);
+ static jstring NewJavaString(AutoLocalJNIFrame* frame, const nsAString& string);
+ static jstring NewJavaString(AutoLocalJNIFrame* frame, const char* string);
+ static jstring NewJavaString(AutoLocalJNIFrame* frame, const nsACString& string);
+
+ 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;
+
+private:
+ class DelayedTask;
+ nsTArray<DelayedTask> mUiTaskQueue;
+ mozilla::Mutex mUiTaskQueueLock;
+
+public:
+ void PostTaskToUiThread(already_AddRefed<Runnable> aTask, int aDelayMs);
+ int64_t RunDelayedUiThreadTasks();
+};
+
+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:
+ 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:
+ AutoLocalJNIFrame(int nEntries = 15)
+ : mEntries(nEntries)
+ , mJNIEnv(jni::GetGeckoThreadEnv())
+ , mHasFrameBeenPushed(false)
+ {
+ MOZ_ASSERT(mJNIEnv);
+ Push();
+ }
+
+ 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;
+};
+
+}
+
+#define NS_ANDROIDBRIDGE_CID \
+{ 0x0FE2321D, 0xEBD9, 0x467D, \
+ { 0xA7, 0x43, 0x03, 0xA6, 0x8D, 0x40, 0x59, 0x9E } }
+
+class nsAndroidBridge final : public nsIAndroidBridge,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIANDROIDBRIDGE
+ NS_DECL_NSIOBSERVER
+
+ nsAndroidBridge();
+
+private:
+ ~nsAndroidBridge();
+
+ void AddObservers();
+ void RemoveObservers();
+
+ void UpdateAudioPlayingWindows(uint64_t aWindowId, bool aPlaying);
+
+ nsTArray<uint64_t> mAudioPlayingWindows;
+
+protected:
+};
+
+#endif /* AndroidBridge_h__ */
diff --git a/widget/android/AndroidBridgeUtilities.h b/widget/android/AndroidBridgeUtilities.h
new file mode 100644
index 000000000..ca8326281
--- /dev/null
+++ b/widget/android/AndroidBridgeUtilities.h
@@ -0,0 +1,13 @@
+#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/AndroidCompositorWidget.cpp b/widget/android/AndroidCompositorWidget.cpp
new file mode 100644
index 000000000..91cc08531
--- /dev/null
+++ b/widget/android/AndroidCompositorWidget.cpp
@@ -0,0 +1,63 @@
+/* -*- 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 {
+
+void
+AndroidCompositorWidget::SetFirstPaintViewport(const LayerIntPoint& aOffset,
+ const CSSToLayerScale& aZoom,
+ const CSSRect& aCssPageRect)
+{
+ auto layerClient = static_cast<nsWindow*>(RealWidget())->GetLayerClient();
+ if (!layerClient) {
+ return;
+ }
+
+ layerClient->SetFirstPaintViewport(
+ float(aOffset.x), float(aOffset.y), aZoom.scale, aCssPageRect.x,
+ aCssPageRect.y, aCssPageRect.XMost(), aCssPageRect.YMost());
+}
+
+void
+AndroidCompositorWidget::SyncFrameMetrics(const ParentLayerPoint& aScrollOffset,
+ const CSSToParentLayerScale& aZoom,
+ const CSSRect& aCssPageRect,
+ const CSSRect& aDisplayPort,
+ const CSSToLayerScale& aPaintedResolution,
+ bool aLayersUpdated,
+ int32_t aPaintSyncId,
+ ScreenMargin& aFixedLayerMargins)
+{
+ auto layerClient = static_cast<nsWindow*>(RealWidget())->GetLayerClient();
+ if (!layerClient) {
+ return;
+ }
+
+ // convert the displayport rect from document-relative CSS pixels to
+ // document-relative device pixels
+ LayerIntRect dp = gfx::RoundedToInt(aDisplayPort * aPaintedResolution);
+
+ java::ViewTransform::LocalRef viewTransform = layerClient->SyncFrameMetrics(
+ aScrollOffset.x, aScrollOffset.y, aZoom.scale,
+ aCssPageRect.x, aCssPageRect.y,
+ aCssPageRect.XMost(), aCssPageRect.YMost(),
+ dp.x, dp.y, dp.width, dp.height,
+ aPaintedResolution.scale, aLayersUpdated, aPaintSyncId);
+
+ MOZ_ASSERT(viewTransform, "No view transform object!");
+
+ aFixedLayerMargins.top = viewTransform->FixedLayerMarginTop();
+ aFixedLayerMargins.right = viewTransform->FixedLayerMarginRight();
+ aFixedLayerMargins.bottom = viewTransform->FixedLayerMarginBottom();
+ aFixedLayerMargins.left = viewTransform->FixedLayerMarginLeft();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/AndroidCompositorWidget.h b/widget/android/AndroidCompositorWidget.h
new file mode 100644
index 000000000..23076e30d
--- /dev/null
+++ b/widget/android/AndroidCompositorWidget.h
@@ -0,0 +1,44 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "mozilla/widget/InProcessCompositorWidget.h"
+
+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; }
+
+ void SetFirstPaintViewport(const LayerIntPoint& aOffset,
+ const CSSToLayerScale& aZoom,
+ const CSSRect& aCssPageRect);
+
+ void SyncFrameMetrics(const ParentLayerPoint& aScrollOffset,
+ const CSSToParentLayerScale& aZoom,
+ const CSSRect& aCssPageRect,
+ const CSSRect& aDisplayPort,
+ const CSSToLayerScale& aPaintedResolution,
+ bool aLayersUpdated,
+ int32_t aPaintSyncId,
+ ScreenMargin& aFixedLayerMargins);
+};
+
+} // 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 000000000..1df053afb
--- /dev/null
+++ b/widget/android/AndroidContentController.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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::NotifyDefaultPrevented(IAPZCTreeManager* aManager,
+ uint64_t aInputBlockId,
+ bool aDefaultPrevented)
+{
+ if (!AndroidBridge::IsJavaUiThread()) {
+ // The notification must reach the APZ on the Java UI thread (aka the
+ // APZ "controller" thread) but we get it from the Gecko thread, so we
+ // have to throw it onto the other thread.
+ AndroidBridge::Bridge()->PostTaskToUiThread(NewRunnableMethod<uint64_t, bool>(
+ aManager, &IAPZCTreeManager::ContentReceivedInputBlock,
+ aInputBlockId, aDefaultPrevented), 0);
+ return;
+ }
+
+ aManager->ContentReceivedInputBlock(aInputBlockId, aDefaultPrevented);
+}
+
+void
+AndroidContentController::DispatchSingleTapToObservers(const LayoutDevicePoint& aPoint,
+ const ScrollableLayerGuid& aGuid) const
+{
+ nsIContent* content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
+ nsPresContext* context = content
+ ? mozilla::layers::APZCCallbackHelper::GetPresContextForContent(content)
+ : nullptr;
+
+ if (!context) {
+ return;
+ }
+
+ CSSPoint point = mozilla::layers::APZCCallbackHelper::ApplyCallbackTransform(
+ aPoint / context->CSSToDevPixelScale(), aGuid);
+
+ nsPresContext* rcdContext = context->GetToplevelContentDocumentPresContext();
+ if (rcdContext && rcdContext->PresShell()->ScaleToResolution()) {
+ // We need to convert from the root document to the root content document,
+ // by unapplying the resolution that's on the content document.
+ const float resolution = rcdContext->PresShell()->GetResolution();
+ point.x /= resolution;
+ point.y /= resolution;
+ }
+
+ CSSIntPoint rounded = RoundedToInt(point);
+ nsAppShell::PostEvent([rounded] {
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ if (!obsServ) {
+ return;
+ }
+
+ nsPrintfCString data("{\"x\":%d,\"y\":%d}", rounded.x, rounded.y);
+ obsServ->NotifyObservers(nullptr, "Gesture:SingleTap",
+ NS_ConvertASCIItoUTF16(data).get());
+ });
+}
+
+void
+AndroidContentController::HandleTap(TapType aType, const LayoutDevicePoint& aPoint,
+ Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId)
+{
+ // This function will get invoked first on the Java UI thread, and then
+ // again on the main thread (because of the code in ChromeProcessController::
+ // HandleTap). We want to post the SingleTap message once; it can be
+ // done from either thread but we need access to the callback transform
+ // so we do it from the main thread.
+ if (NS_IsMainThread() &&
+ (aType == TapType::eSingleTap || aType == TapType::eSecondTap)) {
+ DispatchSingleTapToObservers(aPoint, aGuid);
+ }
+
+ ChromeProcessController::HandleTap(aType, aPoint, aModifiers, aGuid, aInputBlockId);
+}
+
+void
+AndroidContentController::PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs)
+{
+ AndroidBridge::Bridge()->PostTaskToUiThread(Move(aTask), aDelayMs);
+}
+void
+AndroidContentController::UpdateOverscrollVelocity(const float aX, const float aY, const bool aIsRootContent)
+{
+ if (aIsRootContent && mAndroidWindow) {
+ mAndroidWindow->UpdateOverscrollVelocity(aX, aY);
+ }
+}
+
+void
+AndroidContentController::UpdateOverscrollOffset(const float aX, const float aY, const bool aIsRootContent)
+{
+ if (aIsRootContent && mAndroidWindow) {
+ mAndroidWindow->UpdateOverscrollOffset(aX, aY);
+ }
+}
+
+void
+AndroidContentController::SetScrollingRootContent(const bool isRootContent)
+{
+ if (mAndroidWindow) {
+ mAndroidWindow->SetScrollingRootContent(isRootContent);
+ }
+}
+
+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 000000000..39674c939
--- /dev/null
+++ b/widget/android/AndroidContentController.h
@@ -0,0 +1,59 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "nsIDOMWindowUtils.h"
+#include "nsTArray.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace layers {
+class APZEventState;
+class IAPZCTreeManager;
+}
+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 HandleTap(TapType aType, const LayoutDevicePoint& aPoint, Modifiers aModifiers,
+ const ScrollableLayerGuid& aGuid, uint64_t aInputBlockId) override;
+ void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) override;
+ void UpdateOverscrollVelocity(const float aX, const float aY, const bool aIsRootContent) override;
+ void UpdateOverscrollOffset(const float aX, const float aY, const bool aIsRootContent) override;
+ void SetScrollingRootContent(const bool isRootContent) override;
+ void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
+ APZStateChange aChange,
+ int aArg) override;
+
+ static void NotifyDefaultPrevented(mozilla::layers::IAPZCTreeManager* aManager,
+ uint64_t aInputBlockId, bool aDefaultPrevented);
+private:
+ nsWindow* mAndroidWindow;
+
+ void DispatchSingleTapToObservers(const LayoutDevicePoint& aPoint,
+ const ScrollableLayerGuid& aGuid) const;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/android/AndroidDirectTexture.h b/widget/android/AndroidDirectTexture.h
new file mode 100644
index 000000000..8582b8582
--- /dev/null
+++ b/widget/android/AndroidDirectTexture.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AndroidDirectTexture_h_
+#define AndroidDirectTexture_h_
+
+#include "gfxTypes.h"
+#include "mozilla/Mutex.h"
+#include "AndroidGraphicBuffer.h"
+#include "nsRect.h"
+
+namespace mozilla {
+
+/**
+ * This is a thread safe wrapper around AndroidGraphicBuffer that handles
+ * double buffering. Each call to Bind() flips the buffer when necessary.
+ *
+ * You need to be careful when destroying an instance of this class. If either
+ * buffer is locked by the application of the driver/hardware, bad things will
+ * happen. Be sure that the OpenGL texture is no longer on the screen.
+ */
+class AndroidDirectTexture
+{
+public:
+ AndroidDirectTexture(uint32_t width, uint32_t height, uint32_t usage, gfxImageFormat format);
+ virtual ~AndroidDirectTexture();
+
+ bool Lock(uint32_t usage, unsigned char **bits);
+ bool Lock(uint32_t usage, const nsIntRect& rect, unsigned char **bits);
+ bool Unlock(bool aFlip = true);
+
+ bool Reallocate(uint32_t aWidth, uint32_t aHeight);
+ bool Reallocate(uint32_t aWidth, uint32_t aHeight, gfxImageFormat aFormat);
+
+ uint32_t Width() { return mWidth; }
+ uint32_t Height() { return mHeight; }
+
+ bool Bind();
+
+private:
+ mozilla::Mutex mLock;
+ bool mNeedFlip;
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+ gfxImageFormat mFormat;
+
+ AndroidGraphicBuffer* mFrontBuffer;
+ AndroidGraphicBuffer* mBackBuffer;
+
+ AndroidGraphicBuffer* mPendingReallocBuffer;
+ void ReallocPendingBuffer();
+};
+
+} /* mozilla */
+#endif /* AndroidDirectTexture_h_ */
diff --git a/widget/android/AndroidGraphicBuffer.h b/widget/android/AndroidGraphicBuffer.h
new file mode 100644
index 000000000..269f8680a
--- /dev/null
+++ b/widget/android/AndroidGraphicBuffer.h
@@ -0,0 +1,72 @@
+/* -*- 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 AndroidGraphicBuffer_h_
+#define AndroidGraphicBuffer_h_
+
+#include "gfxTypes.h"
+#include "nsRect.h"
+
+typedef void* EGLImageKHR;
+typedef void* EGLClientBuffer;
+
+namespace mozilla {
+
+/**
+ * This class allows access to Android's direct texturing mechanism. Locking
+ * the buffer gives you a pointer you can read/write to directly. It is fully
+ * threadsafe, but you probably really want to use the AndroidDirectTexture
+ * class which will handle double buffering.
+ *
+ * In order to use the buffer in OpenGL, just call Bind() and it will attach
+ * to whatever texture is bound to GL_TEXTURE_2D.
+ */
+class AndroidGraphicBuffer
+{
+public:
+ enum {
+ UsageSoftwareRead = 1,
+ UsageSoftwareWrite = 1 << 1,
+ UsageTexture = 1 << 2,
+ UsageTarget = 1 << 3,
+ Usage2D = 1 << 4
+ };
+
+ AndroidGraphicBuffer(uint32_t width, uint32_t height, uint32_t usage, gfxImageFormat format);
+ virtual ~AndroidGraphicBuffer();
+
+ int Lock(uint32_t usage, unsigned char **bits);
+ int Lock(uint32_t usage, const nsIntRect& rect, unsigned char **bits);
+ int Unlock();
+ bool Reallocate(uint32_t aWidth, uint32_t aHeight, gfxImageFormat aFormat);
+
+ uint32_t Width() { return mWidth; }
+ uint32_t Height() { return mHeight; }
+
+ bool Bind();
+
+ static bool IsBlacklisted();
+
+private:
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mUsage;
+ gfxImageFormat mFormat;
+
+ bool EnsureInitialized();
+ bool EnsureEGLImage();
+
+ void DestroyBuffer();
+ bool EnsureBufferCreated();
+
+ uint32_t GetAndroidUsage(uint32_t aUsage);
+ uint32_t GetAndroidFormat(gfxImageFormat aFormat);
+
+ void *mHandle;
+ void *mEGLImage;
+};
+
+} /* mozilla */
+#endif /* AndroidGraphicBuffer_h_ */
diff --git a/widget/android/AndroidJNI.cpp b/widget/android/AndroidJNI.cpp
new file mode 100644
index 000000000..3e27d253b
--- /dev/null
+++ b/widget/android/AndroidJNI.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "mozilla/Hal.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+#include "AndroidBridge.h"
+#include "AndroidContentController.h"
+#include "AndroidGraphicBuffer.h"
+
+#include <jni.h>
+#include <pthread.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "nsAppShell.h"
+#include "nsWindow.h"
+#include <android/log.h>
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/UniquePtr.h"
+
+#include "mozilla/layers/APZCTreeManager.h"
+#include "nsPluginInstanceOwner.h"
+#include "AndroidSurfaceTexture.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+
+/* Forward declare all the JNI methods as extern "C" */
+
+extern "C" {
+/*
+ * Incoming JNI methods
+ */
+
+}
diff --git a/widget/android/AndroidJNIWrapper.cpp b/widget/android/AndroidJNIWrapper.cpp
new file mode 100644
index 000000000..e549c6fc7
--- /dev/null
+++ b/widget/android/AndroidJNIWrapper.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 <prthread.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsThreadUtils.h"
+#include "AndroidBridge.h"
+
+extern "C" {
+ jclass __jsjni_GetGlobalClassRef(const char *className);
+}
+
+class GetGlobalClassRefRunnable : public mozilla::Runnable {
+ public:
+ GetGlobalClassRefRunnable(const char *className, jclass *foundClass) :
+ mClassName(className), mResult(foundClass) {}
+ NS_IMETHOD Run() override {
+ *mResult = __jsjni_GetGlobalClassRef(mClassName);
+ return NS_OK;
+ }
+ private:
+ const char *mClassName;
+ jclass *mResult;
+};
+
+extern "C" {
+ __attribute__ ((visibility("default")))
+ jclass
+ jsjni_FindClass(const char *className) {
+ // FindClass outside the main thread will run into problems due
+ // to missing the classpath
+ MOZ_ASSERT(NS_IsMainThread());
+ JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
+ return env->FindClass(className);
+ }
+
+ jclass
+ __jsjni_GetGlobalClassRef(const char *className) {
+ // root class globally
+ JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
+ jclass globalRef = static_cast<jclass>(env->NewGlobalRef(env->FindClass(className)));
+ if (!globalRef)
+ return nullptr;
+
+ // return the newly create global reference
+ return globalRef;
+ }
+
+ __attribute__ ((visibility("default")))
+ jclass
+ jsjni_GetGlobalClassRef(const char *className) {
+ if (NS_IsMainThread()) {
+ return __jsjni_GetGlobalClassRef(className);
+ }
+
+ nsCOMPtr<nsIThread> mainThread;
+ mozilla::DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(mainThread));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ jclass foundClass;
+ nsCOMPtr<nsIRunnable> runnable_ref(new GetGlobalClassRefRunnable(className,
+ &foundClass));
+ RefPtr<mozilla::SyncRunnable> sr = new mozilla::SyncRunnable(runnable_ref);
+ sr->DispatchToThread(mainThread);
+ if (!foundClass)
+ return nullptr;
+
+ return foundClass;
+ }
+
+ __attribute__ ((visibility("default")))
+ jmethodID
+ jsjni_GetStaticMethodID(jclass methodClass,
+ const char *methodName,
+ const char *signature) {
+ JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
+ return env->GetStaticMethodID(methodClass, methodName, signature);
+ }
+
+ __attribute__ ((visibility("default")))
+ bool
+ jsjni_ExceptionCheck() {
+ JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
+ return env->ExceptionCheck();
+ }
+
+ __attribute__ ((visibility("default")))
+ void
+ jsjni_CallStaticVoidMethodA(jclass cls,
+ jmethodID method,
+ jvalue *values) {
+ JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
+
+ mozilla::AutoLocalJNIFrame jniFrame(env);
+ env->CallStaticVoidMethodA(cls, method, values);
+ }
+
+ __attribute__ ((visibility("default")))
+ int
+ jsjni_CallStaticIntMethodA(jclass cls,
+ jmethodID method,
+ jvalue *values) {
+ JNIEnv *env = mozilla::jni::GetGeckoThreadEnv();
+
+ mozilla::AutoLocalJNIFrame jniFrame(env);
+ return env->CallStaticIntMethodA(cls, method, values);
+ }
+
+ __attribute__ ((visibility("default")))
+ jobject jsjni_GetGlobalContextRef() {
+ return mozilla::AndroidBridge::Bridge()->GetGlobalContextRef();
+ }
+
+ __attribute__ ((visibility("default")))
+ JavaVM* jsjni_GetVM() {
+ JavaVM* jvm;
+ JNIEnv* const env = mozilla::jni::GetGeckoThreadEnv();
+ MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
+ return jvm;
+ }
+
+ __attribute__ ((visibility("default")))
+ JNIEnv* jsjni_GetJNIForThread() {
+ return mozilla::jni::GetEnvForThread();
+ }
+
+ // For compatibility with JNI.jsm; some addons bundle their own JNI.jsm,
+ // so we cannot just change the function name used in JNI.jsm.
+ __attribute__ ((visibility("default")))
+ JNIEnv* GetJNIForThread() {
+ return mozilla::jni::GetEnvForThread();
+ }
+}
diff --git a/widget/android/AndroidJNIWrapper.h b/widget/android/AndroidJNIWrapper.h
new file mode 100644
index 000000000..90bca2693
--- /dev/null
+++ b/widget/android/AndroidJNIWrapper.h
@@ -0,0 +1,34 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 AndroidJNIWrapper_h__
+#define AndroidJNIWrapper_h__
+
+#include "mozilla/Types.h"
+#include <jni.h>
+#include <android/log.h>
+
+extern "C" MOZ_EXPORT jclass jsjni_FindClass(const char *className);
+
+/**
+ * JNIEnv::FindClass alternative.
+ * Callable from any thread, including code
+ * invoked via the JNI that doesn't have MOZILLA_INTERNAL_API defined.
+ * The caller is responsible for ensuring that the class is not leaked by
+ * calling DeleteGlobalRef at an appropriate time.
+ */
+extern "C" MOZ_EXPORT jclass jsjni_GetGlobalClassRef(const char *className);
+
+extern "C" MOZ_EXPORT jmethodID jsjni_GetStaticMethodID(jclass methodClass,
+ const char *methodName,
+ const char *signature);
+extern "C" MOZ_EXPORT bool jsjni_ExceptionCheck();
+extern "C" MOZ_EXPORT void jsjni_CallStaticVoidMethodA(jclass cls, jmethodID method, jvalue *values);
+extern "C" MOZ_EXPORT int jsjni_CallStaticIntMethodA(jclass cls, jmethodID method, jvalue *values);
+extern "C" MOZ_EXPORT jobject jsjni_GetGlobalContextRef();
+extern "C" MOZ_EXPORT JavaVM* jsjni_GetVM();
+extern "C" MOZ_EXPORT JNIEnv* jsjni_GetJNIForThread();
+
+#endif /* AndroidJNIWrapper_h__ */
diff --git a/widget/android/AndroidJavaWrappers.cpp b/widget/android/AndroidJavaWrappers.cpp
new file mode 100644
index 000000000..eb657a9c4
--- /dev/null
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "AndroidJavaWrappers.h"
+
+using namespace mozilla;
+
+nsJNIString::nsJNIString(jstring jstr, JNIEnv *jenv)
+{
+ if (!jstr) {
+ SetIsVoid(true);
+ return;
+ }
+ JNIEnv *jni = jenv;
+ if (!jni) {
+ jni = jni::GetGeckoThreadEnv();
+ }
+ const jchar* jCharPtr = jni->GetStringChars(jstr, nullptr);
+
+ if (!jCharPtr) {
+ SetIsVoid(true);
+ return;
+ }
+
+ jsize len = jni->GetStringLength(jstr);
+
+ if (len <= 0) {
+ SetIsVoid(true);
+ } else {
+ Assign(reinterpret_cast<const char16_t*>(jCharPtr), len);
+ }
+ jni->ReleaseStringChars(jstr, jCharPtr);
+}
+
+nsJNICString::nsJNICString(jstring jstr, JNIEnv *jenv)
+{
+ if (!jstr) {
+ SetIsVoid(true);
+ return;
+ }
+ JNIEnv *jni = jenv;
+ if (!jni) {
+ jni = jni::GetGeckoThreadEnv();
+ }
+ const char* jCharPtr = jni->GetStringUTFChars(jstr, nullptr);
+
+ if (!jCharPtr) {
+ SetIsVoid(true);
+ return;
+ }
+
+ jsize len = jni->GetStringUTFLength(jstr);
+
+ if (len <= 0) {
+ SetIsVoid(true);
+ } else {
+ Assign(jCharPtr, len);
+ }
+ jni->ReleaseStringUTFChars(jstr, jCharPtr);
+}
diff --git a/widget/android/AndroidJavaWrappers.h b/widget/android/AndroidJavaWrappers.h
new file mode 100644
index 000000000..fadf97353
--- /dev/null
+++ b/widget/android/AndroidJavaWrappers.h
@@ -0,0 +1,218 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 AndroidJavaWrappers_h__
+#define AndroidJavaWrappers_h__
+
+#include <jni.h>
+#include <android/input.h>
+#include <android/log.h>
+#include <android/api-level.h>
+
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIAndroidBridge.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/EventForwards.h"
+#include "InputData.h"
+#include "Units.h"
+#include "FrameMetrics.h"
+
+//#define FORCE_ALOG 1
+
+class nsIAndroidDisplayport;
+class nsIWidget;
+
+namespace mozilla {
+
+enum {
+ // These keycode masks are not defined in android/keycodes.h:
+#if __ANDROID_API__ < 13
+ AKEYCODE_ESCAPE = 111,
+ AKEYCODE_FORWARD_DEL = 112,
+ AKEYCODE_CTRL_LEFT = 113,
+ AKEYCODE_CTRL_RIGHT = 114,
+ AKEYCODE_CAPS_LOCK = 115,
+ AKEYCODE_SCROLL_LOCK = 116,
+ AKEYCODE_META_LEFT = 117,
+ AKEYCODE_META_RIGHT = 118,
+ AKEYCODE_FUNCTION = 119,
+ AKEYCODE_SYSRQ = 120,
+ AKEYCODE_BREAK = 121,
+ AKEYCODE_MOVE_HOME = 122,
+ AKEYCODE_MOVE_END = 123,
+ AKEYCODE_INSERT = 124,
+ AKEYCODE_FORWARD = 125,
+ AKEYCODE_MEDIA_PLAY = 126,
+ AKEYCODE_MEDIA_PAUSE = 127,
+ AKEYCODE_MEDIA_CLOSE = 128,
+ AKEYCODE_MEDIA_EJECT = 129,
+ AKEYCODE_MEDIA_RECORD = 130,
+ AKEYCODE_F1 = 131,
+ AKEYCODE_F2 = 132,
+ AKEYCODE_F3 = 133,
+ AKEYCODE_F4 = 134,
+ AKEYCODE_F5 = 135,
+ AKEYCODE_F6 = 136,
+ AKEYCODE_F7 = 137,
+ AKEYCODE_F8 = 138,
+ AKEYCODE_F9 = 139,
+ AKEYCODE_F10 = 140,
+ AKEYCODE_F11 = 141,
+ AKEYCODE_F12 = 142,
+ AKEYCODE_NUM_LOCK = 143,
+ AKEYCODE_NUMPAD_0 = 144,
+ AKEYCODE_NUMPAD_1 = 145,
+ AKEYCODE_NUMPAD_2 = 146,
+ AKEYCODE_NUMPAD_3 = 147,
+ AKEYCODE_NUMPAD_4 = 148,
+ AKEYCODE_NUMPAD_5 = 149,
+ AKEYCODE_NUMPAD_6 = 150,
+ AKEYCODE_NUMPAD_7 = 151,
+ AKEYCODE_NUMPAD_8 = 152,
+ AKEYCODE_NUMPAD_9 = 153,
+ AKEYCODE_NUMPAD_DIVIDE = 154,
+ AKEYCODE_NUMPAD_MULTIPLY = 155,
+ AKEYCODE_NUMPAD_SUBTRACT = 156,
+ AKEYCODE_NUMPAD_ADD = 157,
+ AKEYCODE_NUMPAD_DOT = 158,
+ AKEYCODE_NUMPAD_COMMA = 159,
+ AKEYCODE_NUMPAD_ENTER = 160,
+ AKEYCODE_NUMPAD_EQUALS = 161,
+ AKEYCODE_NUMPAD_LEFT_PAREN = 162,
+ AKEYCODE_NUMPAD_RIGHT_PAREN = 163,
+ AKEYCODE_VOLUME_MUTE = 164,
+ AKEYCODE_INFO = 165,
+ AKEYCODE_CHANNEL_UP = 166,
+ AKEYCODE_CHANNEL_DOWN = 167,
+ AKEYCODE_ZOOM_IN = 168,
+ AKEYCODE_ZOOM_OUT = 169,
+ AKEYCODE_TV = 170,
+ AKEYCODE_WINDOW = 171,
+ AKEYCODE_GUIDE = 172,
+ AKEYCODE_DVR = 173,
+ AKEYCODE_BOOKMARK = 174,
+ AKEYCODE_CAPTIONS = 175,
+ AKEYCODE_SETTINGS = 176,
+ AKEYCODE_TV_POWER = 177,
+ AKEYCODE_TV_INPUT = 178,
+ AKEYCODE_STB_POWER = 179,
+ AKEYCODE_STB_INPUT = 180,
+ AKEYCODE_AVR_POWER = 181,
+ AKEYCODE_AVR_INPUT = 182,
+ AKEYCODE_PROG_RED = 183,
+ AKEYCODE_PROG_GREEN = 184,
+ AKEYCODE_PROG_YELLOW = 185,
+ AKEYCODE_PROG_BLUE = 186,
+ AKEYCODE_APP_SWITCH = 187,
+ AKEYCODE_BUTTON_1 = 188,
+ AKEYCODE_BUTTON_2 = 189,
+ AKEYCODE_BUTTON_3 = 190,
+ AKEYCODE_BUTTON_4 = 191,
+ AKEYCODE_BUTTON_5 = 192,
+ AKEYCODE_BUTTON_6 = 193,
+ AKEYCODE_BUTTON_7 = 194,
+ AKEYCODE_BUTTON_8 = 195,
+ AKEYCODE_BUTTON_9 = 196,
+ AKEYCODE_BUTTON_10 = 197,
+ AKEYCODE_BUTTON_11 = 198,
+ AKEYCODE_BUTTON_12 = 199,
+ AKEYCODE_BUTTON_13 = 200,
+ AKEYCODE_BUTTON_14 = 201,
+ AKEYCODE_BUTTON_15 = 202,
+ AKEYCODE_BUTTON_16 = 203,
+#endif
+#if __ANDROID_API__ < 14
+ AKEYCODE_LANGUAGE_SWITCH = 204,
+ AKEYCODE_MANNER_MODE = 205,
+ AKEYCODE_3D_MODE = 206,
+#endif
+#if __ANDROID_API__ < 15
+ AKEYCODE_CONTACTS = 207,
+ AKEYCODE_CALENDAR = 208,
+ AKEYCODE_MUSIC = 209,
+ AKEYCODE_CALCULATOR = 210,
+#endif
+#if __ANDROID_API__ < 16
+ AKEYCODE_ZENKAKU_HANKAKU = 211,
+ AKEYCODE_EISU = 212,
+ AKEYCODE_MUHENKAN = 213,
+ AKEYCODE_HENKAN = 214,
+ AKEYCODE_KATAKANA_HIRAGANA = 215,
+ AKEYCODE_YEN = 216,
+ AKEYCODE_RO = 217,
+ AKEYCODE_KANA = 218,
+ AKEYCODE_ASSIST = 219,
+#endif
+
+ AMETA_FUNCTION_ON = 0x00000008,
+ AMETA_CTRL_ON = 0x00001000,
+ AMETA_CTRL_LEFT_ON = 0x00002000,
+ AMETA_CTRL_RIGHT_ON = 0x00004000,
+ AMETA_META_ON = 0x00010000,
+ AMETA_META_LEFT_ON = 0x00020000,
+ AMETA_META_RIGHT_ON = 0x00040000,
+ AMETA_CAPS_LOCK_ON = 0x00100000,
+ AMETA_NUM_LOCK_ON = 0x00200000,
+ AMETA_SCROLL_LOCK_ON = 0x00400000,
+
+ AMETA_ALT_MASK = AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON | AMETA_ALT_ON,
+ AMETA_CTRL_MASK = AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON | AMETA_CTRL_ON,
+ AMETA_META_MASK = AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON | AMETA_META_ON,
+ AMETA_SHIFT_MASK = AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON | AMETA_SHIFT_ON,
+};
+
+class AndroidMotionEvent
+{
+public:
+ enum {
+ ACTION_DOWN = 0,
+ ACTION_UP = 1,
+ ACTION_MOVE = 2,
+ ACTION_CANCEL = 3,
+ ACTION_OUTSIDE = 4,
+ ACTION_POINTER_DOWN = 5,
+ ACTION_POINTER_UP = 6,
+ ACTION_HOVER_MOVE = 7,
+ ACTION_HOVER_ENTER = 9,
+ ACTION_HOVER_EXIT = 10,
+ ACTION_MAGNIFY_START = 11,
+ ACTION_MAGNIFY = 12,
+ ACTION_MAGNIFY_END = 13,
+ EDGE_TOP = 0x00000001,
+ EDGE_BOTTOM = 0x00000002,
+ EDGE_LEFT = 0x00000004,
+ EDGE_RIGHT = 0x00000008,
+ SAMPLE_X = 0,
+ SAMPLE_Y = 1,
+ SAMPLE_PRESSURE = 2,
+ SAMPLE_SIZE = 3,
+ NUM_SAMPLE_DATA = 4,
+ TOOL_TYPE_UNKNOWN = 0,
+ TOOL_TYPE_FINGER = 1,
+ TOOL_TYPE_STYLUS = 2,
+ TOOL_TYPE_MOUSE = 3,
+ TOOL_TYPE_ERASER = 4,
+ dummy_java_enum_list_end
+ };
+};
+
+class nsJNIString : public nsString
+{
+public:
+ nsJNIString(jstring jstr, JNIEnv *jenv);
+};
+
+class nsJNICString : public nsCString
+{
+public:
+ nsJNICString(jstring jstr, JNIEnv *jenv);
+};
+
+}
+
+#endif
diff --git a/widget/android/GeckoBatteryManager.h b/widget/android/GeckoBatteryManager.h
new file mode 100644
index 000000000..a09e83efd
--- /dev/null
+++ b/widget/android/GeckoBatteryManager.h
@@ -0,0 +1,30 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "GeneratedJNINatives.h"
+#include "nsAppShell.h"
+
+#include "mozilla/Hal.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/GeckoNetworkManager.h b/widget/android/GeckoNetworkManager.h
new file mode 100644
index 000000000..63cbb9c79
--- /dev/null
+++ b/widget/android/GeckoNetworkManager.h
@@ -0,0 +1,53 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "GeneratedJNINatives.h"
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsINetworkLinkService.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/GeckoScreenOrientation.h b/widget/android/GeckoScreenOrientation.h
new file mode 100644
index 000000000..c6e5861be
--- /dev/null
+++ b/widget/android/GeckoScreenOrientation.h
@@ -0,0 +1,55 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "GeneratedJNINatives.h"
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIScreenManager.h"
+
+#include "mozilla/Hal.h"
+#include "mozilla/dom/ScreenOrientation.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<dom::ScreenOrientationInternal>(aOrientation),
+ aAngle, colorDepth, pixelDepth));
+ }
+};
+
+} // namespace mozilla
+
+#endif // GeckoScreenOrientation_h
diff --git a/widget/android/GeneratedJNINatives.h b/widget/android/GeneratedJNINatives.h
new file mode 100644
index 000000000..bce88ce65
--- /dev/null
+++ b/widget/android/GeneratedJNINatives.h
@@ -0,0 +1,526 @@
+// GENERATED CODE
+// Generated by the Java program at /build/annotationProcessors at compile time
+// from annotations on Java methods. To update, change the annotations on the
+// corresponding Java methods and rerun the build. Manually updating this file
+// will cause your build to fail.
+
+#ifndef GeneratedJNINatives_h
+#define GeneratedJNINatives_h
+
+#include "GeneratedJNIWrappers.h"
+#include "mozilla/jni/Natives.h"
+
+namespace mozilla {
+namespace java {
+
+template<class Impl>
+class AlarmReceiver::Natives : public mozilla::jni::NativeImpl<AlarmReceiver, Impl>
+{
+public:
+ static const JNINativeMethod methods[1];
+};
+
+template<class Impl>
+const JNINativeMethod AlarmReceiver::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<AlarmReceiver::NotifyAlarmFired_t>(
+ mozilla::jni::NativeStub<AlarmReceiver::NotifyAlarmFired_t, Impl>
+ ::template Wrap<&Impl::NotifyAlarmFired>)
+};
+
+template<class Impl>
+class AndroidGamepadManager::Natives : public mozilla::jni::NativeImpl<AndroidGamepadManager, Impl>
+{
+public:
+ static const JNINativeMethod methods[3];
+};
+
+template<class Impl>
+const JNINativeMethod AndroidGamepadManager::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<AndroidGamepadManager::OnAxisChange_t>(
+ mozilla::jni::NativeStub<AndroidGamepadManager::OnAxisChange_t, Impl>
+ ::template Wrap<&Impl::OnAxisChange>),
+
+ mozilla::jni::MakeNativeMethod<AndroidGamepadManager::OnButtonChange_t>(
+ mozilla::jni::NativeStub<AndroidGamepadManager::OnButtonChange_t, Impl>
+ ::template Wrap<&Impl::OnButtonChange>),
+
+ mozilla::jni::MakeNativeMethod<AndroidGamepadManager::OnGamepadChange_t>(
+ mozilla::jni::NativeStub<AndroidGamepadManager::OnGamepadChange_t, Impl>
+ ::template Wrap<&Impl::OnGamepadChange>)
+};
+
+template<class Impl>
+class GeckoAppShell::Natives : public mozilla::jni::NativeImpl<GeckoAppShell, Impl>
+{
+public:
+ static const JNINativeMethod methods[8];
+};
+
+template<class Impl>
+const JNINativeMethod GeckoAppShell::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<GeckoAppShell::NotifyObservers_t>(
+ mozilla::jni::NativeStub<GeckoAppShell::NotifyObservers_t, Impl>
+ ::template Wrap<&Impl::NotifyObservers>),
+
+ mozilla::jni::MakeNativeMethod<GeckoAppShell::NotifyAlertListener_t>(
+ mozilla::jni::NativeStub<GeckoAppShell::NotifyAlertListener_t, Impl>
+ ::template Wrap<&Impl::NotifyAlertListener>),
+
+ mozilla::jni::MakeNativeMethod<GeckoAppShell::NotifyUriVisited_t>(
+ mozilla::jni::NativeStub<GeckoAppShell::NotifyUriVisited_t, Impl>
+ ::template Wrap<&Impl::NotifyUriVisited>),
+
+ mozilla::jni::MakeNativeMethod<GeckoAppShell::OnFullScreenPluginHidden_t>(
+ mozilla::jni::NativeStub<GeckoAppShell::OnFullScreenPluginHidden_t, Impl>
+ ::template Wrap<&Impl::OnFullScreenPluginHidden>),
+
+ mozilla::jni::MakeNativeMethod<GeckoAppShell::OnLocationChanged_t>(
+ mozilla::jni::NativeStub<GeckoAppShell::OnLocationChanged_t, Impl>
+ ::template Wrap<&Impl::OnLocationChanged>),
+
+ mozilla::jni::MakeNativeMethod<GeckoAppShell::OnSensorChanged_t>(
+ mozilla::jni::NativeStub<GeckoAppShell::OnSensorChanged_t, Impl>
+ ::template Wrap<&Impl::OnSensorChanged>),
+
+ mozilla::jni::MakeNativeMethod<GeckoAppShell::ReportJavaCrash_t>(
+ mozilla::jni::NativeStub<GeckoAppShell::ReportJavaCrash_t, Impl>
+ ::template Wrap<&Impl::ReportJavaCrash>),
+
+ mozilla::jni::MakeNativeMethod<GeckoAppShell::SyncNotifyObservers_t>(
+ mozilla::jni::NativeStub<GeckoAppShell::SyncNotifyObservers_t, Impl>
+ ::template Wrap<&Impl::SyncNotifyObservers>)
+};
+
+template<class Impl>
+class GeckoAppShell::CameraCallback::Natives : public mozilla::jni::NativeImpl<CameraCallback, Impl>
+{
+public:
+ static const JNINativeMethod methods[1];
+};
+
+template<class Impl>
+const JNINativeMethod GeckoAppShell::CameraCallback::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<GeckoAppShell::CameraCallback::OnFrameData_t>(
+ mozilla::jni::NativeStub<GeckoAppShell::CameraCallback::OnFrameData_t, Impl>
+ ::template Wrap<&Impl::OnFrameData>)
+};
+
+template<class Impl>
+class GeckoBatteryManager::Natives : public mozilla::jni::NativeImpl<GeckoBatteryManager, Impl>
+{
+public:
+ static const JNINativeMethod methods[1];
+};
+
+template<class Impl>
+const JNINativeMethod GeckoBatteryManager::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<GeckoBatteryManager::OnBatteryChange_t>(
+ mozilla::jni::NativeStub<GeckoBatteryManager::OnBatteryChange_t, Impl>
+ ::template Wrap<&Impl::OnBatteryChange>)
+};
+
+template<class Impl>
+class GeckoEditable::Natives : public mozilla::jni::NativeImpl<GeckoEditable, Impl>
+{
+public:
+ static const JNINativeMethod methods[7];
+};
+
+template<class Impl>
+const JNINativeMethod GeckoEditable::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<GeckoEditable::DisposeNative_t>(
+ mozilla::jni::NativeStub<GeckoEditable::DisposeNative_t, Impl>
+ ::template Wrap<&Impl::DisposeNative>),
+
+ mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeAddCompositionRange_t>(
+ mozilla::jni::NativeStub<GeckoEditable::OnImeAddCompositionRange_t, Impl>
+ ::template Wrap<&Impl::OnImeAddCompositionRange>),
+
+ mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeReplaceText_t>(
+ mozilla::jni::NativeStub<GeckoEditable::OnImeReplaceText_t, Impl>
+ ::template Wrap<&Impl::OnImeReplaceText>),
+
+ mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeRequestCursorUpdates_t>(
+ mozilla::jni::NativeStub<GeckoEditable::OnImeRequestCursorUpdates_t, Impl>
+ ::template Wrap<&Impl::OnImeRequestCursorUpdates>),
+
+ mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeSynchronize_t>(
+ mozilla::jni::NativeStub<GeckoEditable::OnImeSynchronize_t, Impl>
+ ::template Wrap<&Impl::OnImeSynchronize>),
+
+ mozilla::jni::MakeNativeMethod<GeckoEditable::OnImeUpdateComposition_t>(
+ mozilla::jni::NativeStub<GeckoEditable::OnImeUpdateComposition_t, Impl>
+ ::template Wrap<&Impl::OnImeUpdateComposition>),
+
+ mozilla::jni::MakeNativeMethod<GeckoEditable::OnKeyEvent_t>(
+ mozilla::jni::NativeStub<GeckoEditable::OnKeyEvent_t, Impl>
+ ::template Wrap<&Impl::OnKeyEvent>)
+};
+
+template<class Impl>
+class GeckoNetworkManager::Natives : public mozilla::jni::NativeImpl<GeckoNetworkManager, Impl>
+{
+public:
+ static const JNINativeMethod methods[2];
+};
+
+template<class Impl>
+const JNINativeMethod GeckoNetworkManager::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<GeckoNetworkManager::OnConnectionChanged_t>(
+ mozilla::jni::NativeStub<GeckoNetworkManager::OnConnectionChanged_t, Impl>
+ ::template Wrap<&Impl::OnConnectionChanged>),
+
+ mozilla::jni::MakeNativeMethod<GeckoNetworkManager::OnStatusChanged_t>(
+ mozilla::jni::NativeStub<GeckoNetworkManager::OnStatusChanged_t, Impl>
+ ::template Wrap<&Impl::OnStatusChanged>)
+};
+
+template<class Impl>
+class GeckoScreenOrientation::Natives : public mozilla::jni::NativeImpl<GeckoScreenOrientation, Impl>
+{
+public:
+ static const JNINativeMethod methods[1];
+};
+
+template<class Impl>
+const JNINativeMethod GeckoScreenOrientation::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<GeckoScreenOrientation::OnOrientationChange_t>(
+ mozilla::jni::NativeStub<GeckoScreenOrientation::OnOrientationChange_t, Impl>
+ ::template Wrap<&Impl::OnOrientationChange>)
+};
+
+template<class Impl>
+class GeckoThread::Natives : public mozilla::jni::NativeImpl<GeckoThread, Impl>
+{
+public:
+ static const JNINativeMethod methods[6];
+};
+
+template<class Impl>
+const JNINativeMethod GeckoThread::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<GeckoThread::CreateServices_t>(
+ mozilla::jni::NativeStub<GeckoThread::CreateServices_t, Impl>
+ ::template Wrap<&Impl::CreateServices>),
+
+ mozilla::jni::MakeNativeMethod<GeckoThread::OnPause_t>(
+ mozilla::jni::NativeStub<GeckoThread::OnPause_t, Impl>
+ ::template Wrap<&Impl::OnPause>),
+
+ mozilla::jni::MakeNativeMethod<GeckoThread::OnResume_t>(
+ mozilla::jni::NativeStub<GeckoThread::OnResume_t, Impl>
+ ::template Wrap<&Impl::OnResume>),
+
+ mozilla::jni::MakeNativeMethod<GeckoThread::RunUiThreadCallback_t>(
+ mozilla::jni::NativeStub<GeckoThread::RunUiThreadCallback_t, Impl>
+ ::template Wrap<&Impl::RunUiThreadCallback>),
+
+ mozilla::jni::MakeNativeMethod<GeckoThread::SpeculativeConnect_t>(
+ mozilla::jni::NativeStub<GeckoThread::SpeculativeConnect_t, Impl>
+ ::template Wrap<&Impl::SpeculativeConnect>),
+
+ mozilla::jni::MakeNativeMethod<GeckoThread::WaitOnGecko_t>(
+ mozilla::jni::NativeStub<GeckoThread::WaitOnGecko_t, Impl>
+ ::template Wrap<&Impl::WaitOnGecko>)
+};
+
+template<class Impl>
+class GeckoView::Window::Natives : public mozilla::jni::NativeImpl<Window, Impl>
+{
+public:
+ static const JNINativeMethod methods[5];
+};
+
+template<class Impl>
+const JNINativeMethod GeckoView::Window::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<GeckoView::Window::Close_t>(
+ mozilla::jni::NativeStub<GeckoView::Window::Close_t, Impl>
+ ::template Wrap<&Impl::Close>),
+
+ mozilla::jni::MakeNativeMethod<GeckoView::Window::DisposeNative_t>(
+ mozilla::jni::NativeStub<GeckoView::Window::DisposeNative_t, Impl>
+ ::template Wrap<&Impl::DisposeNative>),
+
+ mozilla::jni::MakeNativeMethod<GeckoView::Window::LoadUri_t>(
+ mozilla::jni::NativeStub<GeckoView::Window::LoadUri_t, Impl>
+ ::template Wrap<&Impl::LoadUri>),
+
+ mozilla::jni::MakeNativeMethod<GeckoView::Window::Open_t>(
+ mozilla::jni::NativeStub<GeckoView::Window::Open_t, Impl>
+ ::template Wrap<&Impl::Open>),
+
+ mozilla::jni::MakeNativeMethod<GeckoView::Window::Reattach_t>(
+ mozilla::jni::NativeStub<GeckoView::Window::Reattach_t, Impl>
+ ::template Wrap<&Impl::Reattach>)
+};
+
+template<class Impl>
+class PrefsHelper::Natives : public mozilla::jni::NativeImpl<PrefsHelper, Impl>
+{
+public:
+ static const JNINativeMethod methods[4];
+};
+
+template<class Impl>
+const JNINativeMethod PrefsHelper::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<PrefsHelper::AddObserver_t>(
+ mozilla::jni::NativeStub<PrefsHelper::AddObserver_t, Impl>
+ ::template Wrap<&Impl::AddObserver>),
+
+ mozilla::jni::MakeNativeMethod<PrefsHelper::GetPrefs_t>(
+ mozilla::jni::NativeStub<PrefsHelper::GetPrefs_t, Impl>
+ ::template Wrap<&Impl::GetPrefs>),
+
+ mozilla::jni::MakeNativeMethod<PrefsHelper::RemoveObserver_t>(
+ mozilla::jni::NativeStub<PrefsHelper::RemoveObserver_t, Impl>
+ ::template Wrap<&Impl::RemoveObserver>),
+
+ mozilla::jni::MakeNativeMethod<PrefsHelper::SetPref_t>(
+ mozilla::jni::NativeStub<PrefsHelper::SetPref_t, Impl>
+ ::template Wrap<&Impl::SetPref>)
+};
+
+template<class Impl>
+class SurfaceTextureListener::Natives : public mozilla::jni::NativeImpl<SurfaceTextureListener, Impl>
+{
+public:
+ static const JNINativeMethod methods[1];
+};
+
+template<class Impl>
+const JNINativeMethod SurfaceTextureListener::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<SurfaceTextureListener::OnFrameAvailable_t>(
+ mozilla::jni::NativeStub<SurfaceTextureListener::OnFrameAvailable_t, Impl>
+ ::template Wrap<&Impl::OnFrameAvailable>)
+};
+
+template<class Impl>
+class LayerView::Compositor::Natives : public mozilla::jni::NativeImpl<Compositor, Impl>
+{
+public:
+ static const JNINativeMethod methods[7];
+};
+
+template<class Impl>
+const JNINativeMethod LayerView::Compositor::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<LayerView::Compositor::AttachToJava_t>(
+ mozilla::jni::NativeStub<LayerView::Compositor::AttachToJava_t, Impl>
+ ::template Wrap<&Impl::AttachToJava>),
+
+ mozilla::jni::MakeNativeMethod<LayerView::Compositor::CreateCompositor_t>(
+ mozilla::jni::NativeStub<LayerView::Compositor::CreateCompositor_t, Impl>
+ ::template Wrap<&Impl::CreateCompositor>),
+
+ mozilla::jni::MakeNativeMethod<LayerView::Compositor::DisposeNative_t>(
+ mozilla::jni::NativeStub<LayerView::Compositor::DisposeNative_t, Impl>
+ ::template Wrap<&Impl::DisposeNative>),
+
+ mozilla::jni::MakeNativeMethod<LayerView::Compositor::OnSizeChanged_t>(
+ mozilla::jni::NativeStub<LayerView::Compositor::OnSizeChanged_t, Impl>
+ ::template Wrap<&Impl::OnSizeChanged>),
+
+ mozilla::jni::MakeNativeMethod<LayerView::Compositor::SyncInvalidateAndScheduleComposite_t>(
+ mozilla::jni::NativeStub<LayerView::Compositor::SyncInvalidateAndScheduleComposite_t, Impl>
+ ::template Wrap<&Impl::SyncInvalidateAndScheduleComposite>),
+
+ mozilla::jni::MakeNativeMethod<LayerView::Compositor::SyncPauseCompositor_t>(
+ mozilla::jni::NativeStub<LayerView::Compositor::SyncPauseCompositor_t, Impl>
+ ::template Wrap<&Impl::SyncPauseCompositor>),
+
+ mozilla::jni::MakeNativeMethod<LayerView::Compositor::SyncResumeResizeCompositor_t>(
+ mozilla::jni::NativeStub<LayerView::Compositor::SyncResumeResizeCompositor_t, Impl>
+ ::template Wrap<&Impl::SyncResumeResizeCompositor>)
+};
+
+template<class Impl>
+class NativePanZoomController::Natives : public mozilla::jni::NativeImpl<NativePanZoomController, Impl>
+{
+public:
+ static const JNINativeMethod methods[7];
+};
+
+template<class Impl>
+const JNINativeMethod NativePanZoomController::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<NativePanZoomController::AdjustScrollForSurfaceShift_t>(
+ mozilla::jni::NativeStub<NativePanZoomController::AdjustScrollForSurfaceShift_t, Impl>
+ ::template Wrap<&Impl::AdjustScrollForSurfaceShift>),
+
+ mozilla::jni::MakeNativeMethod<NativePanZoomController::DisposeNative_t>(
+ mozilla::jni::NativeStub<NativePanZoomController::DisposeNative_t, Impl>
+ ::template Wrap<&Impl::DisposeNative>),
+
+ mozilla::jni::MakeNativeMethod<NativePanZoomController::HandleMotionEvent_t>(
+ mozilla::jni::NativeStub<NativePanZoomController::HandleMotionEvent_t, Impl>
+ ::template Wrap<&Impl::HandleMotionEvent>),
+
+ mozilla::jni::MakeNativeMethod<NativePanZoomController::HandleMotionEventVelocity_t>(
+ mozilla::jni::NativeStub<NativePanZoomController::HandleMotionEventVelocity_t, Impl>
+ ::template Wrap<&Impl::HandleMotionEventVelocity>),
+
+ mozilla::jni::MakeNativeMethod<NativePanZoomController::HandleMouseEvent_t>(
+ mozilla::jni::NativeStub<NativePanZoomController::HandleMouseEvent_t, Impl>
+ ::template Wrap<&Impl::HandleMouseEvent>),
+
+ mozilla::jni::MakeNativeMethod<NativePanZoomController::HandleScrollEvent_t>(
+ mozilla::jni::NativeStub<NativePanZoomController::HandleScrollEvent_t, Impl>
+ ::template Wrap<&Impl::HandleScrollEvent>),
+
+ mozilla::jni::MakeNativeMethod<NativePanZoomController::SetIsLongpressEnabled_t>(
+ mozilla::jni::NativeStub<NativePanZoomController::SetIsLongpressEnabled_t, Impl>
+ ::template Wrap<&Impl::SetIsLongpressEnabled>)
+};
+
+template<class Impl>
+class NativeJSContainer::Natives : public mozilla::jni::NativeImpl<NativeJSContainer, Impl>
+{
+public:
+ static const JNINativeMethod methods[2];
+};
+
+template<class Impl>
+const JNINativeMethod NativeJSContainer::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<NativeJSContainer::Clone2_t>(
+ mozilla::jni::NativeStub<NativeJSContainer::Clone2_t, Impl>
+ ::template Wrap<&Impl::Clone>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSContainer::DisposeNative_t>(
+ mozilla::jni::NativeStub<NativeJSContainer::DisposeNative_t, Impl>
+ ::template Wrap<&Impl::DisposeNative>)
+};
+
+template<class Impl>
+class NativeJSObject::Natives : public mozilla::jni::NativeImpl<NativeJSObject, Impl>
+{
+public:
+ static const JNINativeMethod methods[27];
+};
+
+template<class Impl>
+const JNINativeMethod NativeJSObject::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetBoolean_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetBoolean_t, Impl>
+ ::template Wrap<&Impl::GetBoolean>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetBooleanArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetBooleanArray_t, Impl>
+ ::template Wrap<&Impl::GetBooleanArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetBundle_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetBundle_t, Impl>
+ ::template Wrap<&Impl::GetBundle>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetBundleArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetBundleArray_t, Impl>
+ ::template Wrap<&Impl::GetBundleArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetDouble_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetDouble_t, Impl>
+ ::template Wrap<&Impl::GetDouble>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetDoubleArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetDoubleArray_t, Impl>
+ ::template Wrap<&Impl::GetDoubleArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetInt_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetInt_t, Impl>
+ ::template Wrap<&Impl::GetInt>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetIntArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetIntArray_t, Impl>
+ ::template Wrap<&Impl::GetIntArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetObject_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetObject_t, Impl>
+ ::template Wrap<&Impl::GetObject>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetObjectArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetObjectArray_t, Impl>
+ ::template Wrap<&Impl::GetObjectArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetString_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetString_t, Impl>
+ ::template Wrap<&Impl::GetString>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::GetStringArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::GetStringArray_t, Impl>
+ ::template Wrap<&Impl::GetStringArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::Has_t>(
+ mozilla::jni::NativeStub<NativeJSObject::Has_t, Impl>
+ ::template Wrap<&Impl::Has>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptBoolean_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptBoolean_t, Impl>
+ ::template Wrap<&Impl::OptBoolean>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptBooleanArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptBooleanArray_t, Impl>
+ ::template Wrap<&Impl::OptBooleanArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptBundle_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptBundle_t, Impl>
+ ::template Wrap<&Impl::OptBundle>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptBundleArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptBundleArray_t, Impl>
+ ::template Wrap<&Impl::OptBundleArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptDouble_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptDouble_t, Impl>
+ ::template Wrap<&Impl::OptDouble>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptDoubleArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptDoubleArray_t, Impl>
+ ::template Wrap<&Impl::OptDoubleArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptInt_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptInt_t, Impl>
+ ::template Wrap<&Impl::OptInt>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptIntArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptIntArray_t, Impl>
+ ::template Wrap<&Impl::OptIntArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptObject_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptObject_t, Impl>
+ ::template Wrap<&Impl::OptObject>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptObjectArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptObjectArray_t, Impl>
+ ::template Wrap<&Impl::OptObjectArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptString_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptString_t, Impl>
+ ::template Wrap<&Impl::OptString>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::OptStringArray_t>(
+ mozilla::jni::NativeStub<NativeJSObject::OptStringArray_t, Impl>
+ ::template Wrap<&Impl::OptStringArray>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::ToBundle_t>(
+ mozilla::jni::NativeStub<NativeJSObject::ToBundle_t, Impl>
+ ::template Wrap<&Impl::ToBundle>),
+
+ mozilla::jni::MakeNativeMethod<NativeJSObject::ToString_t>(
+ mozilla::jni::NativeStub<NativeJSObject::ToString_t, Impl>
+ ::template Wrap<&Impl::ToString>)
+};
+
+} /* java */
+} /* mozilla */
+#endif // GeneratedJNINatives_h
diff --git a/widget/android/GeneratedJNIWrappers.cpp b/widget/android/GeneratedJNIWrappers.cpp
new file mode 100644
index 000000000..37564ba24
--- /dev/null
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -0,0 +1,1874 @@
+// GENERATED CODE
+// Generated by the Java program at /build/annotationProcessors at compile time
+// from annotations on Java methods. To update, change the annotations on the
+// corresponding Java methods and rerun the build. Manually updating this file
+// will cause your build to fail.
+
+#include "GeneratedJNIWrappers.h"
+#include "mozilla/jni/Accessors.h"
+
+namespace mozilla {
+namespace java {
+
+const char AlarmReceiver::name[] =
+ "org/mozilla/gecko/AlarmReceiver";
+
+constexpr char AlarmReceiver::NotifyAlarmFired_t::name[];
+constexpr char AlarmReceiver::NotifyAlarmFired_t::signature[];
+
+const char AndroidGamepadManager::name[] =
+ "org/mozilla/gecko/AndroidGamepadManager";
+
+constexpr char AndroidGamepadManager::OnAxisChange_t::name[];
+constexpr char AndroidGamepadManager::OnAxisChange_t::signature[];
+
+constexpr char AndroidGamepadManager::OnButtonChange_t::name[];
+constexpr char AndroidGamepadManager::OnButtonChange_t::signature[];
+
+constexpr char AndroidGamepadManager::OnGamepadAdded_t::name[];
+constexpr char AndroidGamepadManager::OnGamepadAdded_t::signature[];
+
+auto AndroidGamepadManager::OnGamepadAdded(int32_t a0, int32_t a1) -> void
+{
+ return mozilla::jni::Method<OnGamepadAdded_t>::Call(AndroidGamepadManager::Context(), nullptr, a0, a1);
+}
+
+constexpr char AndroidGamepadManager::OnGamepadChange_t::name[];
+constexpr char AndroidGamepadManager::OnGamepadChange_t::signature[];
+
+constexpr char AndroidGamepadManager::Start_t::name[];
+constexpr char AndroidGamepadManager::Start_t::signature[];
+
+auto AndroidGamepadManager::Start() -> void
+{
+ return mozilla::jni::Method<Start_t>::Call(AndroidGamepadManager::Context(), nullptr);
+}
+
+constexpr char AndroidGamepadManager::Stop_t::name[];
+constexpr char AndroidGamepadManager::Stop_t::signature[];
+
+auto AndroidGamepadManager::Stop() -> void
+{
+ return mozilla::jni::Method<Stop_t>::Call(AndroidGamepadManager::Context(), nullptr);
+}
+
+const char GeckoAppShell::name[] =
+ "org/mozilla/gecko/GeckoAppShell";
+
+constexpr char GeckoAppShell::AddFullScreenPluginView_t::name[];
+constexpr char GeckoAppShell::AddFullScreenPluginView_t::signature[];
+
+auto GeckoAppShell::AddFullScreenPluginView(mozilla::jni::Object::Param a0) -> void
+{
+ return mozilla::jni::Method<AddFullScreenPluginView_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::CancelVibrate_t::name[];
+constexpr char GeckoAppShell::CancelVibrate_t::signature[];
+
+auto GeckoAppShell::CancelVibrate() -> void
+{
+ return mozilla::jni::Method<CancelVibrate_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::CheckURIVisited_t::name[];
+constexpr char GeckoAppShell::CheckURIVisited_t::signature[];
+
+auto GeckoAppShell::CheckURIVisited(mozilla::jni::String::Param a0) -> void
+{
+ return mozilla::jni::Method<CheckURIVisited_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::CloseCamera_t::name[];
+constexpr char GeckoAppShell::CloseCamera_t::signature[];
+
+auto GeckoAppShell::CloseCamera() -> void
+{
+ return mozilla::jni::Method<CloseCamera_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::CloseNotification_t::name[];
+constexpr char GeckoAppShell::CloseNotification_t::signature[];
+
+auto GeckoAppShell::CloseNotification(mozilla::jni::String::Param a0) -> void
+{
+ return mozilla::jni::Method<CloseNotification_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::ConnectionGetMimeType_t::name[];
+constexpr char GeckoAppShell::ConnectionGetMimeType_t::signature[];
+
+auto GeckoAppShell::ConnectionGetMimeType(mozilla::jni::Object::Param a0) -> mozilla::jni::String::LocalRef
+{
+ return mozilla::jni::Method<ConnectionGetMimeType_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::CreateInputStream_t::name[];
+constexpr char GeckoAppShell::CreateInputStream_t::signature[];
+
+auto GeckoAppShell::CreateInputStream(mozilla::jni::Object::Param a0) -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Method<CreateInputStream_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::CreateShortcut_t::name[];
+constexpr char GeckoAppShell::CreateShortcut_t::signature[];
+
+auto GeckoAppShell::CreateShortcut(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> void
+{
+ return mozilla::jni::Method<CreateShortcut_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoAppShell::DisableAlarm_t::name[];
+constexpr char GeckoAppShell::DisableAlarm_t::signature[];
+
+auto GeckoAppShell::DisableAlarm() -> void
+{
+ return mozilla::jni::Method<DisableAlarm_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::DisableBatteryNotifications_t::name[];
+constexpr char GeckoAppShell::DisableBatteryNotifications_t::signature[];
+
+auto GeckoAppShell::DisableBatteryNotifications() -> void
+{
+ return mozilla::jni::Method<DisableBatteryNotifications_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::DisableNetworkNotifications_t::name[];
+constexpr char GeckoAppShell::DisableNetworkNotifications_t::signature[];
+
+auto GeckoAppShell::DisableNetworkNotifications() -> void
+{
+ return mozilla::jni::Method<DisableNetworkNotifications_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::DisableScreenOrientationNotifications_t::name[];
+constexpr char GeckoAppShell::DisableScreenOrientationNotifications_t::signature[];
+
+auto GeckoAppShell::DisableScreenOrientationNotifications() -> void
+{
+ return mozilla::jni::Method<DisableScreenOrientationNotifications_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::DisableSensor_t::name[];
+constexpr char GeckoAppShell::DisableSensor_t::signature[];
+
+auto GeckoAppShell::DisableSensor(int32_t a0) -> void
+{
+ return mozilla::jni::Method<DisableSensor_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::EnableBatteryNotifications_t::name[];
+constexpr char GeckoAppShell::EnableBatteryNotifications_t::signature[];
+
+auto GeckoAppShell::EnableBatteryNotifications() -> void
+{
+ return mozilla::jni::Method<EnableBatteryNotifications_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::EnableLocation_t::name[];
+constexpr char GeckoAppShell::EnableLocation_t::signature[];
+
+auto GeckoAppShell::EnableLocation(bool a0) -> void
+{
+ return mozilla::jni::Method<EnableLocation_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::EnableLocationHighAccuracy_t::name[];
+constexpr char GeckoAppShell::EnableLocationHighAccuracy_t::signature[];
+
+auto GeckoAppShell::EnableLocationHighAccuracy(bool a0) -> void
+{
+ return mozilla::jni::Method<EnableLocationHighAccuracy_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::EnableNetworkNotifications_t::name[];
+constexpr char GeckoAppShell::EnableNetworkNotifications_t::signature[];
+
+auto GeckoAppShell::EnableNetworkNotifications() -> void
+{
+ return mozilla::jni::Method<EnableNetworkNotifications_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::EnableScreenOrientationNotifications_t::name[];
+constexpr char GeckoAppShell::EnableScreenOrientationNotifications_t::signature[];
+
+auto GeckoAppShell::EnableScreenOrientationNotifications() -> void
+{
+ return mozilla::jni::Method<EnableScreenOrientationNotifications_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::EnableSensor_t::name[];
+constexpr char GeckoAppShell::EnableSensor_t::signature[];
+
+auto GeckoAppShell::EnableSensor(int32_t a0) -> void
+{
+ return mozilla::jni::Method<EnableSensor_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::GetApplicationContext_t::name[];
+constexpr char GeckoAppShell::GetApplicationContext_t::signature[];
+
+auto GeckoAppShell::GetApplicationContext() -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Method<GetApplicationContext_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetConnection_t::name[];
+constexpr char GeckoAppShell::GetConnection_t::signature[];
+
+auto GeckoAppShell::GetConnection(mozilla::jni::String::Param a0) -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Method<GetConnection_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::GetContext_t::name[];
+constexpr char GeckoAppShell::GetContext_t::signature[];
+
+auto GeckoAppShell::GetContext() -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Method<GetContext_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetCurrentBatteryInformation_t::name[];
+constexpr char GeckoAppShell::GetCurrentBatteryInformation_t::signature[];
+
+auto GeckoAppShell::GetCurrentBatteryInformation() -> mozilla::jni::DoubleArray::LocalRef
+{
+ return mozilla::jni::Method<GetCurrentBatteryInformation_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetCurrentNetworkInformation_t::name[];
+constexpr char GeckoAppShell::GetCurrentNetworkInformation_t::signature[];
+
+auto GeckoAppShell::GetCurrentNetworkInformation() -> mozilla::jni::DoubleArray::LocalRef
+{
+ return mozilla::jni::Method<GetCurrentNetworkInformation_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetDensity_t::name[];
+constexpr char GeckoAppShell::GetDensity_t::signature[];
+
+auto GeckoAppShell::GetDensity() -> float
+{
+ return mozilla::jni::Method<GetDensity_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetDpi_t::name[];
+constexpr char GeckoAppShell::GetDpi_t::signature[];
+
+auto GeckoAppShell::GetDpi() -> int32_t
+{
+ return mozilla::jni::Method<GetDpi_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetExceptionStackTrace_t::name[];
+constexpr char GeckoAppShell::GetExceptionStackTrace_t::signature[];
+
+auto GeckoAppShell::GetExceptionStackTrace(mozilla::jni::Throwable::Param a0) -> mozilla::jni::String::LocalRef
+{
+ return mozilla::jni::Method<GetExceptionStackTrace_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::GetExtensionFromMimeType_t::name[];
+constexpr char GeckoAppShell::GetExtensionFromMimeType_t::signature[];
+
+auto GeckoAppShell::GetExtensionFromMimeType(mozilla::jni::String::Param a0) -> mozilla::jni::String::LocalRef
+{
+ return mozilla::jni::Method<GetExtensionFromMimeType_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::GetHWDecoderCapability_t::name[];
+constexpr char GeckoAppShell::GetHWDecoderCapability_t::signature[];
+
+auto GeckoAppShell::GetHWDecoderCapability() -> bool
+{
+ return mozilla::jni::Method<GetHWDecoderCapability_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetHWEncoderCapability_t::name[];
+constexpr char GeckoAppShell::GetHWEncoderCapability_t::signature[];
+
+auto GeckoAppShell::GetHWEncoderCapability() -> bool
+{
+ return mozilla::jni::Method<GetHWEncoderCapability_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetHandlersForMimeType_t::name[];
+constexpr char GeckoAppShell::GetHandlersForMimeType_t::signature[];
+
+auto GeckoAppShell::GetHandlersForMimeType(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> mozilla::jni::ObjectArray::LocalRef
+{
+ return mozilla::jni::Method<GetHandlersForMimeType_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoAppShell::GetHandlersForURL_t::name[];
+constexpr char GeckoAppShell::GetHandlersForURL_t::signature[];
+
+auto GeckoAppShell::GetHandlersForURL(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> mozilla::jni::ObjectArray::LocalRef
+{
+ return mozilla::jni::Method<GetHandlersForURL_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoAppShell::GetIconForExtension_t::name[];
+constexpr char GeckoAppShell::GetIconForExtension_t::signature[];
+
+auto GeckoAppShell::GetIconForExtension(mozilla::jni::String::Param a0, int32_t a1) -> mozilla::jni::ByteArray::LocalRef
+{
+ return mozilla::jni::Method<GetIconForExtension_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoAppShell::GetMaxTouchPoints_t::name[];
+constexpr char GeckoAppShell::GetMaxTouchPoints_t::signature[];
+
+auto GeckoAppShell::GetMaxTouchPoints() -> int32_t
+{
+ return mozilla::jni::Method<GetMaxTouchPoints_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetMimeTypeFromExtensions_t::name[];
+constexpr char GeckoAppShell::GetMimeTypeFromExtensions_t::signature[];
+
+auto GeckoAppShell::GetMimeTypeFromExtensions(mozilla::jni::String::Param a0) -> mozilla::jni::String::LocalRef
+{
+ return mozilla::jni::Method<GetMimeTypeFromExtensions_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::GetNetworkLinkType_t::name[];
+constexpr char GeckoAppShell::GetNetworkLinkType_t::signature[];
+
+auto GeckoAppShell::GetNetworkLinkType() -> int32_t
+{
+ return mozilla::jni::Method<GetNetworkLinkType_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetProxyForURI_t::name[];
+constexpr char GeckoAppShell::GetProxyForURI_t::signature[];
+
+auto GeckoAppShell::GetProxyForURI(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, int32_t a3) -> mozilla::jni::String::LocalRef
+{
+ return mozilla::jni::Method<GetProxyForURI_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1, a2, a3);
+}
+
+constexpr char GeckoAppShell::GetScreenAngle_t::name[];
+constexpr char GeckoAppShell::GetScreenAngle_t::signature[];
+
+auto GeckoAppShell::GetScreenAngle() -> int32_t
+{
+ return mozilla::jni::Method<GetScreenAngle_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetScreenDepth_t::name[];
+constexpr char GeckoAppShell::GetScreenDepth_t::signature[];
+
+auto GeckoAppShell::GetScreenDepth() -> int32_t
+{
+ return mozilla::jni::Method<GetScreenDepth_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetScreenOrientation_t::name[];
+constexpr char GeckoAppShell::GetScreenOrientation_t::signature[];
+
+auto GeckoAppShell::GetScreenOrientation() -> int16_t
+{
+ return mozilla::jni::Method<GetScreenOrientation_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetScreenSize_t::name[];
+constexpr char GeckoAppShell::GetScreenSize_t::signature[];
+
+auto GeckoAppShell::GetScreenSize() -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Method<GetScreenSize_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetShowPasswordSetting_t::name[];
+constexpr char GeckoAppShell::GetShowPasswordSetting_t::signature[];
+
+auto GeckoAppShell::GetShowPasswordSetting() -> bool
+{
+ return mozilla::jni::Method<GetShowPasswordSetting_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::GetSystemColors_t::name[];
+constexpr char GeckoAppShell::GetSystemColors_t::signature[];
+
+auto GeckoAppShell::GetSystemColors() -> mozilla::jni::IntArray::LocalRef
+{
+ return mozilla::jni::Method<GetSystemColors_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::HandleGeckoMessage_t::name[];
+constexpr char GeckoAppShell::HandleGeckoMessage_t::signature[];
+
+auto GeckoAppShell::HandleGeckoMessage(mozilla::jni::Object::Param a0) -> void
+{
+ return mozilla::jni::Method<HandleGeckoMessage_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::HandleUncaughtException_t::name[];
+constexpr char GeckoAppShell::HandleUncaughtException_t::signature[];
+
+auto GeckoAppShell::HandleUncaughtException(mozilla::jni::Throwable::Param a0) -> void
+{
+ return mozilla::jni::Method<HandleUncaughtException_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::HideProgressDialog_t::name[];
+constexpr char GeckoAppShell::HideProgressDialog_t::signature[];
+
+auto GeckoAppShell::HideProgressDialog() -> void
+{
+ return mozilla::jni::Method<HideProgressDialog_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::InitCamera_t::name[];
+constexpr char GeckoAppShell::InitCamera_t::signature[];
+
+auto GeckoAppShell::InitCamera(mozilla::jni::String::Param a0, int32_t a1, int32_t a2, int32_t a3) -> mozilla::jni::IntArray::LocalRef
+{
+ return mozilla::jni::Method<InitCamera_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1, a2, a3);
+}
+
+constexpr char GeckoAppShell::IsNetworkLinkKnown_t::name[];
+constexpr char GeckoAppShell::IsNetworkLinkKnown_t::signature[];
+
+auto GeckoAppShell::IsNetworkLinkKnown() -> bool
+{
+ return mozilla::jni::Method<IsNetworkLinkKnown_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::IsNetworkLinkUp_t::name[];
+constexpr char GeckoAppShell::IsNetworkLinkUp_t::signature[];
+
+auto GeckoAppShell::IsNetworkLinkUp() -> bool
+{
+ return mozilla::jni::Method<IsNetworkLinkUp_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::IsTablet_t::name[];
+constexpr char GeckoAppShell::IsTablet_t::signature[];
+
+auto GeckoAppShell::IsTablet() -> bool
+{
+ return mozilla::jni::Method<IsTablet_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::KillAnyZombies_t::name[];
+constexpr char GeckoAppShell::KillAnyZombies_t::signature[];
+
+auto GeckoAppShell::KillAnyZombies() -> void
+{
+ return mozilla::jni::Method<KillAnyZombies_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::LoadPluginClass_t::name[];
+constexpr char GeckoAppShell::LoadPluginClass_t::signature[];
+
+auto GeckoAppShell::LoadPluginClass(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> mozilla::jni::Class::LocalRef
+{
+ return mozilla::jni::Method<LoadPluginClass_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoAppShell::LockScreenOrientation_t::name[];
+constexpr char GeckoAppShell::LockScreenOrientation_t::signature[];
+
+auto GeckoAppShell::LockScreenOrientation(int32_t a0) -> void
+{
+ return mozilla::jni::Method<LockScreenOrientation_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::MarkURIVisited_t::name[];
+constexpr char GeckoAppShell::MarkURIVisited_t::signature[];
+
+auto GeckoAppShell::MarkURIVisited(mozilla::jni::String::Param a0) -> void
+{
+ return mozilla::jni::Method<MarkURIVisited_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::MoveTaskToBack_t::name[];
+constexpr char GeckoAppShell::MoveTaskToBack_t::signature[];
+
+auto GeckoAppShell::MoveTaskToBack() -> void
+{
+ return mozilla::jni::Method<MoveTaskToBack_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::NotifyObservers_t::name[];
+constexpr char GeckoAppShell::NotifyObservers_t::signature[];
+
+constexpr char GeckoAppShell::NotifyAlertListener_t::name[];
+constexpr char GeckoAppShell::NotifyAlertListener_t::signature[];
+
+constexpr char GeckoAppShell::NotifyUriVisited_t::name[];
+constexpr char GeckoAppShell::NotifyUriVisited_t::signature[];
+
+constexpr char GeckoAppShell::NotifyWakeLockChanged_t::name[];
+constexpr char GeckoAppShell::NotifyWakeLockChanged_t::signature[];
+
+auto GeckoAppShell::NotifyWakeLockChanged(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> void
+{
+ return mozilla::jni::Method<NotifyWakeLockChanged_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoAppShell::OnFullScreenPluginHidden_t::name[];
+constexpr char GeckoAppShell::OnFullScreenPluginHidden_t::signature[];
+
+constexpr char GeckoAppShell::OnLocationChanged_t::name[];
+constexpr char GeckoAppShell::OnLocationChanged_t::signature[];
+
+constexpr char GeckoAppShell::OnSensorChanged_t::name[];
+constexpr char GeckoAppShell::OnSensorChanged_t::signature[];
+
+constexpr char GeckoAppShell::OpenUriExternal_t::name[];
+constexpr char GeckoAppShell::OpenUriExternal_t::signature[];
+
+auto GeckoAppShell::OpenUriExternal(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, mozilla::jni::String::Param a3, mozilla::jni::String::Param a4, mozilla::jni::String::Param a5) -> bool
+{
+ return mozilla::jni::Method<OpenUriExternal_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1, a2, a3, a4, a5);
+}
+
+constexpr char GeckoAppShell::OpenWindowForNotification_t::name[];
+constexpr char GeckoAppShell::OpenWindowForNotification_t::signature[];
+
+auto GeckoAppShell::OpenWindowForNotification() -> void
+{
+ return mozilla::jni::Method<OpenWindowForNotification_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::PerformHapticFeedback_t::name[];
+constexpr char GeckoAppShell::PerformHapticFeedback_t::signature[];
+
+auto GeckoAppShell::PerformHapticFeedback(bool a0) -> void
+{
+ return mozilla::jni::Method<PerformHapticFeedback_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::RemoveFullScreenPluginView_t::name[];
+constexpr char GeckoAppShell::RemoveFullScreenPluginView_t::signature[];
+
+auto GeckoAppShell::RemoveFullScreenPluginView(mozilla::jni::Object::Param a0) -> void
+{
+ return mozilla::jni::Method<RemoveFullScreenPluginView_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::ReportJavaCrash_t::name[];
+constexpr char GeckoAppShell::ReportJavaCrash_t::signature[];
+
+constexpr char GeckoAppShell::ScheduleRestart_t::name[];
+constexpr char GeckoAppShell::ScheduleRestart_t::signature[];
+
+auto GeckoAppShell::ScheduleRestart() -> void
+{
+ return mozilla::jni::Method<ScheduleRestart_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::SetAlarm_t::name[];
+constexpr char GeckoAppShell::SetAlarm_t::signature[];
+
+auto GeckoAppShell::SetAlarm(int32_t a0, int32_t a1) -> bool
+{
+ return mozilla::jni::Method<SetAlarm_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoAppShell::SetFullScreen_t::name[];
+constexpr char GeckoAppShell::SetFullScreen_t::signature[];
+
+auto GeckoAppShell::SetFullScreen(bool a0) -> void
+{
+ return mozilla::jni::Method<SetFullScreen_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::SetKeepScreenOn_t::name[];
+constexpr char GeckoAppShell::SetKeepScreenOn_t::signature[];
+
+auto GeckoAppShell::SetKeepScreenOn(bool a0) -> void
+{
+ return mozilla::jni::Method<SetKeepScreenOn_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::SetScreenDepthOverride_t::name[];
+constexpr char GeckoAppShell::SetScreenDepthOverride_t::signature[];
+
+auto GeckoAppShell::SetScreenDepthOverride(int32_t a0) -> void
+{
+ return mozilla::jni::Method<SetScreenDepthOverride_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::SetURITitle_t::name[];
+constexpr char GeckoAppShell::SetURITitle_t::signature[];
+
+auto GeckoAppShell::SetURITitle(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> void
+{
+ return mozilla::jni::Method<SetURITitle_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoAppShell::ShowNotification_t::name[];
+constexpr char GeckoAppShell::ShowNotification_t::signature[];
+
+auto GeckoAppShell::ShowNotification(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, mozilla::jni::String::Param a3, mozilla::jni::String::Param a4, mozilla::jni::String::Param a5, mozilla::jni::String::Param a6) -> void
+{
+ return mozilla::jni::Method<ShowNotification_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1, a2, a3, a4, a5, a6);
+}
+
+constexpr char GeckoAppShell::SyncNotifyObservers_t::name[];
+constexpr char GeckoAppShell::SyncNotifyObservers_t::signature[];
+
+constexpr char GeckoAppShell::UnlockProfile_t::name[];
+constexpr char GeckoAppShell::UnlockProfile_t::signature[];
+
+auto GeckoAppShell::UnlockProfile() -> bool
+{
+ return mozilla::jni::Method<UnlockProfile_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::UnlockScreenOrientation_t::name[];
+constexpr char GeckoAppShell::UnlockScreenOrientation_t::signature[];
+
+auto GeckoAppShell::UnlockScreenOrientation() -> void
+{
+ return mozilla::jni::Method<UnlockScreenOrientation_t>::Call(GeckoAppShell::Context(), nullptr);
+}
+
+constexpr char GeckoAppShell::Vibrate_t::name[];
+constexpr char GeckoAppShell::Vibrate_t::signature[];
+
+auto GeckoAppShell::Vibrate(int64_t a0) -> void
+{
+ return mozilla::jni::Method<Vibrate_t>::Call(GeckoAppShell::Context(), nullptr, a0);
+}
+
+constexpr char GeckoAppShell::Vibrate2_t::name[];
+constexpr char GeckoAppShell::Vibrate2_t::signature[];
+
+auto GeckoAppShell::Vibrate(mozilla::jni::LongArray::Param a0, int32_t a1) -> void
+{
+ return mozilla::jni::Method<Vibrate2_t>::Call(GeckoAppShell::Context(), nullptr, a0, a1);
+}
+
+const char GeckoAppShell::CameraCallback::name[] =
+ "org/mozilla/gecko/GeckoAppShell$CameraCallback";
+
+constexpr char GeckoAppShell::CameraCallback::OnFrameData_t::name[];
+constexpr char GeckoAppShell::CameraCallback::OnFrameData_t::signature[];
+
+const char GeckoBatteryManager::name[] =
+ "org/mozilla/gecko/GeckoBatteryManager";
+
+constexpr char GeckoBatteryManager::OnBatteryChange_t::name[];
+constexpr char GeckoBatteryManager::OnBatteryChange_t::signature[];
+
+const char GeckoEditable::name[] =
+ "org/mozilla/gecko/GeckoEditable";
+
+constexpr char GeckoEditable::New_t::name[];
+constexpr char GeckoEditable::New_t::signature[];
+
+auto GeckoEditable::New(mozilla::jni::Object::Param a0) -> GeckoEditable::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(GeckoEditable::Context(), nullptr, a0);
+}
+
+constexpr char GeckoEditable::DisposeNative_t::name[];
+constexpr char GeckoEditable::DisposeNative_t::signature[];
+
+constexpr char GeckoEditable::NotifyIME_t::name[];
+constexpr char GeckoEditable::NotifyIME_t::signature[];
+
+auto GeckoEditable::NotifyIME(int32_t a0) const -> void
+{
+ return mozilla::jni::Method<NotifyIME_t>::Call(GeckoEditable::mCtx, nullptr, a0);
+}
+
+constexpr char GeckoEditable::NotifyIMEContext_t::name[];
+constexpr char GeckoEditable::NotifyIMEContext_t::signature[];
+
+auto GeckoEditable::NotifyIMEContext(int32_t a0, mozilla::jni::String::Param a1, mozilla::jni::String::Param a2, mozilla::jni::String::Param a3) const -> void
+{
+ return mozilla::jni::Method<NotifyIMEContext_t>::Call(GeckoEditable::mCtx, nullptr, a0, a1, a2, a3);
+}
+
+constexpr char GeckoEditable::OnDefaultKeyEvent_t::name[];
+constexpr char GeckoEditable::OnDefaultKeyEvent_t::signature[];
+
+auto GeckoEditable::OnDefaultKeyEvent(mozilla::jni::Object::Param a0) const -> void
+{
+ return mozilla::jni::Method<OnDefaultKeyEvent_t>::Call(GeckoEditable::mCtx, nullptr, a0);
+}
+
+constexpr char GeckoEditable::OnImeAddCompositionRange_t::name[];
+constexpr char GeckoEditable::OnImeAddCompositionRange_t::signature[];
+
+constexpr char GeckoEditable::OnImeReplaceText_t::name[];
+constexpr char GeckoEditable::OnImeReplaceText_t::signature[];
+
+constexpr char GeckoEditable::OnImeRequestCursorUpdates_t::name[];
+constexpr char GeckoEditable::OnImeRequestCursorUpdates_t::signature[];
+
+constexpr char GeckoEditable::OnImeSynchronize_t::name[];
+constexpr char GeckoEditable::OnImeSynchronize_t::signature[];
+
+constexpr char GeckoEditable::OnImeUpdateComposition_t::name[];
+constexpr char GeckoEditable::OnImeUpdateComposition_t::signature[];
+
+constexpr char GeckoEditable::OnKeyEvent_t::name[];
+constexpr char GeckoEditable::OnKeyEvent_t::signature[];
+
+constexpr char GeckoEditable::OnSelectionChange_t::name[];
+constexpr char GeckoEditable::OnSelectionChange_t::signature[];
+
+auto GeckoEditable::OnSelectionChange(int32_t a0, int32_t a1) const -> void
+{
+ return mozilla::jni::Method<OnSelectionChange_t>::Call(GeckoEditable::mCtx, nullptr, a0, a1);
+}
+
+constexpr char GeckoEditable::OnTextChange_t::name[];
+constexpr char GeckoEditable::OnTextChange_t::signature[];
+
+auto GeckoEditable::OnTextChange(mozilla::jni::String::Param a0, int32_t a1, int32_t a2, int32_t a3) const -> void
+{
+ return mozilla::jni::Method<OnTextChange_t>::Call(GeckoEditable::mCtx, nullptr, a0, a1, a2, a3);
+}
+
+constexpr char GeckoEditable::OnViewChange_t::name[];
+constexpr char GeckoEditable::OnViewChange_t::signature[];
+
+auto GeckoEditable::OnViewChange(mozilla::jni::Object::Param a0) const -> void
+{
+ return mozilla::jni::Method<OnViewChange_t>::Call(GeckoEditable::mCtx, nullptr, a0);
+}
+
+constexpr char GeckoEditable::UpdateCompositionRects_t::name[];
+constexpr char GeckoEditable::UpdateCompositionRects_t::signature[];
+
+auto GeckoEditable::UpdateCompositionRects(mozilla::jni::ObjectArray::Param a0) const -> void
+{
+ return mozilla::jni::Method<UpdateCompositionRects_t>::Call(GeckoEditable::mCtx, nullptr, a0);
+}
+
+const char GeckoEditableListener::name[] =
+ "org/mozilla/gecko/GeckoEditableListener";
+
+const char GeckoNetworkManager::name[] =
+ "org/mozilla/gecko/GeckoNetworkManager";
+
+constexpr char GeckoNetworkManager::OnConnectionChanged_t::name[];
+constexpr char GeckoNetworkManager::OnConnectionChanged_t::signature[];
+
+constexpr char GeckoNetworkManager::OnStatusChanged_t::name[];
+constexpr char GeckoNetworkManager::OnStatusChanged_t::signature[];
+
+const char GeckoScreenOrientation::name[] =
+ "org/mozilla/gecko/GeckoScreenOrientation";
+
+constexpr char GeckoScreenOrientation::OnOrientationChange_t::name[];
+constexpr char GeckoScreenOrientation::OnOrientationChange_t::signature[];
+
+const char GeckoThread::name[] =
+ "org/mozilla/gecko/GeckoThread";
+
+constexpr char GeckoThread::CheckAndSetState_t::name[];
+constexpr char GeckoThread::CheckAndSetState_t::signature[];
+
+auto GeckoThread::CheckAndSetState(mozilla::jni::Object::Param a0, mozilla::jni::Object::Param a1) -> bool
+{
+ return mozilla::jni::Method<CheckAndSetState_t>::Call(GeckoThread::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoThread::CreateServices_t::name[];
+constexpr char GeckoThread::CreateServices_t::signature[];
+
+constexpr char GeckoThread::OnPause_t::name[];
+constexpr char GeckoThread::OnPause_t::signature[];
+
+constexpr char GeckoThread::OnResume_t::name[];
+constexpr char GeckoThread::OnResume_t::signature[];
+
+constexpr char GeckoThread::PumpMessageLoop_t::name[];
+constexpr char GeckoThread::PumpMessageLoop_t::signature[];
+
+auto GeckoThread::PumpMessageLoop(mozilla::jni::Object::Param a0) -> bool
+{
+ return mozilla::jni::Method<PumpMessageLoop_t>::Call(GeckoThread::Context(), nullptr, a0);
+}
+
+constexpr char GeckoThread::RequestUiThreadCallback_t::name[];
+constexpr char GeckoThread::RequestUiThreadCallback_t::signature[];
+
+auto GeckoThread::RequestUiThreadCallback(int64_t a0) -> void
+{
+ return mozilla::jni::Method<RequestUiThreadCallback_t>::Call(GeckoThread::Context(), nullptr, a0);
+}
+
+constexpr char GeckoThread::RunUiThreadCallback_t::name[];
+constexpr char GeckoThread::RunUiThreadCallback_t::signature[];
+
+constexpr char GeckoThread::SetState_t::name[];
+constexpr char GeckoThread::SetState_t::signature[];
+
+auto GeckoThread::SetState(mozilla::jni::Object::Param a0) -> void
+{
+ return mozilla::jni::Method<SetState_t>::Call(GeckoThread::Context(), nullptr, a0);
+}
+
+constexpr char GeckoThread::SpeculativeConnect_t::name[];
+constexpr char GeckoThread::SpeculativeConnect_t::signature[];
+
+constexpr char GeckoThread::WaitOnGecko_t::name[];
+constexpr char GeckoThread::WaitOnGecko_t::signature[];
+
+constexpr char GeckoThread::ClsLoader_t::name[];
+constexpr char GeckoThread::ClsLoader_t::signature[];
+
+auto GeckoThread::ClsLoader() -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Field<ClsLoader_t>::Get(GeckoThread::Context(), nullptr);
+}
+
+constexpr char GeckoThread::MsgQueue_t::name[];
+constexpr char GeckoThread::MsgQueue_t::signature[];
+
+auto GeckoThread::MsgQueue() -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Field<MsgQueue_t>::Get(GeckoThread::Context(), nullptr);
+}
+
+auto GeckoThread::MsgQueue(mozilla::jni::Object::Param a0) -> void
+{
+ return mozilla::jni::Field<MsgQueue_t>::Set(GeckoThread::Context(), nullptr, a0);
+}
+
+const char GeckoThread::State::name[] =
+ "org/mozilla/gecko/GeckoThread$State";
+
+constexpr char GeckoThread::State::EXITED_t::name[];
+constexpr char GeckoThread::State::EXITED_t::signature[];
+
+auto GeckoThread::State::EXITED() -> State::LocalRef
+{
+ return mozilla::jni::Field<EXITED_t>::Get(State::Context(), nullptr);
+}
+
+constexpr char GeckoThread::State::EXITING_t::name[];
+constexpr char GeckoThread::State::EXITING_t::signature[];
+
+auto GeckoThread::State::EXITING() -> State::LocalRef
+{
+ return mozilla::jni::Field<EXITING_t>::Get(State::Context(), nullptr);
+}
+
+constexpr char GeckoThread::State::INITIAL_t::name[];
+constexpr char GeckoThread::State::INITIAL_t::signature[];
+
+auto GeckoThread::State::INITIAL() -> State::LocalRef
+{
+ return mozilla::jni::Field<INITIAL_t>::Get(State::Context(), nullptr);
+}
+
+constexpr char GeckoThread::State::JNI_READY_t::name[];
+constexpr char GeckoThread::State::JNI_READY_t::signature[];
+
+auto GeckoThread::State::JNI_READY() -> State::LocalRef
+{
+ return mozilla::jni::Field<JNI_READY_t>::Get(State::Context(), nullptr);
+}
+
+constexpr char GeckoThread::State::LAUNCHED_t::name[];
+constexpr char GeckoThread::State::LAUNCHED_t::signature[];
+
+auto GeckoThread::State::LAUNCHED() -> State::LocalRef
+{
+ return mozilla::jni::Field<LAUNCHED_t>::Get(State::Context(), nullptr);
+}
+
+constexpr char GeckoThread::State::LIBS_READY_t::name[];
+constexpr char GeckoThread::State::LIBS_READY_t::signature[];
+
+auto GeckoThread::State::LIBS_READY() -> State::LocalRef
+{
+ return mozilla::jni::Field<LIBS_READY_t>::Get(State::Context(), nullptr);
+}
+
+constexpr char GeckoThread::State::MOZGLUE_READY_t::name[];
+constexpr char GeckoThread::State::MOZGLUE_READY_t::signature[];
+
+auto GeckoThread::State::MOZGLUE_READY() -> State::LocalRef
+{
+ return mozilla::jni::Field<MOZGLUE_READY_t>::Get(State::Context(), nullptr);
+}
+
+constexpr char GeckoThread::State::PROFILE_READY_t::name[];
+constexpr char GeckoThread::State::PROFILE_READY_t::signature[];
+
+auto GeckoThread::State::PROFILE_READY() -> State::LocalRef
+{
+ return mozilla::jni::Field<PROFILE_READY_t>::Get(State::Context(), nullptr);
+}
+
+constexpr char GeckoThread::State::RUNNING_t::name[];
+constexpr char GeckoThread::State::RUNNING_t::signature[];
+
+auto GeckoThread::State::RUNNING() -> State::LocalRef
+{
+ return mozilla::jni::Field<RUNNING_t>::Get(State::Context(), nullptr);
+}
+
+const char GeckoView::name[] =
+ "org/mozilla/gecko/GeckoView";
+
+const char GeckoView::Window::name[] =
+ "org/mozilla/gecko/GeckoView$Window";
+
+constexpr char GeckoView::Window::Close_t::name[];
+constexpr char GeckoView::Window::Close_t::signature[];
+
+constexpr char GeckoView::Window::DisposeNative_t::name[];
+constexpr char GeckoView::Window::DisposeNative_t::signature[];
+
+constexpr char GeckoView::Window::LoadUri_t::name[];
+constexpr char GeckoView::Window::LoadUri_t::signature[];
+
+constexpr char GeckoView::Window::Open_t::name[];
+constexpr char GeckoView::Window::Open_t::signature[];
+
+constexpr char GeckoView::Window::Reattach_t::name[];
+constexpr char GeckoView::Window::Reattach_t::signature[];
+
+const char PrefsHelper::name[] =
+ "org/mozilla/gecko/PrefsHelper";
+
+constexpr char PrefsHelper::CallPrefHandler_t::name[];
+constexpr char PrefsHelper::CallPrefHandler_t::signature[];
+
+auto PrefsHelper::CallPrefHandler(mozilla::jni::Object::Param a0, int32_t a1, mozilla::jni::String::Param a2, bool a3, int32_t a4, mozilla::jni::String::Param a5) -> void
+{
+ return mozilla::jni::Method<CallPrefHandler_t>::Call(PrefsHelper::Context(), nullptr, a0, a1, a2, a3, a4, a5);
+}
+
+constexpr char PrefsHelper::AddObserver_t::name[];
+constexpr char PrefsHelper::AddObserver_t::signature[];
+
+constexpr char PrefsHelper::GetPrefs_t::name[];
+constexpr char PrefsHelper::GetPrefs_t::signature[];
+
+constexpr char PrefsHelper::RemoveObserver_t::name[];
+constexpr char PrefsHelper::RemoveObserver_t::signature[];
+
+constexpr char PrefsHelper::SetPref_t::name[];
+constexpr char PrefsHelper::SetPref_t::signature[];
+
+constexpr char PrefsHelper::OnPrefChange_t::name[];
+constexpr char PrefsHelper::OnPrefChange_t::signature[];
+
+auto PrefsHelper::OnPrefChange(mozilla::jni::String::Param a0, int32_t a1, bool a2, int32_t a3, mozilla::jni::String::Param a4) -> void
+{
+ return mozilla::jni::Method<OnPrefChange_t>::Call(PrefsHelper::Context(), nullptr, a0, a1, a2, a3, a4);
+}
+
+const char SurfaceTextureListener::name[] =
+ "org/mozilla/gecko/SurfaceTextureListener";
+
+constexpr char SurfaceTextureListener::New_t::name[];
+constexpr char SurfaceTextureListener::New_t::signature[];
+
+auto SurfaceTextureListener::New() -> SurfaceTextureListener::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(SurfaceTextureListener::Context(), nullptr);
+}
+
+constexpr char SurfaceTextureListener::OnFrameAvailable_t::name[];
+constexpr char SurfaceTextureListener::OnFrameAvailable_t::signature[];
+
+const char GeckoLayerClient::name[] =
+ "org/mozilla/gecko/gfx/GeckoLayerClient";
+
+constexpr char GeckoLayerClient::ContentDocumentChanged_t::name[];
+constexpr char GeckoLayerClient::ContentDocumentChanged_t::signature[];
+
+auto GeckoLayerClient::ContentDocumentChanged() const -> void
+{
+ return mozilla::jni::Method<ContentDocumentChanged_t>::Call(GeckoLayerClient::mCtx, nullptr);
+}
+
+constexpr char GeckoLayerClient::CreateFrame_t::name[];
+constexpr char GeckoLayerClient::CreateFrame_t::signature[];
+
+auto GeckoLayerClient::CreateFrame() const -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Method<CreateFrame_t>::Call(GeckoLayerClient::mCtx, nullptr);
+}
+
+constexpr char GeckoLayerClient::IsContentDocumentDisplayed_t::name[];
+constexpr char GeckoLayerClient::IsContentDocumentDisplayed_t::signature[];
+
+auto GeckoLayerClient::IsContentDocumentDisplayed() const -> bool
+{
+ return mozilla::jni::Method<IsContentDocumentDisplayed_t>::Call(GeckoLayerClient::mCtx, nullptr);
+}
+
+constexpr char GeckoLayerClient::OnGeckoReady_t::name[];
+constexpr char GeckoLayerClient::OnGeckoReady_t::signature[];
+
+auto GeckoLayerClient::OnGeckoReady() const -> void
+{
+ return mozilla::jni::Method<OnGeckoReady_t>::Call(GeckoLayerClient::mCtx, nullptr);
+}
+
+constexpr char GeckoLayerClient::SetFirstPaintViewport_t::name[];
+constexpr char GeckoLayerClient::SetFirstPaintViewport_t::signature[];
+
+auto GeckoLayerClient::SetFirstPaintViewport(float a0, float a1, float a2, float a3, float a4, float a5, float a6) const -> void
+{
+ return mozilla::jni::Method<SetFirstPaintViewport_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5, a6);
+}
+
+constexpr char GeckoLayerClient::SyncFrameMetrics_t::name[];
+constexpr char GeckoLayerClient::SyncFrameMetrics_t::signature[];
+
+auto GeckoLayerClient::SyncFrameMetrics(float a0, float a1, float a2, float a3, float a4, float a5, float a6, int32_t a7, int32_t a8, int32_t a9, int32_t a10, float a11, bool a12, int32_t a13) const -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Method<SyncFrameMetrics_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13);
+}
+
+constexpr char GeckoLayerClient::SynthesizeNativeMouseEvent_t::name[];
+constexpr char GeckoLayerClient::SynthesizeNativeMouseEvent_t::signature[];
+
+auto GeckoLayerClient::SynthesizeNativeMouseEvent(int32_t a0, int32_t a1, int32_t a2) const -> void
+{
+ return mozilla::jni::Method<SynthesizeNativeMouseEvent_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2);
+}
+
+constexpr char GeckoLayerClient::SynthesizeNativeTouchPoint_t::name[];
+constexpr char GeckoLayerClient::SynthesizeNativeTouchPoint_t::signature[];
+
+auto GeckoLayerClient::SynthesizeNativeTouchPoint(int32_t a0, int32_t a1, int32_t a2, int32_t a3, double a4, int32_t a5) const -> void
+{
+ return mozilla::jni::Method<SynthesizeNativeTouchPoint_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5);
+}
+
+constexpr char GeckoLayerClient::ClearColor_t::name[];
+constexpr char GeckoLayerClient::ClearColor_t::signature[];
+
+auto GeckoLayerClient::ClearColor() const -> int32_t
+{
+ return mozilla::jni::Field<ClearColor_t>::Get(GeckoLayerClient::mCtx, nullptr);
+}
+
+auto GeckoLayerClient::ClearColor(int32_t a0) const -> void
+{
+ return mozilla::jni::Field<ClearColor_t>::Set(GeckoLayerClient::mCtx, nullptr, a0);
+}
+
+const char ImmutableViewportMetrics::name[] =
+ "org/mozilla/gecko/gfx/ImmutableViewportMetrics";
+
+constexpr char ImmutableViewportMetrics::New_t::name[];
+constexpr char ImmutableViewportMetrics::New_t::signature[];
+
+auto ImmutableViewportMetrics::New(float a0, float a1, float a2, float a3, float a4, float a5, float a6, float a7, float a8, float a9, int32_t a10, int32_t a11, float a12) -> ImmutableViewportMetrics::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(ImmutableViewportMetrics::Context(), nullptr, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
+}
+
+const char LayerRenderer::name[] =
+ "org/mozilla/gecko/gfx/LayerRenderer";
+
+const char LayerRenderer::Frame::name[] =
+ "org/mozilla/gecko/gfx/LayerRenderer$Frame";
+
+constexpr char LayerRenderer::Frame::BeginDrawing_t::name[];
+constexpr char LayerRenderer::Frame::BeginDrawing_t::signature[];
+
+auto LayerRenderer::Frame::BeginDrawing() const -> void
+{
+ return mozilla::jni::Method<BeginDrawing_t>::Call(Frame::mCtx, nullptr);
+}
+
+constexpr char LayerRenderer::Frame::EndDrawing_t::name[];
+constexpr char LayerRenderer::Frame::EndDrawing_t::signature[];
+
+auto LayerRenderer::Frame::EndDrawing() const -> void
+{
+ return mozilla::jni::Method<EndDrawing_t>::Call(Frame::mCtx, nullptr);
+}
+
+const char LayerView::name[] =
+ "org/mozilla/gecko/gfx/LayerView";
+
+constexpr char LayerView::GetCompositor_t::name[];
+constexpr char LayerView::GetCompositor_t::signature[];
+
+auto LayerView::GetCompositor() const -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Method<GetCompositor_t>::Call(LayerView::mCtx, nullptr);
+}
+
+constexpr char LayerView::UpdateZoomedView_t::name[];
+constexpr char LayerView::UpdateZoomedView_t::signature[];
+
+auto LayerView::UpdateZoomedView(mozilla::jni::ByteBuffer::Param a0) -> void
+{
+ return mozilla::jni::Method<UpdateZoomedView_t>::Call(LayerView::Context(), nullptr, a0);
+}
+
+constexpr char LayerView::CompositorCreated_t::name[];
+constexpr char LayerView::CompositorCreated_t::signature[];
+
+auto LayerView::CompositorCreated() const -> bool
+{
+ return mozilla::jni::Field<CompositorCreated_t>::Get(LayerView::mCtx, nullptr);
+}
+
+auto LayerView::CompositorCreated(bool a0) const -> void
+{
+ return mozilla::jni::Field<CompositorCreated_t>::Set(LayerView::mCtx, nullptr, a0);
+}
+
+const char LayerView::Compositor::name[] =
+ "org/mozilla/gecko/gfx/LayerView$Compositor";
+
+constexpr char LayerView::Compositor::AttachToJava_t::name[];
+constexpr char LayerView::Compositor::AttachToJava_t::signature[];
+
+constexpr char LayerView::Compositor::CreateCompositor_t::name[];
+constexpr char LayerView::Compositor::CreateCompositor_t::signature[];
+
+constexpr char LayerView::Compositor::Destroy_t::name[];
+constexpr char LayerView::Compositor::Destroy_t::signature[];
+
+auto LayerView::Compositor::Destroy() const -> void
+{
+ return mozilla::jni::Method<Destroy_t>::Call(Compositor::mCtx, nullptr);
+}
+
+constexpr char LayerView::Compositor::DisposeNative_t::name[];
+constexpr char LayerView::Compositor::DisposeNative_t::signature[];
+
+constexpr char LayerView::Compositor::OnSizeChanged_t::name[];
+constexpr char LayerView::Compositor::OnSizeChanged_t::signature[];
+
+constexpr char LayerView::Compositor::Reattach_t::name[];
+constexpr char LayerView::Compositor::Reattach_t::signature[];
+
+auto LayerView::Compositor::Reattach() const -> void
+{
+ return mozilla::jni::Method<Reattach_t>::Call(Compositor::mCtx, nullptr);
+}
+
+constexpr char LayerView::Compositor::SyncInvalidateAndScheduleComposite_t::name[];
+constexpr char LayerView::Compositor::SyncInvalidateAndScheduleComposite_t::signature[];
+
+constexpr char LayerView::Compositor::SyncPauseCompositor_t::name[];
+constexpr char LayerView::Compositor::SyncPauseCompositor_t::signature[];
+
+constexpr char LayerView::Compositor::SyncResumeResizeCompositor_t::name[];
+constexpr char LayerView::Compositor::SyncResumeResizeCompositor_t::signature[];
+
+const char NativePanZoomController::name[] =
+ "org/mozilla/gecko/gfx/NativePanZoomController";
+
+constexpr char NativePanZoomController::AdjustScrollForSurfaceShift_t::name[];
+constexpr char NativePanZoomController::AdjustScrollForSurfaceShift_t::signature[];
+
+constexpr char NativePanZoomController::Destroy_t::name[];
+constexpr char NativePanZoomController::Destroy_t::signature[];
+
+auto NativePanZoomController::Destroy() const -> void
+{
+ return mozilla::jni::Method<Destroy_t>::Call(NativePanZoomController::mCtx, nullptr);
+}
+
+constexpr char NativePanZoomController::DisposeNative_t::name[];
+constexpr char NativePanZoomController::DisposeNative_t::signature[];
+
+constexpr char NativePanZoomController::HandleMotionEvent_t::name[];
+constexpr char NativePanZoomController::HandleMotionEvent_t::signature[];
+
+constexpr char NativePanZoomController::HandleMotionEventVelocity_t::name[];
+constexpr char NativePanZoomController::HandleMotionEventVelocity_t::signature[];
+
+constexpr char NativePanZoomController::HandleMouseEvent_t::name[];
+constexpr char NativePanZoomController::HandleMouseEvent_t::signature[];
+
+constexpr char NativePanZoomController::HandleScrollEvent_t::name[];
+constexpr char NativePanZoomController::HandleScrollEvent_t::signature[];
+
+constexpr char NativePanZoomController::SetIsLongpressEnabled_t::name[];
+constexpr char NativePanZoomController::SetIsLongpressEnabled_t::signature[];
+
+constexpr char NativePanZoomController::OnSelectionDragState_t::name[];
+constexpr char NativePanZoomController::OnSelectionDragState_t::signature[];
+
+auto NativePanZoomController::OnSelectionDragState(bool a0) const -> void
+{
+ return mozilla::jni::Method<OnSelectionDragState_t>::Call(NativePanZoomController::mCtx, nullptr, a0);
+}
+
+constexpr char NativePanZoomController::SetScrollingRootContent_t::name[];
+constexpr char NativePanZoomController::SetScrollingRootContent_t::signature[];
+
+auto NativePanZoomController::SetScrollingRootContent(bool a0) const -> void
+{
+ return mozilla::jni::Method<SetScrollingRootContent_t>::Call(NativePanZoomController::mCtx, nullptr, a0);
+}
+
+constexpr char NativePanZoomController::UpdateOverscrollOffset_t::name[];
+constexpr char NativePanZoomController::UpdateOverscrollOffset_t::signature[];
+
+auto NativePanZoomController::UpdateOverscrollOffset(float a0, float a1) const -> void
+{
+ return mozilla::jni::Method<UpdateOverscrollOffset_t>::Call(NativePanZoomController::mCtx, nullptr, a0, a1);
+}
+
+constexpr char NativePanZoomController::UpdateOverscrollVelocity_t::name[];
+constexpr char NativePanZoomController::UpdateOverscrollVelocity_t::signature[];
+
+auto NativePanZoomController::UpdateOverscrollVelocity(float a0, float a1) const -> void
+{
+ return mozilla::jni::Method<UpdateOverscrollVelocity_t>::Call(NativePanZoomController::mCtx, nullptr, a0, a1);
+}
+
+const char ProgressiveUpdateData::name[] =
+ "org/mozilla/gecko/gfx/ProgressiveUpdateData";
+
+constexpr char ProgressiveUpdateData::New_t::name[];
+constexpr char ProgressiveUpdateData::New_t::signature[];
+
+auto ProgressiveUpdateData::New() -> ProgressiveUpdateData::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(ProgressiveUpdateData::Context(), nullptr);
+}
+
+constexpr char ProgressiveUpdateData::SetViewport_t::name[];
+constexpr char ProgressiveUpdateData::SetViewport_t::signature[];
+
+auto ProgressiveUpdateData::SetViewport(mozilla::jni::Object::Param a0) const -> void
+{
+ return mozilla::jni::Method<SetViewport_t>::Call(ProgressiveUpdateData::mCtx, nullptr, a0);
+}
+
+constexpr char ProgressiveUpdateData::Abort_t::name[];
+constexpr char ProgressiveUpdateData::Abort_t::signature[];
+
+auto ProgressiveUpdateData::Abort() const -> bool
+{
+ return mozilla::jni::Field<Abort_t>::Get(ProgressiveUpdateData::mCtx, nullptr);
+}
+
+auto ProgressiveUpdateData::Abort(bool a0) const -> void
+{
+ return mozilla::jni::Field<Abort_t>::Set(ProgressiveUpdateData::mCtx, nullptr, a0);
+}
+
+constexpr char ProgressiveUpdateData::Scale_t::name[];
+constexpr char ProgressiveUpdateData::Scale_t::signature[];
+
+auto ProgressiveUpdateData::Scale() const -> float
+{
+ return mozilla::jni::Field<Scale_t>::Get(ProgressiveUpdateData::mCtx, nullptr);
+}
+
+auto ProgressiveUpdateData::Scale(float a0) const -> void
+{
+ return mozilla::jni::Field<Scale_t>::Set(ProgressiveUpdateData::mCtx, nullptr, a0);
+}
+
+constexpr char ProgressiveUpdateData::X_t::name[];
+constexpr char ProgressiveUpdateData::X_t::signature[];
+
+auto ProgressiveUpdateData::X() const -> float
+{
+ return mozilla::jni::Field<X_t>::Get(ProgressiveUpdateData::mCtx, nullptr);
+}
+
+auto ProgressiveUpdateData::X(float a0) const -> void
+{
+ return mozilla::jni::Field<X_t>::Set(ProgressiveUpdateData::mCtx, nullptr, a0);
+}
+
+constexpr char ProgressiveUpdateData::Y_t::name[];
+constexpr char ProgressiveUpdateData::Y_t::signature[];
+
+auto ProgressiveUpdateData::Y() const -> float
+{
+ return mozilla::jni::Field<Y_t>::Get(ProgressiveUpdateData::mCtx, nullptr);
+}
+
+auto ProgressiveUpdateData::Y(float a0) const -> void
+{
+ return mozilla::jni::Field<Y_t>::Set(ProgressiveUpdateData::mCtx, nullptr, a0);
+}
+
+const char StackScroller::name[] =
+ "org/mozilla/gecko/gfx/StackScroller";
+
+constexpr char StackScroller::New_t::name[];
+constexpr char StackScroller::New_t::signature[];
+
+auto StackScroller::New(mozilla::jni::Object::Param a0, StackScroller::LocalRef* a1) -> nsresult
+{
+ MOZ_ASSERT(a1);
+ nsresult rv = NS_OK;
+ *a1 = mozilla::jni::Constructor<New_t>::Call(StackScroller::Context(), &rv, a0);
+ return rv;
+}
+
+constexpr char StackScroller::AbortAnimation_t::name[];
+constexpr char StackScroller::AbortAnimation_t::signature[];
+
+auto StackScroller::AbortAnimation() const -> nsresult
+{
+ nsresult rv = NS_OK;
+ mozilla::jni::Method<AbortAnimation_t>::Call(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::ComputeScrollOffset_t::name[];
+constexpr char StackScroller::ComputeScrollOffset_t::signature[];
+
+auto StackScroller::ComputeScrollOffset(int64_t a0, bool* a1) const -> nsresult
+{
+ MOZ_ASSERT(a1);
+ nsresult rv = NS_OK;
+ *a1 = mozilla::jni::Method<ComputeScrollOffset_t>::Call(StackScroller::mCtx, &rv, a0);
+ return rv;
+}
+
+constexpr char StackScroller::Fling_t::name[];
+constexpr char StackScroller::Fling_t::signature[];
+
+auto StackScroller::Fling(int32_t a0, int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8, int32_t a9, int64_t a10) const -> nsresult
+{
+ nsresult rv = NS_OK;
+ mozilla::jni::Method<Fling_t>::Call(StackScroller::mCtx, &rv, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
+ return rv;
+}
+
+constexpr char StackScroller::ForceFinished_t::name[];
+constexpr char StackScroller::ForceFinished_t::signature[];
+
+auto StackScroller::ForceFinished(bool a0) const -> nsresult
+{
+ nsresult rv = NS_OK;
+ mozilla::jni::Method<ForceFinished_t>::Call(StackScroller::mCtx, &rv, a0);
+ return rv;
+}
+
+constexpr char StackScroller::GetCurrSpeedX_t::name[];
+constexpr char StackScroller::GetCurrSpeedX_t::signature[];
+
+auto StackScroller::GetCurrSpeedX(float* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Method<GetCurrSpeedX_t>::Call(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::GetCurrSpeedY_t::name[];
+constexpr char StackScroller::GetCurrSpeedY_t::signature[];
+
+auto StackScroller::GetCurrSpeedY(float* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Method<GetCurrSpeedY_t>::Call(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::GetCurrX_t::name[];
+constexpr char StackScroller::GetCurrX_t::signature[];
+
+auto StackScroller::GetCurrX(int32_t* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Method<GetCurrX_t>::Call(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::GetCurrY_t::name[];
+constexpr char StackScroller::GetCurrY_t::signature[];
+
+auto StackScroller::GetCurrY(int32_t* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Method<GetCurrY_t>::Call(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::GetFinalX_t::name[];
+constexpr char StackScroller::GetFinalX_t::signature[];
+
+auto StackScroller::GetFinalX(int32_t* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Method<GetFinalX_t>::Call(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::GetFinalY_t::name[];
+constexpr char StackScroller::GetFinalY_t::signature[];
+
+auto StackScroller::GetFinalY(int32_t* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Method<GetFinalY_t>::Call(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::InitContants_t::name[];
+constexpr char StackScroller::InitContants_t::signature[];
+
+auto StackScroller::InitContants() -> nsresult
+{
+ nsresult rv = NS_OK;
+ mozilla::jni::Method<InitContants_t>::Call(StackScroller::Context(), &rv);
+ return rv;
+}
+
+constexpr char StackScroller::IsFinished_t::name[];
+constexpr char StackScroller::IsFinished_t::signature[];
+
+auto StackScroller::IsFinished(bool* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Method<IsFinished_t>::Call(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::SetFinalX_t::name[];
+constexpr char StackScroller::SetFinalX_t::signature[];
+
+auto StackScroller::SetFinalX(int32_t a0) const -> nsresult
+{
+ nsresult rv = NS_OK;
+ mozilla::jni::Method<SetFinalX_t>::Call(StackScroller::mCtx, &rv, a0);
+ return rv;
+}
+
+constexpr char StackScroller::SpringBack_t::name[];
+constexpr char StackScroller::SpringBack_t::signature[];
+
+auto StackScroller::SpringBack(int32_t a0, int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int64_t a6, bool* a7) const -> nsresult
+{
+ MOZ_ASSERT(a7);
+ nsresult rv = NS_OK;
+ *a7 = mozilla::jni::Method<SpringBack_t>::Call(StackScroller::mCtx, &rv, a0, a1, a2, a3, a4, a5, a6);
+ return rv;
+}
+
+constexpr char StackScroller::StartScroll_t::name[];
+constexpr char StackScroller::StartScroll_t::signature[];
+
+auto StackScroller::StartScroll(int32_t a0, int32_t a1, int32_t a2, int32_t a3, int64_t a4, int32_t a5) const -> nsresult
+{
+ nsresult rv = NS_OK;
+ mozilla::jni::Method<StartScroll_t>::Call(StackScroller::mCtx, &rv, a0, a1, a2, a3, a4, a5);
+ return rv;
+}
+
+constexpr char StackScroller::ViscousFluid_t::name[];
+constexpr char StackScroller::ViscousFluid_t::signature[];
+
+auto StackScroller::ViscousFluid(float a0, float* a1) -> nsresult
+{
+ MOZ_ASSERT(a1);
+ nsresult rv = NS_OK;
+ *a1 = mozilla::jni::Method<ViscousFluid_t>::Call(StackScroller::Context(), &rv, a0);
+ return rv;
+}
+
+constexpr char StackScroller::MFlywheel_t::name[];
+constexpr char StackScroller::MFlywheel_t::signature[];
+
+auto StackScroller::MFlywheel(bool* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Field<MFlywheel_t>::Get(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::MMode_t::name[];
+constexpr char StackScroller::MMode_t::signature[];
+
+auto StackScroller::MMode(int32_t* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Field<MMode_t>::Get(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+auto StackScroller::MMode(int32_t a0) const -> nsresult
+{
+ nsresult rv = NS_OK;
+ mozilla::jni::Field<MMode_t>::Set(StackScroller::mCtx, &rv, a0);
+ return rv;
+}
+
+constexpr char StackScroller::MScrollerX_t::name[];
+constexpr char StackScroller::MScrollerX_t::signature[];
+
+auto StackScroller::MScrollerX(mozilla::jni::Object::LocalRef* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Field<MScrollerX_t>::Get(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::MScrollerY_t::name[];
+constexpr char StackScroller::MScrollerY_t::signature[];
+
+auto StackScroller::MScrollerY(mozilla::jni::Object::LocalRef* a0) const -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Field<MScrollerY_t>::Get(StackScroller::mCtx, &rv);
+ return rv;
+}
+
+constexpr char StackScroller::SViscousFluidNormalize_t::name[];
+constexpr char StackScroller::SViscousFluidNormalize_t::signature[];
+
+auto StackScroller::SViscousFluidNormalize(float* a0) -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Field<SViscousFluidNormalize_t>::Get(StackScroller::Context(), &rv);
+ return rv;
+}
+
+auto StackScroller::SViscousFluidNormalize(float a0) -> nsresult
+{
+ nsresult rv = NS_OK;
+ mozilla::jni::Field<SViscousFluidNormalize_t>::Set(StackScroller::Context(), &rv, a0);
+ return rv;
+}
+
+constexpr char StackScroller::SViscousFluidScale_t::name[];
+constexpr char StackScroller::SViscousFluidScale_t::signature[];
+
+auto StackScroller::SViscousFluidScale(float* a0) -> nsresult
+{
+ MOZ_ASSERT(a0);
+ nsresult rv = NS_OK;
+ *a0 = mozilla::jni::Field<SViscousFluidScale_t>::Get(StackScroller::Context(), &rv);
+ return rv;
+}
+
+auto StackScroller::SViscousFluidScale(float a0) -> nsresult
+{
+ nsresult rv = NS_OK;
+ mozilla::jni::Field<SViscousFluidScale_t>::Set(StackScroller::Context(), &rv, a0);
+ return rv;
+}
+
+const char ViewTransform::name[] =
+ "org/mozilla/gecko/gfx/ViewTransform";
+
+constexpr char ViewTransform::New_t::name[];
+constexpr char ViewTransform::New_t::signature[];
+
+auto ViewTransform::New(float a0, float a1, float a2) -> ViewTransform::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(ViewTransform::Context(), nullptr, a0, a1, a2);
+}
+
+constexpr char ViewTransform::FixedLayerMarginBottom_t::name[];
+constexpr char ViewTransform::FixedLayerMarginBottom_t::signature[];
+
+auto ViewTransform::FixedLayerMarginBottom() const -> float
+{
+ return mozilla::jni::Field<FixedLayerMarginBottom_t>::Get(ViewTransform::mCtx, nullptr);
+}
+
+auto ViewTransform::FixedLayerMarginBottom(float a0) const -> void
+{
+ return mozilla::jni::Field<FixedLayerMarginBottom_t>::Set(ViewTransform::mCtx, nullptr, a0);
+}
+
+constexpr char ViewTransform::FixedLayerMarginLeft_t::name[];
+constexpr char ViewTransform::FixedLayerMarginLeft_t::signature[];
+
+auto ViewTransform::FixedLayerMarginLeft() const -> float
+{
+ return mozilla::jni::Field<FixedLayerMarginLeft_t>::Get(ViewTransform::mCtx, nullptr);
+}
+
+auto ViewTransform::FixedLayerMarginLeft(float a0) const -> void
+{
+ return mozilla::jni::Field<FixedLayerMarginLeft_t>::Set(ViewTransform::mCtx, nullptr, a0);
+}
+
+constexpr char ViewTransform::FixedLayerMarginRight_t::name[];
+constexpr char ViewTransform::FixedLayerMarginRight_t::signature[];
+
+auto ViewTransform::FixedLayerMarginRight() const -> float
+{
+ return mozilla::jni::Field<FixedLayerMarginRight_t>::Get(ViewTransform::mCtx, nullptr);
+}
+
+auto ViewTransform::FixedLayerMarginRight(float a0) const -> void
+{
+ return mozilla::jni::Field<FixedLayerMarginRight_t>::Set(ViewTransform::mCtx, nullptr, a0);
+}
+
+constexpr char ViewTransform::FixedLayerMarginTop_t::name[];
+constexpr char ViewTransform::FixedLayerMarginTop_t::signature[];
+
+auto ViewTransform::FixedLayerMarginTop() const -> float
+{
+ return mozilla::jni::Field<FixedLayerMarginTop_t>::Get(ViewTransform::mCtx, nullptr);
+}
+
+auto ViewTransform::FixedLayerMarginTop(float a0) const -> void
+{
+ return mozilla::jni::Field<FixedLayerMarginTop_t>::Set(ViewTransform::mCtx, nullptr, a0);
+}
+
+constexpr char ViewTransform::Height_t::name[];
+constexpr char ViewTransform::Height_t::signature[];
+
+auto ViewTransform::Height() const -> float
+{
+ return mozilla::jni::Field<Height_t>::Get(ViewTransform::mCtx, nullptr);
+}
+
+auto ViewTransform::Height(float a0) const -> void
+{
+ return mozilla::jni::Field<Height_t>::Set(ViewTransform::mCtx, nullptr, a0);
+}
+
+constexpr char ViewTransform::Scale_t::name[];
+constexpr char ViewTransform::Scale_t::signature[];
+
+auto ViewTransform::Scale() const -> float
+{
+ return mozilla::jni::Field<Scale_t>::Get(ViewTransform::mCtx, nullptr);
+}
+
+auto ViewTransform::Scale(float a0) const -> void
+{
+ return mozilla::jni::Field<Scale_t>::Set(ViewTransform::mCtx, nullptr, a0);
+}
+
+constexpr char ViewTransform::Width_t::name[];
+constexpr char ViewTransform::Width_t::signature[];
+
+auto ViewTransform::Width() const -> float
+{
+ return mozilla::jni::Field<Width_t>::Get(ViewTransform::mCtx, nullptr);
+}
+
+auto ViewTransform::Width(float a0) const -> void
+{
+ return mozilla::jni::Field<Width_t>::Set(ViewTransform::mCtx, nullptr, a0);
+}
+
+constexpr char ViewTransform::X_t::name[];
+constexpr char ViewTransform::X_t::signature[];
+
+auto ViewTransform::X() const -> float
+{
+ return mozilla::jni::Field<X_t>::Get(ViewTransform::mCtx, nullptr);
+}
+
+auto ViewTransform::X(float a0) const -> void
+{
+ return mozilla::jni::Field<X_t>::Set(ViewTransform::mCtx, nullptr, a0);
+}
+
+constexpr char ViewTransform::Y_t::name[];
+constexpr char ViewTransform::Y_t::signature[];
+
+auto ViewTransform::Y() const -> float
+{
+ return mozilla::jni::Field<Y_t>::Get(ViewTransform::mCtx, nullptr);
+}
+
+auto ViewTransform::Y(float a0) const -> void
+{
+ return mozilla::jni::Field<Y_t>::Set(ViewTransform::mCtx, nullptr, a0);
+}
+
+const char Clipboard::name[] =
+ "org/mozilla/gecko/util/Clipboard";
+
+constexpr char Clipboard::ClearText_t::name[];
+constexpr char Clipboard::ClearText_t::signature[];
+
+auto Clipboard::ClearText() -> void
+{
+ return mozilla::jni::Method<ClearText_t>::Call(Clipboard::Context(), nullptr);
+}
+
+constexpr char Clipboard::GetText_t::name[];
+constexpr char Clipboard::GetText_t::signature[];
+
+auto Clipboard::GetText() -> mozilla::jni::String::LocalRef
+{
+ return mozilla::jni::Method<GetText_t>::Call(Clipboard::Context(), nullptr);
+}
+
+constexpr char Clipboard::HasText_t::name[];
+constexpr char Clipboard::HasText_t::signature[];
+
+auto Clipboard::HasText() -> bool
+{
+ return mozilla::jni::Method<HasText_t>::Call(Clipboard::Context(), nullptr);
+}
+
+constexpr char Clipboard::SetText_t::name[];
+constexpr char Clipboard::SetText_t::signature[];
+
+auto Clipboard::SetText(mozilla::jni::String::Param a0) -> void
+{
+ return mozilla::jni::Method<SetText_t>::Call(Clipboard::Context(), nullptr, a0);
+}
+
+const char HardwareCodecCapabilityUtils::name[] =
+ "org/mozilla/gecko/util/HardwareCodecCapabilityUtils";
+
+constexpr char HardwareCodecCapabilityUtils::HasHWVP9_t::name[];
+constexpr char HardwareCodecCapabilityUtils::HasHWVP9_t::signature[];
+
+auto HardwareCodecCapabilityUtils::HasHWVP9() -> bool
+{
+ return mozilla::jni::Method<HasHWVP9_t>::Call(HardwareCodecCapabilityUtils::Context(), nullptr);
+}
+
+constexpr char HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType_t::name[];
+constexpr char HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType_t::signature[];
+
+auto HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType(mozilla::jni::String::Param a0) -> bool
+{
+ return mozilla::jni::Method<FindDecoderCodecInfoForMimeType_t>::Call(HardwareCodecCapabilityUtils::Context(), nullptr, a0);
+}
+
+const char NativeJSContainer::name[] =
+ "org/mozilla/gecko/util/NativeJSContainer";
+
+constexpr char NativeJSContainer::New_t::name[];
+constexpr char NativeJSContainer::New_t::signature[];
+
+auto NativeJSContainer::New() -> NativeJSContainer::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(NativeJSContainer::Context(), nullptr);
+}
+
+constexpr char NativeJSContainer::Clone2_t::name[];
+constexpr char NativeJSContainer::Clone2_t::signature[];
+
+constexpr char NativeJSContainer::DisposeNative_t::name[];
+constexpr char NativeJSContainer::DisposeNative_t::signature[];
+
+const char NativeJSObject::name[] =
+ "org/mozilla/gecko/util/NativeJSObject";
+
+constexpr char NativeJSObject::New_t::name[];
+constexpr char NativeJSObject::New_t::signature[];
+
+auto NativeJSObject::New() -> NativeJSObject::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(NativeJSObject::Context(), nullptr);
+}
+
+constexpr char NativeJSObject::DisposeNative_t::name[];
+constexpr char NativeJSObject::DisposeNative_t::signature[];
+
+auto NativeJSObject::DisposeNative() const -> void
+{
+ return mozilla::jni::Method<DisposeNative_t>::Call(NativeJSObject::mCtx, nullptr);
+}
+
+constexpr char NativeJSObject::GetBoolean_t::name[];
+constexpr char NativeJSObject::GetBoolean_t::signature[];
+
+constexpr char NativeJSObject::GetBooleanArray_t::name[];
+constexpr char NativeJSObject::GetBooleanArray_t::signature[];
+
+constexpr char NativeJSObject::GetBundle_t::name[];
+constexpr char NativeJSObject::GetBundle_t::signature[];
+
+constexpr char NativeJSObject::GetBundleArray_t::name[];
+constexpr char NativeJSObject::GetBundleArray_t::signature[];
+
+constexpr char NativeJSObject::GetDouble_t::name[];
+constexpr char NativeJSObject::GetDouble_t::signature[];
+
+constexpr char NativeJSObject::GetDoubleArray_t::name[];
+constexpr char NativeJSObject::GetDoubleArray_t::signature[];
+
+constexpr char NativeJSObject::GetInt_t::name[];
+constexpr char NativeJSObject::GetInt_t::signature[];
+
+constexpr char NativeJSObject::GetIntArray_t::name[];
+constexpr char NativeJSObject::GetIntArray_t::signature[];
+
+constexpr char NativeJSObject::GetObject_t::name[];
+constexpr char NativeJSObject::GetObject_t::signature[];
+
+constexpr char NativeJSObject::GetObjectArray_t::name[];
+constexpr char NativeJSObject::GetObjectArray_t::signature[];
+
+constexpr char NativeJSObject::GetString_t::name[];
+constexpr char NativeJSObject::GetString_t::signature[];
+
+constexpr char NativeJSObject::GetStringArray_t::name[];
+constexpr char NativeJSObject::GetStringArray_t::signature[];
+
+constexpr char NativeJSObject::Has_t::name[];
+constexpr char NativeJSObject::Has_t::signature[];
+
+constexpr char NativeJSObject::OptBoolean_t::name[];
+constexpr char NativeJSObject::OptBoolean_t::signature[];
+
+constexpr char NativeJSObject::OptBooleanArray_t::name[];
+constexpr char NativeJSObject::OptBooleanArray_t::signature[];
+
+constexpr char NativeJSObject::OptBundle_t::name[];
+constexpr char NativeJSObject::OptBundle_t::signature[];
+
+constexpr char NativeJSObject::OptBundleArray_t::name[];
+constexpr char NativeJSObject::OptBundleArray_t::signature[];
+
+constexpr char NativeJSObject::OptDouble_t::name[];
+constexpr char NativeJSObject::OptDouble_t::signature[];
+
+constexpr char NativeJSObject::OptDoubleArray_t::name[];
+constexpr char NativeJSObject::OptDoubleArray_t::signature[];
+
+constexpr char NativeJSObject::OptInt_t::name[];
+constexpr char NativeJSObject::OptInt_t::signature[];
+
+constexpr char NativeJSObject::OptIntArray_t::name[];
+constexpr char NativeJSObject::OptIntArray_t::signature[];
+
+constexpr char NativeJSObject::OptObject_t::name[];
+constexpr char NativeJSObject::OptObject_t::signature[];
+
+constexpr char NativeJSObject::OptObjectArray_t::name[];
+constexpr char NativeJSObject::OptObjectArray_t::signature[];
+
+constexpr char NativeJSObject::OptString_t::name[];
+constexpr char NativeJSObject::OptString_t::signature[];
+
+constexpr char NativeJSObject::OptStringArray_t::name[];
+constexpr char NativeJSObject::OptStringArray_t::signature[];
+
+constexpr char NativeJSObject::ToBundle_t::name[];
+constexpr char NativeJSObject::ToBundle_t::signature[];
+
+constexpr char NativeJSObject::ToString_t::name[];
+constexpr char NativeJSObject::ToString_t::signature[];
+
+} /* java */
+} /* mozilla */
diff --git a/widget/android/GeneratedJNIWrappers.h b/widget/android/GeneratedJNIWrappers.h
new file mode 100644
index 000000000..7d4777648
--- /dev/null
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -0,0 +1,5456 @@
+// GENERATED CODE
+// Generated by the Java program at /build/annotationProcessors at compile time
+// from annotations on Java methods. To update, change the annotations on the
+// corresponding Java methods and rerun the build. Manually updating this file
+// will cause your build to fail.
+
+#ifndef GeneratedJNIWrappers_h
+#define GeneratedJNIWrappers_h
+
+#include "mozilla/jni/Refs.h"
+
+namespace mozilla {
+namespace java {
+
+class AlarmReceiver : public mozilla::jni::ObjectBase<AlarmReceiver>
+{
+public:
+ static const char name[];
+
+ explicit AlarmReceiver(const Context& ctx) : ObjectBase<AlarmReceiver>(ctx) {}
+
+ struct NotifyAlarmFired_t {
+ typedef AlarmReceiver Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "notifyAlarmFired";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+
+ template<class Impl> class Natives;
+};
+
+class AndroidGamepadManager : public mozilla::jni::ObjectBase<AndroidGamepadManager>
+{
+public:
+ static const char name[];
+
+ explicit AndroidGamepadManager(const Context& ctx) : ObjectBase<AndroidGamepadManager>(ctx) {}
+
+ struct OnAxisChange_t {
+ typedef AndroidGamepadManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::BooleanArray::Param,
+ mozilla::jni::FloatArray::Param> Args;
+ static constexpr char name[] = "onAxisChange";
+ static constexpr char signature[] =
+ "(I[Z[F)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnButtonChange_t {
+ typedef AndroidGamepadManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ bool,
+ float> Args;
+ static constexpr char name[] = "onButtonChange";
+ static constexpr char signature[] =
+ "(IIZF)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnGamepadAdded_t {
+ typedef AndroidGamepadManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "onGamepadAdded";
+ static constexpr char signature[] =
+ "(II)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto OnGamepadAdded(int32_t, int32_t) -> void;
+
+ struct OnGamepadChange_t {
+ typedef AndroidGamepadManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ bool> Args;
+ static constexpr char name[] = "onGamepadChange";
+ static constexpr char signature[] =
+ "(IZ)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct Start_t {
+ typedef AndroidGamepadManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "start";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Start() -> void;
+
+ struct Stop_t {
+ typedef AndroidGamepadManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "stop";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Stop() -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class GeckoAppShell : public mozilla::jni::ObjectBase<GeckoAppShell>
+{
+public:
+ static const char name[];
+
+ explicit GeckoAppShell(const Context& ctx) : ObjectBase<GeckoAppShell>(ctx) {}
+
+ class CameraCallback;
+
+ struct AddFullScreenPluginView_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "addFullScreenPluginView";
+ static constexpr char signature[] =
+ "(Landroid/view/View;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto AddFullScreenPluginView(mozilla::jni::Object::Param) -> void;
+
+ struct CancelVibrate_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "cancelVibrate";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CancelVibrate() -> void;
+
+ struct CheckURIVisited_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "checkUriVisited";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CheckURIVisited(mozilla::jni::String::Param) -> void;
+
+ struct CloseCamera_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "closeCamera";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CloseCamera() -> void;
+
+ struct CloseNotification_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "closeNotification";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CloseNotification(mozilla::jni::String::Param) -> void;
+
+ struct ConnectionGetMimeType_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "connectionGetMimeType";
+ static constexpr char signature[] =
+ "(Ljava/net/URLConnection;)Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto ConnectionGetMimeType(mozilla::jni::Object::Param) -> mozilla::jni::String::LocalRef;
+
+ struct CreateInputStream_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "createInputStream";
+ static constexpr char signature[] =
+ "(Ljava/net/URLConnection;)Ljava/io/InputStream;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CreateInputStream(mozilla::jni::Object::Param) -> mozilla::jni::Object::LocalRef;
+
+ struct CreateShortcut_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "createShortcut";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CreateShortcut(mozilla::jni::String::Param, mozilla::jni::String::Param) -> void;
+
+ struct DisableAlarm_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disableAlarm";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto DisableAlarm() -> void;
+
+ struct DisableBatteryNotifications_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disableBatteryNotifications";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto DisableBatteryNotifications() -> void;
+
+ struct DisableNetworkNotifications_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disableNetworkNotifications";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto DisableNetworkNotifications() -> void;
+
+ struct DisableScreenOrientationNotifications_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disableScreenOrientationNotifications";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto DisableScreenOrientationNotifications() -> void;
+
+ struct DisableSensor_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "disableSensor";
+ static constexpr char signature[] =
+ "(I)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto DisableSensor(int32_t) -> void;
+
+ struct EnableBatteryNotifications_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "enableBatteryNotifications";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto EnableBatteryNotifications() -> void;
+
+ struct EnableLocation_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "enableLocation";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto EnableLocation(bool) -> void;
+
+ struct EnableLocationHighAccuracy_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "enableLocationHighAccuracy";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto EnableLocationHighAccuracy(bool) -> void;
+
+ struct EnableNetworkNotifications_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "enableNetworkNotifications";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto EnableNetworkNotifications() -> void;
+
+ struct EnableScreenOrientationNotifications_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "enableScreenOrientationNotifications";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto EnableScreenOrientationNotifications() -> void;
+
+ struct EnableSensor_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "enableSensor";
+ static constexpr char signature[] =
+ "(I)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto EnableSensor(int32_t) -> void;
+
+ struct GetApplicationContext_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getApplicationContext";
+ static constexpr char signature[] =
+ "()Landroid/content/Context;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetApplicationContext() -> mozilla::jni::Object::LocalRef;
+
+ struct GetConnection_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getConnection";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Ljava/net/URLConnection;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetConnection(mozilla::jni::String::Param) -> mozilla::jni::Object::LocalRef;
+
+ struct GetContext_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getContext";
+ static constexpr char signature[] =
+ "()Landroid/content/Context;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetContext() -> mozilla::jni::Object::LocalRef;
+
+ struct GetCurrentBatteryInformation_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::DoubleArray::LocalRef ReturnType;
+ typedef mozilla::jni::DoubleArray::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getCurrentBatteryInformation";
+ static constexpr char signature[] =
+ "()[D";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetCurrentBatteryInformation() -> mozilla::jni::DoubleArray::LocalRef;
+
+ struct GetCurrentNetworkInformation_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::DoubleArray::LocalRef ReturnType;
+ typedef mozilla::jni::DoubleArray::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getCurrentNetworkInformation";
+ static constexpr char signature[] =
+ "()[D";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetCurrentNetworkInformation() -> mozilla::jni::DoubleArray::LocalRef;
+
+ struct GetDensity_t {
+ typedef GeckoAppShell Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getDensity";
+ static constexpr char signature[] =
+ "()F";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetDensity() -> float;
+
+ struct GetDpi_t {
+ typedef GeckoAppShell Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getDpi";
+ static constexpr char signature[] =
+ "()I";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetDpi() -> int32_t;
+
+ struct GetExceptionStackTrace_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Throwable::Param> Args;
+ static constexpr char name[] = "getExceptionStackTrace";
+ static constexpr char signature[] =
+ "(Ljava/lang/Throwable;)Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::IGNORE;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetExceptionStackTrace(mozilla::jni::Throwable::Param) -> mozilla::jni::String::LocalRef;
+
+ struct GetExtensionFromMimeType_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getExtensionFromMimeType";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetExtensionFromMimeType(mozilla::jni::String::Param) -> mozilla::jni::String::LocalRef;
+
+ struct GetHWDecoderCapability_t {
+ typedef GeckoAppShell Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getHWDecoderCapability";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetHWDecoderCapability() -> bool;
+
+ struct GetHWEncoderCapability_t {
+ typedef GeckoAppShell Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getHWEncoderCapability";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetHWEncoderCapability() -> bool;
+
+ struct GetHandlersForMimeType_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::ObjectArray::LocalRef ReturnType;
+ typedef mozilla::jni::ObjectArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getHandlersForMimeType";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetHandlersForMimeType(mozilla::jni::String::Param, mozilla::jni::String::Param) -> mozilla::jni::ObjectArray::LocalRef;
+
+ struct GetHandlersForURL_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::ObjectArray::LocalRef ReturnType;
+ typedef mozilla::jni::ObjectArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getHandlersForURL";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetHandlersForURL(mozilla::jni::String::Param, mozilla::jni::String::Param) -> mozilla::jni::ObjectArray::LocalRef;
+
+ struct GetIconForExtension_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::ByteArray::LocalRef ReturnType;
+ typedef mozilla::jni::ByteArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ int32_t> Args;
+ static constexpr char name[] = "getIconForExtension";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;I)[B";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetIconForExtension(mozilla::jni::String::Param, int32_t) -> mozilla::jni::ByteArray::LocalRef;
+
+ struct GetMaxTouchPoints_t {
+ typedef GeckoAppShell Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getMaxTouchPoints";
+ static constexpr char signature[] =
+ "()I";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetMaxTouchPoints() -> int32_t;
+
+ struct GetMimeTypeFromExtensions_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getMimeTypeFromExtensions";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetMimeTypeFromExtensions(mozilla::jni::String::Param) -> mozilla::jni::String::LocalRef;
+
+ struct GetNetworkLinkType_t {
+ typedef GeckoAppShell Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getNetworkLinkType";
+ static constexpr char signature[] =
+ "()I";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetNetworkLinkType() -> int32_t;
+
+ struct GetProxyForURI_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ int32_t> Args;
+ static constexpr char name[] = "getProxyForURI";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetProxyForURI(mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, int32_t) -> mozilla::jni::String::LocalRef;
+
+ struct GetScreenAngle_t {
+ typedef GeckoAppShell Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getScreenAngle";
+ static constexpr char signature[] =
+ "()I";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetScreenAngle() -> int32_t;
+
+ struct GetScreenDepth_t {
+ typedef GeckoAppShell Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getScreenDepth";
+ static constexpr char signature[] =
+ "()I";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetScreenDepth() -> int32_t;
+
+ struct GetScreenOrientation_t {
+ typedef GeckoAppShell Owner;
+ typedef int16_t ReturnType;
+ typedef int16_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getScreenOrientation";
+ static constexpr char signature[] =
+ "()S";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetScreenOrientation() -> int16_t;
+
+ struct GetScreenSize_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getScreenSize";
+ static constexpr char signature[] =
+ "()Landroid/graphics/Rect;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetScreenSize() -> mozilla::jni::Object::LocalRef;
+
+ struct GetShowPasswordSetting_t {
+ typedef GeckoAppShell Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getShowPasswordSetting";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetShowPasswordSetting() -> bool;
+
+ struct GetSystemColors_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::IntArray::LocalRef ReturnType;
+ typedef mozilla::jni::IntArray::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getSystemColors";
+ static constexpr char signature[] =
+ "()[I";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetSystemColors() -> mozilla::jni::IntArray::LocalRef;
+
+ struct HandleGeckoMessage_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "handleGeckoMessage";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/util/NativeJSContainer;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto HandleGeckoMessage(mozilla::jni::Object::Param) -> void;
+
+ struct HandleUncaughtException_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Throwable::Param> Args;
+ static constexpr char name[] = "handleUncaughtException";
+ static constexpr char signature[] =
+ "(Ljava/lang/Throwable;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::IGNORE;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto HandleUncaughtException(mozilla::jni::Throwable::Param) -> void;
+
+ struct HideProgressDialog_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "hideProgressDialog";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto HideProgressDialog() -> void;
+
+ struct InitCamera_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::IntArray::LocalRef ReturnType;
+ typedef mozilla::jni::IntArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ int32_t,
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "initCamera";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;III)[I";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto InitCamera(mozilla::jni::String::Param, int32_t, int32_t, int32_t) -> mozilla::jni::IntArray::LocalRef;
+
+ struct IsNetworkLinkKnown_t {
+ typedef GeckoAppShell Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "isNetworkLinkKnown";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto IsNetworkLinkKnown() -> bool;
+
+ struct IsNetworkLinkUp_t {
+ typedef GeckoAppShell Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "isNetworkLinkUp";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto IsNetworkLinkUp() -> bool;
+
+ struct IsTablet_t {
+ typedef GeckoAppShell Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "isTablet";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto IsTablet() -> bool;
+
+ struct KillAnyZombies_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "killAnyZombies";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto KillAnyZombies() -> void;
+
+ struct LoadPluginClass_t {
+ typedef GeckoAppShell Owner;
+ typedef mozilla::jni::Class::LocalRef ReturnType;
+ typedef mozilla::jni::Class::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "loadPluginClass";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto LoadPluginClass(mozilla::jni::String::Param, mozilla::jni::String::Param) -> mozilla::jni::Class::LocalRef;
+
+ struct LockScreenOrientation_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "lockScreenOrientation";
+ static constexpr char signature[] =
+ "(I)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto LockScreenOrientation(int32_t) -> void;
+
+ struct MarkURIVisited_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "markUriVisited";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto MarkURIVisited(mozilla::jni::String::Param) -> void;
+
+ struct MoveTaskToBack_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "moveTaskToBack";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto MoveTaskToBack() -> void;
+
+ struct NotifyObservers_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "nativeNotifyObservers";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct NotifyAlertListener_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "notifyAlertListener";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct NotifyUriVisited_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "notifyUriVisited";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct NotifyWakeLockChanged_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "notifyWakeLockChanged";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto NotifyWakeLockChanged(mozilla::jni::String::Param, mozilla::jni::String::Param) -> void;
+
+ struct OnFullScreenPluginHidden_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "onFullScreenPluginHidden";
+ static constexpr char signature[] =
+ "(Landroid/view/View;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnLocationChanged_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ double,
+ double,
+ double,
+ float,
+ float,
+ float,
+ int64_t> Args;
+ static constexpr char name[] = "onLocationChanged";
+ static constexpr char signature[] =
+ "(DDDFFFJ)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnSensorChanged_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ float,
+ float,
+ float,
+ float,
+ int32_t,
+ int64_t> Args;
+ static constexpr char name[] = "onSensorChanged";
+ static constexpr char signature[] =
+ "(IFFFFIJ)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OpenUriExternal_t {
+ typedef GeckoAppShell Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "openUriExternal";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto OpenUriExternal(mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) -> bool;
+
+ struct OpenWindowForNotification_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "openWindowForNotification";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto OpenWindowForNotification() -> void;
+
+ struct PerformHapticFeedback_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "performHapticFeedback";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto PerformHapticFeedback(bool) -> void;
+
+ struct RemoveFullScreenPluginView_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "removeFullScreenPluginView";
+ static constexpr char signature[] =
+ "(Landroid/view/View;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto RemoveFullScreenPluginView(mozilla::jni::Object::Param) -> void;
+
+ struct ReportJavaCrash_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Throwable::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "reportJavaCrash";
+ static constexpr char signature[] =
+ "(Ljava/lang/Throwable;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct ScheduleRestart_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "scheduleRestart";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto ScheduleRestart() -> void;
+
+ struct SetAlarm_t {
+ typedef GeckoAppShell Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "setAlarm";
+ static constexpr char signature[] =
+ "(II)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto SetAlarm(int32_t, int32_t) -> bool;
+
+ struct SetFullScreen_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "setFullScreen";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto SetFullScreen(bool) -> void;
+
+ struct SetKeepScreenOn_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "setKeepScreenOn";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto SetKeepScreenOn(bool) -> void;
+
+ struct SetScreenDepthOverride_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "setScreenDepthOverride";
+ static constexpr char signature[] =
+ "(I)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto SetScreenDepthOverride(int32_t) -> void;
+
+ struct SetURITitle_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "setUriTitle";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto SetURITitle(mozilla::jni::String::Param, mozilla::jni::String::Param) -> void;
+
+ struct ShowNotification_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "showNotification";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto ShowNotification(mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) -> void;
+
+ struct SyncNotifyObservers_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "syncNotifyObservers";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct UnlockProfile_t {
+ typedef GeckoAppShell Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "unlockProfile";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto UnlockProfile() -> bool;
+
+ struct UnlockScreenOrientation_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "unlockScreenOrientation";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto UnlockScreenOrientation() -> void;
+
+ struct Vibrate_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int64_t> Args;
+ static constexpr char name[] = "vibrate";
+ static constexpr char signature[] =
+ "(J)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Vibrate(int64_t) -> void;
+
+ struct Vibrate2_t {
+ typedef GeckoAppShell Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::LongArray::Param,
+ int32_t> Args;
+ static constexpr char name[] = "vibrate";
+ static constexpr char signature[] =
+ "([JI)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Vibrate(mozilla::jni::LongArray::Param, int32_t) -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class GeckoAppShell::CameraCallback : public mozilla::jni::ObjectBase<CameraCallback>
+{
+public:
+ static const char name[];
+
+ explicit CameraCallback(const Context& ctx) : ObjectBase<CameraCallback>(ctx) {}
+
+ struct OnFrameData_t {
+ typedef CameraCallback Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::ByteArray::Param> Args;
+ static constexpr char name[] = "onFrameData";
+ static constexpr char signature[] =
+ "(I[B)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+
+ template<class Impl> class Natives;
+};
+
+class GeckoBatteryManager : public mozilla::jni::ObjectBase<GeckoBatteryManager>
+{
+public:
+ static const char name[];
+
+ explicit GeckoBatteryManager(const Context& ctx) : ObjectBase<GeckoBatteryManager>(ctx) {}
+
+ struct OnBatteryChange_t {
+ typedef GeckoBatteryManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ double,
+ bool,
+ double> Args;
+ static constexpr char name[] = "onBatteryChange";
+ static constexpr char signature[] =
+ "(DZD)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+
+ template<class Impl> class Natives;
+};
+
+class GeckoEditable : public mozilla::jni::ObjectBase<GeckoEditable>
+{
+public:
+ static const char name[];
+
+ explicit GeckoEditable(const Context& ctx) : ObjectBase<GeckoEditable>(ctx) {}
+
+ struct New_t {
+ typedef GeckoEditable Owner;
+ typedef GeckoEditable::LocalRef ReturnType;
+ typedef GeckoEditable::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/GeckoView;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New(mozilla::jni::Object::Param) -> GeckoEditable::LocalRef;
+
+ struct DisposeNative_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disposeNative";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct NotifyIME_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "notifyIME";
+ static constexpr char signature[] =
+ "(I)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto NotifyIME(int32_t) const -> void;
+
+ struct NotifyIMEContext_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "notifyIMEContext";
+ static constexpr char signature[] =
+ "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto NotifyIMEContext(int32_t, mozilla::jni::String::Param, mozilla::jni::String::Param, mozilla::jni::String::Param) const -> void;
+
+ struct OnDefaultKeyEvent_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "onDefaultKeyEvent";
+ static constexpr char signature[] =
+ "(Landroid/view/KeyEvent;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto OnDefaultKeyEvent(mozilla::jni::Object::Param) const -> void;
+
+ struct OnImeAddCompositionRange_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ bool,
+ int32_t,
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "onImeAddCompositionRange";
+ static constexpr char signature[] =
+ "(IIIIIZIII)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct OnImeReplaceText_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "onImeReplaceText";
+ static constexpr char signature[] =
+ "(IILjava/lang/String;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct OnImeRequestCursorUpdates_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "onImeRequestCursorUpdates";
+ static constexpr char signature[] =
+ "(I)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct OnImeSynchronize_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "onImeSynchronize";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct OnImeUpdateComposition_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "onImeUpdateComposition";
+ static constexpr char signature[] =
+ "(II)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct OnKeyEvent_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int64_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ bool,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "onKeyEvent";
+ static constexpr char signature[] =
+ "(IIIIJIIIIIZLandroid/view/KeyEvent;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct OnSelectionChange_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "onSelectionChange";
+ static constexpr char signature[] =
+ "(II)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::IGNORE;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto OnSelectionChange(int32_t, int32_t) const -> void;
+
+ struct OnTextChange_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ int32_t,
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "onTextChange";
+ static constexpr char signature[] =
+ "(Ljava/lang/CharSequence;III)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::IGNORE;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto OnTextChange(mozilla::jni::String::Param, int32_t, int32_t, int32_t) const -> void;
+
+ struct OnViewChange_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "onViewChange";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/GeckoView;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto OnViewChange(mozilla::jni::Object::Param) const -> void;
+
+ struct UpdateCompositionRects_t {
+ typedef GeckoEditable Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ObjectArray::Param> Args;
+ static constexpr char name[] = "updateCompositionRects";
+ static constexpr char signature[] =
+ "([Landroid/graphics/RectF;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto UpdateCompositionRects(mozilla::jni::ObjectArray::Param) const -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class GeckoEditableListener : public mozilla::jni::ObjectBase<GeckoEditableListener>
+{
+public:
+ static const char name[];
+
+ explicit GeckoEditableListener(const Context& ctx) : ObjectBase<GeckoEditableListener>(ctx) {}
+
+ static const int32_t NOTIFY_IME_OF_BLUR = 2;
+
+ static const int32_t NOTIFY_IME_OF_FOCUS = 1;
+
+ static const int32_t NOTIFY_IME_OPEN_VKB = -2;
+
+ static const int32_t NOTIFY_IME_REPLY_EVENT = -1;
+
+ static const int32_t NOTIFY_IME_TO_CANCEL_COMPOSITION = 9;
+
+ static const int32_t NOTIFY_IME_TO_COMMIT_COMPOSITION = 8;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class GeckoNetworkManager : public mozilla::jni::ObjectBase<GeckoNetworkManager>
+{
+public:
+ static const char name[];
+
+ explicit GeckoNetworkManager(const Context& ctx) : ObjectBase<GeckoNetworkManager>(ctx) {}
+
+ struct OnConnectionChanged_t {
+ typedef GeckoNetworkManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::String::Param,
+ bool,
+ int32_t> Args;
+ static constexpr char name[] = "onConnectionChanged";
+ static constexpr char signature[] =
+ "(ILjava/lang/String;ZI)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnStatusChanged_t {
+ typedef GeckoNetworkManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "onStatusChanged";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class GeckoScreenOrientation : public mozilla::jni::ObjectBase<GeckoScreenOrientation>
+{
+public:
+ static const char name[];
+
+ explicit GeckoScreenOrientation(const Context& ctx) : ObjectBase<GeckoScreenOrientation>(ctx) {}
+
+ struct OnOrientationChange_t {
+ typedef GeckoScreenOrientation Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int16_t,
+ int16_t> Args;
+ static constexpr char name[] = "onOrientationChange";
+ static constexpr char signature[] =
+ "(SS)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class GeckoThread : public mozilla::jni::ObjectBase<GeckoThread>
+{
+public:
+ static const char name[];
+
+ explicit GeckoThread(const Context& ctx) : ObjectBase<GeckoThread>(ctx) {}
+
+ class State;
+
+ struct CheckAndSetState_t {
+ typedef GeckoThread Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "checkAndSetState";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/GeckoThread$State;Lorg/mozilla/gecko/GeckoThread$State;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CheckAndSetState(mozilla::jni::Object::Param, mozilla::jni::Object::Param) -> bool;
+
+ struct CreateServices_t {
+ typedef GeckoThread Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "nativeCreateServices";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnPause_t {
+ typedef GeckoThread Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "nativeOnPause";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnResume_t {
+ typedef GeckoThread Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "nativeOnResume";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct PumpMessageLoop_t {
+ typedef GeckoThread Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "pumpMessageLoop";
+ static constexpr char signature[] =
+ "(Landroid/os/Message;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto PumpMessageLoop(mozilla::jni::Object::Param) -> bool;
+
+ struct RequestUiThreadCallback_t {
+ typedef GeckoThread Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int64_t> Args;
+ static constexpr char name[] = "requestUiThreadCallback";
+ static constexpr char signature[] =
+ "(J)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto RequestUiThreadCallback(int64_t) -> void;
+
+ struct RunUiThreadCallback_t {
+ typedef GeckoThread Owner;
+ typedef int64_t ReturnType;
+ typedef int64_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "runUiThreadCallback";
+ static constexpr char signature[] =
+ "()J";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct SetState_t {
+ typedef GeckoThread Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "setState";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/GeckoThread$State;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto SetState(mozilla::jni::Object::Param) -> void;
+
+ struct SpeculativeConnect_t {
+ typedef GeckoThread Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "speculativeConnectNative";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct WaitOnGecko_t {
+ typedef GeckoThread Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "waitOnGecko";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct ClsLoader_t {
+ typedef GeckoThread Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "clsLoader";
+ static constexpr char signature[] =
+ "Ljava/lang/ClassLoader;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto ClsLoader() -> mozilla::jni::Object::LocalRef;
+
+ struct MsgQueue_t {
+ typedef GeckoThread Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "msgQueue";
+ static constexpr char signature[] =
+ "Landroid/os/MessageQueue;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto MsgQueue() -> mozilla::jni::Object::LocalRef;
+
+ static auto MsgQueue(mozilla::jni::Object::Param) -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class GeckoThread::State : public mozilla::jni::ObjectBase<State>
+{
+public:
+ static const char name[];
+
+ explicit State(const Context& ctx) : ObjectBase<State>(ctx) {}
+
+ struct EXITED_t {
+ typedef State Owner;
+ typedef State::LocalRef ReturnType;
+ typedef State::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "EXITED";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/GeckoThread$State;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto EXITED() -> State::LocalRef;
+
+ struct EXITING_t {
+ typedef State Owner;
+ typedef State::LocalRef ReturnType;
+ typedef State::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "EXITING";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/GeckoThread$State;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto EXITING() -> State::LocalRef;
+
+ struct INITIAL_t {
+ typedef State Owner;
+ typedef State::LocalRef ReturnType;
+ typedef State::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "INITIAL";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/GeckoThread$State;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto INITIAL() -> State::LocalRef;
+
+ struct JNI_READY_t {
+ typedef State Owner;
+ typedef State::LocalRef ReturnType;
+ typedef State::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "JNI_READY";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/GeckoThread$State;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto JNI_READY() -> State::LocalRef;
+
+ struct LAUNCHED_t {
+ typedef State Owner;
+ typedef State::LocalRef ReturnType;
+ typedef State::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "LAUNCHED";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/GeckoThread$State;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto LAUNCHED() -> State::LocalRef;
+
+ struct LIBS_READY_t {
+ typedef State Owner;
+ typedef State::LocalRef ReturnType;
+ typedef State::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "LIBS_READY";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/GeckoThread$State;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto LIBS_READY() -> State::LocalRef;
+
+ struct MOZGLUE_READY_t {
+ typedef State Owner;
+ typedef State::LocalRef ReturnType;
+ typedef State::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "MOZGLUE_READY";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/GeckoThread$State;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto MOZGLUE_READY() -> State::LocalRef;
+
+ struct PROFILE_READY_t {
+ typedef State Owner;
+ typedef State::LocalRef ReturnType;
+ typedef State::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "PROFILE_READY";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/GeckoThread$State;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto PROFILE_READY() -> State::LocalRef;
+
+ struct RUNNING_t {
+ typedef State Owner;
+ typedef State::LocalRef ReturnType;
+ typedef State::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "RUNNING";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/GeckoThread$State;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto RUNNING() -> State::LocalRef;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class GeckoView : public mozilla::jni::ObjectBase<GeckoView>
+{
+public:
+ static const char name[];
+
+ explicit GeckoView(const Context& ctx) : ObjectBase<GeckoView>(ctx) {}
+
+ class Window;
+
+ static const int32_t LOAD_DEFAULT = 0;
+
+ static const int32_t LOAD_NEW_TAB = 1;
+
+ static const int32_t LOAD_SWITCH_TAB = 2;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class GeckoView::Window : public mozilla::jni::ObjectBase<Window>
+{
+public:
+ static const char name[];
+
+ explicit Window(const Context& ctx) : ObjectBase<Window>(ctx) {}
+
+ struct Close_t {
+ typedef Window Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "close";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct DisposeNative_t {
+ typedef Window Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disposeNative";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct LoadUri_t {
+ typedef Window Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ int32_t> Args;
+ static constexpr char name[] = "loadUri";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;I)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct Open_t {
+ typedef Window Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ Window::Param,
+ GeckoView::Param,
+ mozilla::jni::Object::Param,
+ mozilla::jni::String::Param,
+ int32_t> Args;
+ static constexpr char name[] = "open";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/GeckoView$Window;Lorg/mozilla/gecko/GeckoView;Ljava/lang/Object;Ljava/lang/String;I)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct Reattach_t {
+ typedef Window Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ GeckoView::Param,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "reattach";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/GeckoView;Ljava/lang/Object;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class PrefsHelper : public mozilla::jni::ObjectBase<PrefsHelper>
+{
+public:
+ static const char name[];
+
+ explicit PrefsHelper(const Context& ctx) : ObjectBase<PrefsHelper>(ctx) {}
+
+ struct CallPrefHandler_t {
+ typedef PrefsHelper Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param,
+ int32_t,
+ mozilla::jni::String::Param,
+ bool,
+ int32_t,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "callPrefHandler";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/PrefsHelper$PrefHandler;ILjava/lang/String;ZILjava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CallPrefHandler(mozilla::jni::Object::Param, int32_t, mozilla::jni::String::Param, bool, int32_t, mozilla::jni::String::Param) -> void;
+
+ struct AddObserver_t {
+ typedef PrefsHelper Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ObjectArray::Param,
+ mozilla::jni::Object::Param,
+ mozilla::jni::ObjectArray::Param> Args;
+ static constexpr char name[] = "nativeAddObserver";
+ static constexpr char signature[] =
+ "([Ljava/lang/String;Lorg/mozilla/gecko/PrefsHelper$PrefHandler;[Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct GetPrefs_t {
+ typedef PrefsHelper Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ObjectArray::Param,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "nativeGetPrefs";
+ static constexpr char signature[] =
+ "([Ljava/lang/String;Lorg/mozilla/gecko/PrefsHelper$PrefHandler;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct RemoveObserver_t {
+ typedef PrefsHelper Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ObjectArray::Param> Args;
+ static constexpr char name[] = "nativeRemoveObserver";
+ static constexpr char signature[] =
+ "([Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct SetPref_t {
+ typedef PrefsHelper Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ bool,
+ int32_t,
+ bool,
+ int32_t,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "nativeSetPref";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;ZIZILjava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnPrefChange_t {
+ typedef PrefsHelper Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ int32_t,
+ bool,
+ int32_t,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "onPrefChange";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;IZILjava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto OnPrefChange(mozilla::jni::String::Param, int32_t, bool, int32_t, mozilla::jni::String::Param) -> void;
+
+ static const int32_t PREF_BOOL = 1;
+
+ static const int32_t PREF_FINISH = 0;
+
+ static const int32_t PREF_INT = 2;
+
+ static const int32_t PREF_INVALID = -1;
+
+ static const int32_t PREF_STRING = 3;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class SurfaceTextureListener : public mozilla::jni::ObjectBase<SurfaceTextureListener>
+{
+public:
+ static const char name[];
+
+ explicit SurfaceTextureListener(const Context& ctx) : ObjectBase<SurfaceTextureListener>(ctx) {}
+
+ struct New_t {
+ typedef SurfaceTextureListener Owner;
+ typedef SurfaceTextureListener::LocalRef ReturnType;
+ typedef SurfaceTextureListener::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New() -> SurfaceTextureListener::LocalRef;
+
+ struct OnFrameAvailable_t {
+ typedef SurfaceTextureListener Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "nativeOnFrameAvailable";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class GeckoLayerClient : public mozilla::jni::ObjectBase<GeckoLayerClient>
+{
+public:
+ static const char name[];
+
+ explicit GeckoLayerClient(const Context& ctx) : ObjectBase<GeckoLayerClient>(ctx) {}
+
+ struct ContentDocumentChanged_t {
+ typedef GeckoLayerClient Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "contentDocumentChanged";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto ContentDocumentChanged() const -> void;
+
+ struct CreateFrame_t {
+ typedef GeckoLayerClient Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "createFrame";
+ static constexpr char signature[] =
+ "()Lorg/mozilla/gecko/gfx/LayerRenderer$Frame;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto CreateFrame() const -> mozilla::jni::Object::LocalRef;
+
+ struct IsContentDocumentDisplayed_t {
+ typedef GeckoLayerClient Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "isContentDocumentDisplayed";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto IsContentDocumentDisplayed() const -> bool;
+
+ struct OnGeckoReady_t {
+ typedef GeckoLayerClient Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "onGeckoReady";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto OnGeckoReady() const -> void;
+
+ struct SetFirstPaintViewport_t {
+ typedef GeckoLayerClient Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float> Args;
+ static constexpr char name[] = "setFirstPaintViewport";
+ static constexpr char signature[] =
+ "(FFFFFFF)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto SetFirstPaintViewport(float, float, float, float, float, float, float) const -> void;
+
+ struct SyncFrameMetrics_t {
+ typedef GeckoLayerClient Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ float,
+ bool,
+ int32_t> Args;
+ static constexpr char name[] = "syncFrameMetrics";
+ static constexpr char signature[] =
+ "(FFFFFFFIIIIFZI)Lorg/mozilla/gecko/gfx/ViewTransform;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto SyncFrameMetrics(float, float, float, float, float, float, float, int32_t, int32_t, int32_t, int32_t, float, bool, int32_t) const -> mozilla::jni::Object::LocalRef;
+
+ struct SynthesizeNativeMouseEvent_t {
+ typedef GeckoLayerClient Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "synthesizeNativeMouseEvent";
+ static constexpr char signature[] =
+ "(III)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto SynthesizeNativeMouseEvent(int32_t, int32_t, int32_t) const -> void;
+
+ struct SynthesizeNativeTouchPoint_t {
+ typedef GeckoLayerClient Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ double,
+ int32_t> Args;
+ static constexpr char name[] = "synthesizeNativeTouchPoint";
+ static constexpr char signature[] =
+ "(IIIIDI)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto SynthesizeNativeTouchPoint(int32_t, int32_t, int32_t, int32_t, double, int32_t) const -> void;
+
+ struct ClearColor_t {
+ typedef GeckoLayerClient Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "mClearColor";
+ static constexpr char signature[] =
+ "I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto ClearColor() const -> int32_t;
+
+ auto ClearColor(int32_t) const -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class ImmutableViewportMetrics : public mozilla::jni::ObjectBase<ImmutableViewportMetrics>
+{
+public:
+ static const char name[];
+
+ explicit ImmutableViewportMetrics(const Context& ctx) : ObjectBase<ImmutableViewportMetrics>(ctx) {}
+
+ struct New_t {
+ typedef ImmutableViewportMetrics Owner;
+ typedef ImmutableViewportMetrics::LocalRef ReturnType;
+ typedef ImmutableViewportMetrics::Param SetterType;
+ typedef mozilla::jni::Args<
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ float,
+ int32_t,
+ int32_t,
+ float> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "(FFFFFFFFFFIIF)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New(float, float, float, float, float, float, float, float, float, float, int32_t, int32_t, float) -> ImmutableViewportMetrics::LocalRef;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+
+};
+
+class LayerRenderer : public mozilla::jni::ObjectBase<LayerRenderer>
+{
+public:
+ static const char name[];
+
+ explicit LayerRenderer(const Context& ctx) : ObjectBase<LayerRenderer>(ctx) {}
+
+ class Frame;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class LayerRenderer::Frame : public mozilla::jni::ObjectBase<Frame>
+{
+public:
+ static const char name[];
+
+ explicit Frame(const Context& ctx) : ObjectBase<Frame>(ctx) {}
+
+ struct BeginDrawing_t {
+ typedef Frame Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "beginDrawing";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto BeginDrawing() const -> void;
+
+ struct EndDrawing_t {
+ typedef Frame Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "endDrawing";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto EndDrawing() const -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class LayerView : public mozilla::jni::ObjectBase<LayerView>
+{
+public:
+ static const char name[];
+
+ explicit LayerView(const Context& ctx) : ObjectBase<LayerView>(ctx) {}
+
+ class Compositor;
+
+ struct GetCompositor_t {
+ typedef LayerView Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getCompositor";
+ static constexpr char signature[] =
+ "()Ljava/lang/Object;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto GetCompositor() const -> mozilla::jni::Object::LocalRef;
+
+ struct UpdateZoomedView_t {
+ typedef LayerView Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteBuffer::Param> Args;
+ static constexpr char name[] = "updateZoomedView";
+ static constexpr char signature[] =
+ "(Ljava/nio/ByteBuffer;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto UpdateZoomedView(mozilla::jni::ByteBuffer::Param) -> void;
+
+ struct CompositorCreated_t {
+ typedef LayerView Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "mCompositorCreated";
+ static constexpr char signature[] =
+ "Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto CompositorCreated() const -> bool;
+
+ auto CompositorCreated(bool) const -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class LayerView::Compositor : public mozilla::jni::ObjectBase<Compositor>
+{
+public:
+ static const char name[];
+
+ explicit Compositor(const Context& ctx) : ObjectBase<Compositor>(ctx) {}
+
+ struct AttachToJava_t {
+ typedef Compositor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "attachToJava";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/gfx/GeckoLayerClient;Lorg/mozilla/gecko/gfx/NativePanZoomController;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct CreateCompositor_t {
+ typedef Compositor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "createCompositor";
+ static constexpr char signature[] =
+ "(IILjava/lang/Object;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ struct Destroy_t {
+ typedef Compositor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "destroy";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Destroy() const -> void;
+
+ struct DisposeNative_t {
+ typedef Compositor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disposeNative";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnSizeChanged_t {
+ typedef Compositor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "onSizeChanged";
+ static constexpr char signature[] =
+ "(IIII)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct Reattach_t {
+ typedef Compositor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "reattach";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Reattach() const -> void;
+
+ struct SyncInvalidateAndScheduleComposite_t {
+ typedef Compositor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "syncInvalidateAndScheduleComposite";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct SyncPauseCompositor_t {
+ typedef Compositor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "syncPauseCompositor";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct SyncResumeResizeCompositor_t {
+ typedef Compositor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "syncResumeResizeCompositor";
+ static constexpr char signature[] =
+ "(IILjava/lang/Object;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class NativePanZoomController : public mozilla::jni::ObjectBase<NativePanZoomController>
+{
+public:
+ static const char name[];
+
+ explicit NativePanZoomController(const Context& ctx) : ObjectBase<NativePanZoomController>(ctx) {}
+
+ struct AdjustScrollForSurfaceShift_t {
+ typedef NativePanZoomController Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ float,
+ float> Args;
+ static constexpr char name[] = "adjustScrollForSurfaceShift";
+ static constexpr char signature[] =
+ "(FF)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct Destroy_t {
+ typedef NativePanZoomController Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "destroy";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Destroy() const -> void;
+
+ struct DisposeNative_t {
+ typedef NativePanZoomController Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disposeNative";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct HandleMotionEvent_t {
+ typedef NativePanZoomController Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int64_t,
+ int32_t,
+ mozilla::jni::IntArray::Param,
+ mozilla::jni::FloatArray::Param,
+ mozilla::jni::FloatArray::Param,
+ mozilla::jni::FloatArray::Param,
+ mozilla::jni::FloatArray::Param,
+ mozilla::jni::FloatArray::Param,
+ mozilla::jni::FloatArray::Param> Args;
+ static constexpr char name[] = "handleMotionEvent";
+ static constexpr char signature[] =
+ "(IIJI[I[F[F[F[F[F[F)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct HandleMotionEventVelocity_t {
+ typedef NativePanZoomController Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int64_t,
+ float> Args;
+ static constexpr char name[] = "handleMotionEventVelocity";
+ static constexpr char signature[] =
+ "(JF)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct HandleMouseEvent_t {
+ typedef NativePanZoomController Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int64_t,
+ int32_t,
+ float,
+ float,
+ int32_t> Args;
+ static constexpr char name[] = "handleMouseEvent";
+ static constexpr char signature[] =
+ "(IJIFFI)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct HandleScrollEvent_t {
+ typedef NativePanZoomController Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ int64_t,
+ int32_t,
+ float,
+ float,
+ float,
+ float> Args;
+ static constexpr char name[] = "handleScrollEvent";
+ static constexpr char signature[] =
+ "(JIFFFF)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct SetIsLongpressEnabled_t {
+ typedef NativePanZoomController Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "nativeSetIsLongpressEnabled";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OnSelectionDragState_t {
+ typedef NativePanZoomController Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "onSelectionDragState";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto OnSelectionDragState(bool) const -> void;
+
+ struct SetScrollingRootContent_t {
+ typedef NativePanZoomController Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "setScrollingRootContent";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto SetScrollingRootContent(bool) const -> void;
+
+ struct UpdateOverscrollOffset_t {
+ typedef NativePanZoomController Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ float,
+ float> Args;
+ static constexpr char name[] = "updateOverscrollOffset";
+ static constexpr char signature[] =
+ "(FF)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto UpdateOverscrollOffset(float, float) const -> void;
+
+ struct UpdateOverscrollVelocity_t {
+ typedef NativePanZoomController Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ float,
+ float> Args;
+ static constexpr char name[] = "updateOverscrollVelocity";
+ static constexpr char signature[] =
+ "(FF)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto UpdateOverscrollVelocity(float, float) const -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class ProgressiveUpdateData : public mozilla::jni::ObjectBase<ProgressiveUpdateData>
+{
+public:
+ static const char name[];
+
+ explicit ProgressiveUpdateData(const Context& ctx) : ObjectBase<ProgressiveUpdateData>(ctx) {}
+
+ struct New_t {
+ typedef ProgressiveUpdateData Owner;
+ typedef ProgressiveUpdateData::LocalRef ReturnType;
+ typedef ProgressiveUpdateData::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New() -> ProgressiveUpdateData::LocalRef;
+
+ struct SetViewport_t {
+ typedef ProgressiveUpdateData Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "setViewport";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/gfx/ImmutableViewportMetrics;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto SetViewport(mozilla::jni::Object::Param) const -> void;
+
+ struct Abort_t {
+ typedef ProgressiveUpdateData Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "abort";
+ static constexpr char signature[] =
+ "Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Abort() const -> bool;
+
+ auto Abort(bool) const -> void;
+
+ struct Scale_t {
+ typedef ProgressiveUpdateData Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "scale";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Scale() const -> float;
+
+ auto Scale(float) const -> void;
+
+ struct X_t {
+ typedef ProgressiveUpdateData Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "x";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto X() const -> float;
+
+ auto X(float) const -> void;
+
+ struct Y_t {
+ typedef ProgressiveUpdateData Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "y";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Y() const -> float;
+
+ auto Y(float) const -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class StackScroller : public mozilla::jni::ObjectBase<StackScroller>
+{
+public:
+ static const char name[];
+
+ explicit StackScroller(const Context& ctx) : ObjectBase<StackScroller>(ctx) {}
+
+ struct New_t {
+ typedef StackScroller Owner;
+ typedef StackScroller::LocalRef ReturnType;
+ typedef StackScroller::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "(Landroid/content/Context;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New(mozilla::jni::Object::Param, StackScroller::LocalRef*) -> nsresult;
+
+ struct AbortAnimation_t {
+ typedef StackScroller Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "abortAnimation";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto AbortAnimation() const -> nsresult;
+
+ struct ComputeScrollOffset_t {
+ typedef StackScroller Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ int64_t> Args;
+ static constexpr char name[] = "computeScrollOffset";
+ static constexpr char signature[] =
+ "(J)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto ComputeScrollOffset(int64_t, bool*) const -> nsresult;
+
+ struct Fling_t {
+ typedef StackScroller Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int64_t> Args;
+ static constexpr char name[] = "fling";
+ static constexpr char signature[] =
+ "(IIIIIIIIIIJ)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Fling(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int64_t) const -> nsresult;
+
+ struct ForceFinished_t {
+ typedef StackScroller Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "forceFinished";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto ForceFinished(bool) const -> nsresult;
+
+ struct GetCurrSpeedX_t {
+ typedef StackScroller Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getCurrSpeedX";
+ static constexpr char signature[] =
+ "()F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto GetCurrSpeedX(float*) const -> nsresult;
+
+ struct GetCurrSpeedY_t {
+ typedef StackScroller Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getCurrSpeedY";
+ static constexpr char signature[] =
+ "()F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto GetCurrSpeedY(float*) const -> nsresult;
+
+ struct GetCurrX_t {
+ typedef StackScroller Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getCurrX";
+ static constexpr char signature[] =
+ "()I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto GetCurrX(int32_t*) const -> nsresult;
+
+ struct GetCurrY_t {
+ typedef StackScroller Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getCurrY";
+ static constexpr char signature[] =
+ "()I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto GetCurrY(int32_t*) const -> nsresult;
+
+ struct GetFinalX_t {
+ typedef StackScroller Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getFinalX";
+ static constexpr char signature[] =
+ "()I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto GetFinalX(int32_t*) const -> nsresult;
+
+ struct GetFinalY_t {
+ typedef StackScroller Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getFinalY";
+ static constexpr char signature[] =
+ "()I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto GetFinalY(int32_t*) const -> nsresult;
+
+ struct InitContants_t {
+ typedef StackScroller Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "initContants";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto InitContants() -> nsresult;
+
+ struct IsFinished_t {
+ typedef StackScroller Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "isFinished";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto IsFinished(bool*) const -> nsresult;
+
+ struct SetFinalX_t {
+ typedef StackScroller Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "setFinalX";
+ static constexpr char signature[] =
+ "(I)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto SetFinalX(int32_t) const -> nsresult;
+
+ struct SpringBack_t {
+ typedef StackScroller Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int64_t> Args;
+ static constexpr char name[] = "springBack";
+ static constexpr char signature[] =
+ "(IIIIIIJ)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto SpringBack(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int64_t, bool*) const -> nsresult;
+
+ struct StartScroll_t {
+ typedef StackScroller Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int64_t,
+ int32_t> Args;
+ static constexpr char name[] = "startScroll";
+ static constexpr char signature[] =
+ "(IIIIJI)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto StartScroll(int32_t, int32_t, int32_t, int32_t, int64_t, int32_t) const -> nsresult;
+
+ struct ViscousFluid_t {
+ typedef StackScroller Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<
+ float> Args;
+ static constexpr char name[] = "viscousFluid";
+ static constexpr char signature[] =
+ "(F)F";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto ViscousFluid(float, float*) -> nsresult;
+
+ static const int32_t FLING_MODE = 1;
+
+ static const int32_t SCROLL_MODE = 0;
+
+ struct MFlywheel_t {
+ typedef StackScroller Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "mFlywheel";
+ static constexpr char signature[] =
+ "Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto MFlywheel(bool*) const -> nsresult;
+
+ struct MMode_t {
+ typedef StackScroller Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "mMode";
+ static constexpr char signature[] =
+ "I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto MMode(int32_t*) const -> nsresult;
+
+ auto MMode(int32_t) const -> nsresult;
+
+ struct MScrollerX_t {
+ typedef StackScroller Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "mScrollerX";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/gfx/StackScroller$SplineStackScroller;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto MScrollerX(mozilla::jni::Object::LocalRef*) const -> nsresult;
+
+ struct MScrollerY_t {
+ typedef StackScroller Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "mScrollerY";
+ static constexpr char signature[] =
+ "Lorg/mozilla/gecko/gfx/StackScroller$SplineStackScroller;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto MScrollerY(mozilla::jni::Object::LocalRef*) const -> nsresult;
+
+ struct SViscousFluidNormalize_t {
+ typedef StackScroller Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "sViscousFluidNormalize";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto SViscousFluidNormalize(float*) -> nsresult;
+
+ static auto SViscousFluidNormalize(float) -> nsresult;
+
+ struct SViscousFluidScale_t {
+ typedef StackScroller Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "sViscousFluidScale";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::NSRESULT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto SViscousFluidScale(float*) -> nsresult;
+
+ static auto SViscousFluidScale(float) -> nsresult;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class ViewTransform : public mozilla::jni::ObjectBase<ViewTransform>
+{
+public:
+ static const char name[];
+
+ explicit ViewTransform(const Context& ctx) : ObjectBase<ViewTransform>(ctx) {}
+
+ struct New_t {
+ typedef ViewTransform Owner;
+ typedef ViewTransform::LocalRef ReturnType;
+ typedef ViewTransform::Param SetterType;
+ typedef mozilla::jni::Args<
+ float,
+ float,
+ float> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "(FFF)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New(float, float, float) -> ViewTransform::LocalRef;
+
+ struct FixedLayerMarginBottom_t {
+ typedef ViewTransform Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "fixedLayerMarginBottom";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto FixedLayerMarginBottom() const -> float;
+
+ auto FixedLayerMarginBottom(float) const -> void;
+
+ struct FixedLayerMarginLeft_t {
+ typedef ViewTransform Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "fixedLayerMarginLeft";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto FixedLayerMarginLeft() const -> float;
+
+ auto FixedLayerMarginLeft(float) const -> void;
+
+ struct FixedLayerMarginRight_t {
+ typedef ViewTransform Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "fixedLayerMarginRight";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto FixedLayerMarginRight() const -> float;
+
+ auto FixedLayerMarginRight(float) const -> void;
+
+ struct FixedLayerMarginTop_t {
+ typedef ViewTransform Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "fixedLayerMarginTop";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto FixedLayerMarginTop() const -> float;
+
+ auto FixedLayerMarginTop(float) const -> void;
+
+ struct Height_t {
+ typedef ViewTransform Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "height";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Height() const -> float;
+
+ auto Height(float) const -> void;
+
+ struct Scale_t {
+ typedef ViewTransform Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "scale";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Scale() const -> float;
+
+ auto Scale(float) const -> void;
+
+ struct Width_t {
+ typedef ViewTransform Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "width";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Width() const -> float;
+
+ auto Width(float) const -> void;
+
+ struct X_t {
+ typedef ViewTransform Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "x";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto X() const -> float;
+
+ auto X(float) const -> void;
+
+ struct Y_t {
+ typedef ViewTransform Owner;
+ typedef float ReturnType;
+ typedef float SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "y";
+ static constexpr char signature[] =
+ "F";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Y() const -> float;
+
+ auto Y(float) const -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class Clipboard : public mozilla::jni::ObjectBase<Clipboard>
+{
+public:
+ static const char name[];
+
+ explicit Clipboard(const Context& ctx) : ObjectBase<Clipboard>(ctx) {}
+
+ struct ClearText_t {
+ typedef Clipboard Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "clearText";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto ClearText() -> void;
+
+ struct GetText_t {
+ typedef Clipboard Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getText";
+ static constexpr char signature[] =
+ "()Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetText() -> mozilla::jni::String::LocalRef;
+
+ struct HasText_t {
+ typedef Clipboard Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "hasText";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto HasText() -> bool;
+
+ struct SetText_t {
+ typedef Clipboard Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "setText";
+ static constexpr char signature[] =
+ "(Ljava/lang/CharSequence;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto SetText(mozilla::jni::String::Param) -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+
+};
+
+class HardwareCodecCapabilityUtils : public mozilla::jni::ObjectBase<HardwareCodecCapabilityUtils>
+{
+public:
+ static const char name[];
+
+ explicit HardwareCodecCapabilityUtils(const Context& ctx) : ObjectBase<HardwareCodecCapabilityUtils>(ctx) {}
+
+ struct HasHWVP9_t {
+ typedef HardwareCodecCapabilityUtils Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "HasHWVP9";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto HasHWVP9() -> bool;
+
+ struct FindDecoderCodecInfoForMimeType_t {
+ typedef HardwareCodecCapabilityUtils Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "findDecoderCodecInfoForMimeType";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto FindDecoderCodecInfoForMimeType(mozilla::jni::String::Param) -> bool;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class NativeJSContainer : public mozilla::jni::ObjectBase<NativeJSContainer>
+{
+public:
+ static const char name[];
+
+ explicit NativeJSContainer(const Context& ctx) : ObjectBase<NativeJSContainer>(ctx) {}
+
+ struct New_t {
+ typedef NativeJSContainer Owner;
+ typedef NativeJSContainer::LocalRef ReturnType;
+ typedef NativeJSContainer::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New() -> NativeJSContainer::LocalRef;
+
+ struct Clone2_t {
+ typedef NativeJSContainer Owner;
+ typedef NativeJSContainer::LocalRef ReturnType;
+ typedef NativeJSContainer::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "clone";
+ static constexpr char signature[] =
+ "()Lorg/mozilla/gecko/util/NativeJSContainer;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct DisposeNative_t {
+ typedef NativeJSContainer Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disposeNative";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+
+ template<class Impl> class Natives;
+};
+
+class NativeJSObject : public mozilla::jni::ObjectBase<NativeJSObject>
+{
+public:
+ static const char name[];
+
+ explicit NativeJSObject(const Context& ctx) : ObjectBase<NativeJSObject>(ctx) {}
+
+ struct New_t {
+ typedef NativeJSObject Owner;
+ typedef NativeJSObject::LocalRef ReturnType;
+ typedef NativeJSObject::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New() -> NativeJSObject::LocalRef;
+
+ struct DisposeNative_t {
+ typedef NativeJSObject Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disposeNative";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto DisposeNative() const -> void;
+
+ struct GetBoolean_t {
+ typedef NativeJSObject Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getBoolean";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetBooleanArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::BooleanArray::LocalRef ReturnType;
+ typedef mozilla::jni::BooleanArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getBooleanArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)[Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetBundle_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getBundle";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Landroid/os/Bundle;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetBundleArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::ObjectArray::LocalRef ReturnType;
+ typedef mozilla::jni::ObjectArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getBundleArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)[Landroid/os/Bundle;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetDouble_t {
+ typedef NativeJSObject Owner;
+ typedef double ReturnType;
+ typedef double SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getDouble";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)D";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetDoubleArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::DoubleArray::LocalRef ReturnType;
+ typedef mozilla::jni::DoubleArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getDoubleArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)[D";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetInt_t {
+ typedef NativeJSObject Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getInt";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetIntArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::IntArray::LocalRef ReturnType;
+ typedef mozilla::jni::IntArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getIntArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)[I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetObject_t {
+ typedef NativeJSObject Owner;
+ typedef NativeJSObject::LocalRef ReturnType;
+ typedef NativeJSObject::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getObject";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Lorg/mozilla/gecko/util/NativeJSObject;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetObjectArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::ObjectArray::LocalRef ReturnType;
+ typedef mozilla::jni::ObjectArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getObjectArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)[Lorg/mozilla/gecko/util/NativeJSObject;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetString_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getString";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Ljava/lang/String;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetStringArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::ObjectArray::LocalRef ReturnType;
+ typedef mozilla::jni::ObjectArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "getStringArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)[Ljava/lang/String;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct Has_t {
+ typedef NativeJSObject Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "has";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptBoolean_t {
+ typedef NativeJSObject Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ bool> Args;
+ static constexpr char name[] = "optBoolean";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Z)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptBooleanArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::BooleanArray::LocalRef ReturnType;
+ typedef mozilla::jni::BooleanArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::BooleanArray::Param> Args;
+ static constexpr char name[] = "optBooleanArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;[Z)[Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptBundle_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "optBundle";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Landroid/os/Bundle;)Landroid/os/Bundle;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptBundleArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::ObjectArray::LocalRef ReturnType;
+ typedef mozilla::jni::ObjectArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::ObjectArray::Param> Args;
+ static constexpr char name[] = "optBundleArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;[Landroid/os/Bundle;)[Landroid/os/Bundle;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptDouble_t {
+ typedef NativeJSObject Owner;
+ typedef double ReturnType;
+ typedef double SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ double> Args;
+ static constexpr char name[] = "optDouble";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;D)D";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptDoubleArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::DoubleArray::LocalRef ReturnType;
+ typedef mozilla::jni::DoubleArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::DoubleArray::Param> Args;
+ static constexpr char name[] = "optDoubleArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;[D)[D";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptInt_t {
+ typedef NativeJSObject Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ int32_t> Args;
+ static constexpr char name[] = "optInt";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;I)I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptIntArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::IntArray::LocalRef ReturnType;
+ typedef mozilla::jni::IntArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::IntArray::Param> Args;
+ static constexpr char name[] = "optIntArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;[I)[I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptObject_t {
+ typedef NativeJSObject Owner;
+ typedef NativeJSObject::LocalRef ReturnType;
+ typedef NativeJSObject::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ NativeJSObject::Param> Args;
+ static constexpr char name[] = "optObject";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Lorg/mozilla/gecko/util/NativeJSObject;)Lorg/mozilla/gecko/util/NativeJSObject;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptObjectArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::ObjectArray::LocalRef ReturnType;
+ typedef mozilla::jni::ObjectArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::ObjectArray::Param> Args;
+ static constexpr char name[] = "optObjectArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;[Lorg/mozilla/gecko/util/NativeJSObject;)[Lorg/mozilla/gecko/util/NativeJSObject;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptString_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "optString";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OptStringArray_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::ObjectArray::LocalRef ReturnType;
+ typedef mozilla::jni::ObjectArray::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::ObjectArray::Param> Args;
+ static constexpr char name[] = "optStringArray";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct ToBundle_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "toBundle";
+ static constexpr char signature[] =
+ "()Landroid/os/Bundle;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct ToString_t {
+ typedef NativeJSObject Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "toString";
+ static constexpr char signature[] =
+ "()Ljava/lang/String;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+
+ template<class Impl> class Natives;
+};
+
+} /* java */
+} /* mozilla */
+#endif // GeneratedJNIWrappers_h
diff --git a/widget/android/GfxInfo.cpp b/widget/android/GfxInfo.cpp
new file mode 100644
index 000000000..af63184a7
--- /dev/null
+++ b/widget/android/GfxInfo.cpp
@@ -0,0 +1,621 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "prprf.h"
+#include "nsHashKeys.h"
+#include "nsVersionComparator.h"
+#include "AndroidBridge.h"
+#include "nsIWindowWatcher.h"
+#include "nsServiceManagerUtils.h"
+
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#include "nsICrashReporter.h"
+#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1"
+#endif
+
+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 blacklisted 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;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString & aDwriteVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString & aCleartypeParams)
+{
+ return NS_ERROR_FAILURE;
+}
+
+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();
+
+ 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(nsAString & aAdapterRAM)
+{
+ EnsureInitialized();
+ aAdapterRAM.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & 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::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;
+}
+
+void
+GfxInfo::AddCrashReportAnnotations()
+{
+#if defined(MOZ_CRASHREPORTER)
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterVendorID"),
+ mGLStrings->Vendor());
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDeviceID"),
+ mGLStrings->Renderer());
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDriverVersion"),
+ mGLStrings->Version());
+
+ /* Add an App Note for now so that we get the data immediately. These
+ * can go away after we store the above in the socorro db */
+ nsAutoCString note;
+ note.AppendPrintf("AdapterDescription: '%s'\n", mAdapterDescription.get());
+
+ CrashReporter::AppendAppNotesToCrashReport(note);
+#endif
+}
+
+const nsTArray<GfxDriverInfo>&
+GfxInfo::GetGfxDriverInfo()
+{
+ if (mDriverInfo->IsEmpty()) {
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Android,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAll), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_STATUS_OK,
+ DRIVER_COMPARISON_IGNORED, GfxDriverInfo::allDriverVersions,
+ "FEATURE_OK_FORCE_OPENGL" );
+ }
+
+ return *mDriverInfo;
+}
+
+nsresult
+GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString &aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString &aFailureId,
+ OperatingSystem* aOS /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ OperatingSystem os = mOS;
+ if (aOS)
+ *aOS = os;
+
+ // OpenGL layers are never blacklisted 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 (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 = mozilla::AndroidBridge::Bridge()->GetHWEncoderCapability() ? nsIGfxInfo::FEATURE_STATUS_OK : nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_WEBRTC_ENCODE";
+ return NS_OK;
+ }
+ }
+ if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_DECODE) {
+ if (mozilla::AndroidBridge::Bridge()) {
+ *aStatus = mozilla::AndroidBridge::Bridge()->GetHWDecoderCapability() ? nsIGfxInfo::FEATURE_STATUS_OK : nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_WEBRTC_DECODE";
+ 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;
+ }
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
+}
+
+#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;
+}
+
+#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;
+}
+
+}
+}
diff --git a/widget/android/GfxInfo.h b/widget/android/GfxInfo.h
new file mode 100644
index 000000000..a3b06099b
--- /dev/null
+++ b/widget/android/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 "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 GetCleartypeParameters(nsAString & aCleartypeParams) override;
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active) override;
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+ 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:
+
+ void AddCrashReportAnnotations();
+
+ bool mInitialized;
+
+ class GLStrings;
+ UniquePtr<GLStrings> mGLStrings;
+
+ nsCString mAdapterDescription;
+
+ OperatingSystem mOS;
+
+ nsString mModel, mHardware, mManufacturer, mProduct;
+ nsCString mOSVersion;
+ uint32_t mOSVersionInteger;
+ int32_t mSDKVersion;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/android/NativeJSContainer.cpp b/widget/android/NativeJSContainer.cpp
new file mode 100644
index 000000000..b8c434656
--- /dev/null
+++ b/widget/android/NativeJSContainer.cpp
@@ -0,0 +1,881 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "NativeJSContainer.h"
+
+#include <jni.h>
+
+#include "Bundle.h"
+#include "GeneratedJNINatives.h"
+#include "MainThreadUtils.h"
+#include "jsapi.h"
+#include "nsJSUtils.h"
+
+#include <mozilla/Vector.h>
+#include <mozilla/jni/Accessors.h>
+#include <mozilla/jni/Refs.h>
+#include <mozilla/jni/Utils.h>
+
+/**
+ * NativeJSContainer.cpp implements the native methods in both
+ * NativeJSContainer and NativeJSObject, using JSAPI to retrieve values from a
+ * JSObject and using JNI to return those values to Java code.
+ */
+
+namespace mozilla {
+namespace widget {
+
+namespace {
+
+bool CheckThread()
+{
+ if (!NS_IsMainThread()) {
+ jni::ThrowException("java/lang/IllegalThreadStateException",
+ "Not on Gecko thread");
+ return false;
+ }
+ return true;
+}
+
+template<class C, typename T> bool
+CheckJNIArgument(const jni::Ref<C, T>& arg)
+{
+ if (!arg) {
+ jni::ThrowException("java/lang/IllegalArgumentException",
+ "Null argument");
+ }
+ return !!arg;
+}
+
+nsresult
+CheckSDKCall(nsresult rv)
+{
+ if (NS_FAILED(rv)) {
+ jni::ThrowException("java/lang/UnsupportedOperationException",
+ "SDK JNI call failed");
+ }
+ return rv;
+}
+
+// Convert a JNI string to a char16_t string that JSAPI expects.
+class JSJNIString final
+{
+ JNIEnv* const mEnv;
+ jni::String::Param mJNIString;
+ const char16_t* const mJSString;
+
+public:
+ JSJNIString(JNIEnv* env, jni::String::Param str)
+ : mEnv(env)
+ , mJNIString(str)
+ , mJSString(!str ? nullptr : reinterpret_cast<const char16_t*>(
+ mEnv->GetStringChars(str.Get(), nullptr)))
+ {}
+
+ ~JSJNIString() {
+ if (mJNIString) {
+ mEnv->ReleaseStringChars(mJNIString.Get(),
+ reinterpret_cast<const jchar*>(mJSString));
+ }
+ }
+
+ operator const char16_t*() const {
+ return mJSString;
+ }
+
+ size_t Length() const {
+ return static_cast<size_t>(mEnv->GetStringLength(mJNIString.Get()));
+ }
+};
+
+} // namepsace
+
+class NativeJSContainerImpl final
+ : public NativeJSObject::Natives<NativeJSContainerImpl>
+ , public NativeJSContainer::Natives<NativeJSContainerImpl>
+{
+ typedef NativeJSContainerImpl Self;
+ typedef NativeJSContainer::Natives<NativeJSContainerImpl> ContainerBase;
+ typedef NativeJSObject::Natives<NativeJSContainerImpl> ObjectBase;
+
+ typedef JS::PersistentRooted<JSObject*> PersistentObject;
+
+ JNIEnv* const mEnv;
+ // Context that the object is valid in
+ JSContext* const mJSContext;
+ // Root JS object
+ PersistentObject mJSObject;
+ // Children objects
+ Vector<NativeJSObject::GlobalRef, 0> mChildren;
+
+ bool CheckObject() const
+ {
+ if (!mJSObject) {
+ jni::ThrowException("java/lang/NullPointerException",
+ "Null JSObject");
+ }
+ return !!mJSObject;
+ }
+
+ bool CheckJSCall(bool result) const
+ {
+ if (!result) {
+ JS_ClearPendingException(mJSContext);
+ jni::ThrowException("java/lang/UnsupportedOperationException",
+ "JSAPI call failed");
+ }
+ return result;
+ }
+
+ // Check that a JS Value contains a particular property type as indicaed by
+ // the property's InValue method (e.g. StringProperty::InValue).
+ bool CheckProperty(bool (Self::*InValue)(JS::HandleValue) const,
+ JS::HandleValue val) const
+ {
+ if (!(this->*InValue)(val)) {
+ // XXX this can happen when converting a double array inside a
+ // Bundle, because double arrays can be misidentified as an int
+ // array. The workaround is to add a dummy first element to the
+ // array that is a floating point value, i.e. [0.5, ...].
+ jni::ThrowException(
+ "org/mozilla/gecko/util/NativeJSObject$InvalidPropertyException",
+ "Property type mismatch");
+ return false;
+ }
+ return true;
+ }
+
+ // Primitive properties
+
+ template<bool (JS::Value::*IsType)() const> bool
+ PrimitiveInValue(JS::HandleValue val) const
+ {
+ return (static_cast<const JS::Value&>(val).*IsType)();
+ }
+
+ template<typename U, U (JS::Value::*ToType)() const> U
+ PrimitiveFromValue(JS::HandleValue val) const
+ {
+ return (static_cast<const JS::Value&>(val).*ToType)();
+ }
+
+ template<class Prop> typename Prop::NativeArray
+ PrimitiveNewArray(JS::HandleObject array, size_t length) const
+ {
+ typedef typename Prop::JNIType JNIType;
+
+ // Fill up a temporary buffer for our array, then use
+ // JNIEnv::Set*ArrayRegion to fill out array in one go.
+
+ UniquePtr<JNIType[]> buffer = MakeUnique<JNIType[]>(length);
+ for (size_t i = 0; i < length; i++) {
+ JS::RootedValue elem(mJSContext);
+ if (!CheckJSCall(JS_GetElement(mJSContext, array, i, &elem)) ||
+ !CheckProperty(Prop::InValue, elem)) {
+ return nullptr;
+ }
+ buffer[i] = JNIType((this->*Prop::FromValue)(elem));
+ }
+ auto jarray = Prop::NativeArray::Adopt(
+ mEnv, (mEnv->*Prop::NewJNIArray)(length));
+ if (!jarray) {
+ return nullptr;
+ }
+ (mEnv->*Prop::SetJNIArrayRegion)(
+ jarray.Get(), 0, length, buffer.get());
+ if (mEnv->ExceptionCheck()) {
+ return nullptr;
+ }
+ return jarray;
+ }
+
+ template<typename U, typename UA, typename V, typename VA,
+ bool (JS::Value::*IsType)() const,
+ U (JS::Value::*ToType)() const,
+ VA (JNIEnv::*NewArray_)(jsize),
+ void (JNIEnv::*SetArrayRegion_)(VA, jsize, jsize, const V*)>
+ struct PrimitiveProperty
+ {
+ // C++ type for a primitive property (e.g. bool)
+ typedef U NativeType;
+ // C++ type for the fallback value used in opt* methods
+ typedef U NativeFallback;
+ // Type for an array of the primitive type (e.g. BooleanArray::LocalRef)
+ typedef typename UA::LocalRef NativeArray;
+ // Type for the fallback value used in opt*Array methods
+ typedef const typename UA::Ref ArrayFallback;
+ // JNI type (e.g. jboolean)
+ typedef V JNIType;
+
+ // JNIEnv function to create a new JNI array of the primiive type
+ typedef decltype(NewArray_) NewJNIArray_t;
+ static constexpr NewJNIArray_t NewJNIArray = NewArray_;
+
+ // JNIEnv function to fill a JNI array of the primiive type
+ typedef decltype(SetArrayRegion_) SetJNIArrayRegion_t;
+ static constexpr SetJNIArrayRegion_t SetJNIArrayRegion = SetArrayRegion_;
+
+ // Function to determine if a JS Value contains the primitive type
+ typedef decltype(&Self::PrimitiveInValue<IsType>) InValue_t;
+ static constexpr InValue_t InValue = &Self::PrimitiveInValue<IsType>;
+
+ // Function to convert a JS Value to the primitive type
+ typedef decltype(&Self::PrimitiveFromValue<U, ToType>) FromValue_t;
+ static constexpr FromValue_t FromValue
+ = &Self::PrimitiveFromValue<U, ToType>;
+
+ // Function to convert a JS array to a JNI array
+ typedef decltype(&Self::PrimitiveNewArray<PrimitiveProperty>) NewArray_t;
+ static constexpr NewArray_t NewArray
+ = &Self::PrimitiveNewArray<PrimitiveProperty>;
+ };
+
+ // String properties
+
+ bool StringInValue(JS::HandleValue val) const
+ {
+ return val.isString();
+ }
+
+ jni::String::LocalRef
+ StringFromValue(const JS::HandleString str) const
+ {
+ nsAutoJSString autoStr;
+ if (!CheckJSCall(autoStr.init(mJSContext, str))) {
+ return nullptr;
+ }
+ // StringParam can automatically convert a nsString to jstring.
+ return jni::StringParam(autoStr, mEnv);
+ }
+
+ jni::String::LocalRef
+ StringFromValue(JS::HandleValue val)
+ {
+ const JS::RootedString str(mJSContext, val.toString());
+ return StringFromValue(str);
+ }
+
+ // Bundle properties
+
+ sdk::Bundle::LocalRef
+ BundleFromValue(const JS::HandleObject obj)
+ {
+ JS::Rooted<JS::IdVector> ids(mJSContext, JS::IdVector(mJSContext));
+ if (!CheckJSCall(JS_Enumerate(mJSContext, obj, &ids))) {
+ return nullptr;
+ }
+
+ const size_t length = ids.length();
+ sdk::Bundle::LocalRef newBundle(mEnv);
+ NS_ENSURE_SUCCESS(CheckSDKCall(
+ sdk::Bundle::New(length, &newBundle)), nullptr);
+
+ // Iterate through each property of the JS object. For each property,
+ // determine its type from a list of supported types, and convert that
+ // proeprty to the supported type.
+
+ for (size_t i = 0; i < ids.length(); i++) {
+ const JS::RootedId id(mJSContext, ids[i]);
+ JS::RootedValue idVal(mJSContext);
+ if (!CheckJSCall(JS_IdToValue(mJSContext, id, &idVal))) {
+ return nullptr;
+ }
+
+ const JS::RootedString idStr(mJSContext,
+ JS::ToString(mJSContext, idVal));
+ if (!CheckJSCall(!!idStr)) {
+ return nullptr;
+ }
+
+ jni::String::LocalRef name = StringFromValue(idStr);
+ JS::RootedValue val(mJSContext);
+ if (!name ||
+ !CheckJSCall(JS_GetPropertyById(mJSContext, obj, id, &val))) {
+ return nullptr;
+ }
+
+#define PUT_IN_BUNDLE_IF_TYPE_IS(TYPE) \
+ if ((this->*TYPE##Property::InValue)(val)) { \
+ auto jval = (this->*TYPE##Property::FromValue)(val); \
+ if (mEnv->ExceptionCheck()) { \
+ return nullptr; \
+ } \
+ NS_ENSURE_SUCCESS(CheckSDKCall( \
+ newBundle->Put##TYPE(name, jval)), nullptr); \
+ continue; \
+ } \
+ ((void) 0) // Accommodate trailing semicolon.
+
+ // Scalar values are faster to check, so check them first.
+ PUT_IN_BUNDLE_IF_TYPE_IS(Boolean);
+ // Int can be casted to double, so check int first.
+ PUT_IN_BUNDLE_IF_TYPE_IS(Int);
+ PUT_IN_BUNDLE_IF_TYPE_IS(Double);
+ PUT_IN_BUNDLE_IF_TYPE_IS(String);
+ // There's no "putObject", so don't check ObjectProperty
+
+ // Check for array types if scalar checks all failed.
+ // XXX empty arrays are treated as boolean arrays. Workaround is
+ // to always have a dummy element to create a non-empty array.
+ PUT_IN_BUNDLE_IF_TYPE_IS(BooleanArray);
+ // XXX because we only check the first element of an array,
+ // a double array can potentially be seen as an int array.
+ // When that happens, the Bundle conversion will fail.
+ PUT_IN_BUNDLE_IF_TYPE_IS(IntArray);
+ PUT_IN_BUNDLE_IF_TYPE_IS(DoubleArray);
+ PUT_IN_BUNDLE_IF_TYPE_IS(StringArray);
+ // There's no "putObjectArray", so don't check ObjectArrayProperty
+ // There's no "putBundleArray", so don't check BundleArrayProperty
+
+ // Use Bundle as the default catch-all for objects
+ PUT_IN_BUNDLE_IF_TYPE_IS(Bundle);
+
+#undef PUT_IN_BUNDLE_IF_TYPE_IS
+
+ // We tried all supported types; just bail.
+ jni::ThrowException("java/lang/UnsupportedOperationException",
+ "Unsupported property type");
+ return nullptr;
+ }
+ return jni::Object::LocalRef::Adopt(newBundle.Env(),
+ newBundle.Forget());
+ }
+
+ sdk::Bundle::LocalRef
+ BundleFromValue(JS::HandleValue val)
+ {
+ if (val.isNull()) {
+ return nullptr;
+ }
+ JS::RootedObject object(mJSContext, &val.toObject());
+ return BundleFromValue(object);
+ }
+
+ // Object properties
+
+ bool ObjectInValue(JS::HandleValue val) const
+ {
+ return val.isObjectOrNull();
+ }
+
+ NativeJSObject::LocalRef
+ ObjectFromValue(JS::HandleValue val)
+ {
+ if (val.isNull()) {
+ return nullptr;
+ }
+ JS::RootedObject object(mJSContext, &val.toObject());
+ return CreateChild(object);
+ }
+
+ template<class Prop> typename Prop::NativeArray
+ ObjectNewArray(JS::HandleObject array, size_t length)
+ {
+ auto jarray = Prop::NativeArray::Adopt(mEnv, mEnv->NewObjectArray(
+ length, typename Prop::ClassType::Context().ClassRef(),
+ nullptr));
+ if (!jarray) {
+ return nullptr;
+ }
+
+ // For object arrays, we have to set each element separately.
+ for (size_t i = 0; i < length; i++) {
+ JS::RootedValue elem(mJSContext);
+ if (!CheckJSCall(JS_GetElement(mJSContext, array, i, &elem)) ||
+ !CheckProperty(Prop::InValue, elem)) {
+ return nullptr;
+ }
+ mEnv->SetObjectArrayElement(
+ jarray.Get(), i, (this->*Prop::FromValue)(elem).Get());
+ if (mEnv->ExceptionCheck()) {
+ return nullptr;
+ }
+ }
+ return jarray;
+ }
+
+ template<class Class,
+ bool (Self::*InValue_)(JS::HandleValue) const,
+ typename Class::LocalRef (Self::*FromValue_)(JS::HandleValue)>
+ struct BaseObjectProperty
+ {
+ // JNI class for the object type (e.g. jni::String)
+ typedef Class ClassType;
+
+ // See comments in PrimitiveProperty.
+ typedef typename ClassType::LocalRef NativeType;
+ typedef const typename ClassType::Ref NativeFallback;
+ typedef typename jni::ObjectArray::LocalRef NativeArray;
+ typedef const jni::ObjectArray::Ref ArrayFallback;
+
+ typedef decltype(InValue_) InValue_t;
+ static constexpr InValue_t InValue = InValue_;
+
+ typedef decltype(FromValue_) FromValue_t;
+ static constexpr FromValue_t FromValue = FromValue_;
+
+ typedef decltype(&Self::ObjectNewArray<BaseObjectProperty>) NewArray_t;
+ static constexpr NewArray_t NewArray
+ = &Self::ObjectNewArray<BaseObjectProperty>;
+ };
+
+ // Array properties
+
+ template<class Prop> bool
+ ArrayInValue(JS::HandleValue val) const
+ {
+ if (!val.isObject()) {
+ return false;
+ }
+ JS::RootedObject obj(mJSContext, &val.toObject());
+ bool isArray;
+ uint32_t length = 0;
+ if (!JS_IsArrayObject(mJSContext, obj, &isArray) ||
+ !isArray ||
+ !JS_GetArrayLength(mJSContext, obj, &length)) {
+ JS_ClearPendingException(mJSContext);
+ return false;
+ }
+ if (!length) {
+ // Empty arrays are always okay.
+ return true;
+ }
+ // We only check to see the first element is the target type. If the
+ // array has mixed types, we'll throw an error during actual conversion.
+ JS::RootedValue element(mJSContext);
+ if (!JS_GetElement(mJSContext, obj, 0, &element)) {
+ JS_ClearPendingException(mJSContext);
+ return false;
+ }
+ return (this->*Prop::InValue)(element);
+ }
+
+ template<class Prop> typename Prop::NativeArray
+ ArrayFromValue(JS::HandleValue val)
+ {
+ JS::RootedObject obj(mJSContext, &val.toObject());
+ uint32_t length = 0;
+ if (!CheckJSCall(JS_GetArrayLength(mJSContext, obj, &length))) {
+ return nullptr;
+ }
+ return (this->*Prop::NewArray)(obj, length);
+ }
+
+ template<class Prop>
+ struct ArrayProperty
+ {
+ // See comments in PrimitiveProperty.
+ typedef typename Prop::NativeArray NativeType;
+ typedef typename Prop::ArrayFallback NativeFallback;
+
+ typedef decltype(&Self::ArrayInValue<Prop>) InValue_t;
+ static constexpr InValue_t InValue
+ = &Self::ArrayInValue<Prop>;
+
+ typedef decltype(&Self::ArrayFromValue<Prop>) FromValue_t;
+ static constexpr FromValue_t FromValue
+ = &Self::ArrayFromValue<Prop>;
+ };
+
+ // "Has" property is a special property type that is used to implement
+ // NativeJSObject.has, by returning true from InValue and FromValue for
+ // every existing property, and having false as the fallback value for
+ // when a property doesn't exist.
+
+ bool HasValue(JS::HandleValue val) const
+ {
+ return true;
+ }
+
+ struct HasProperty
+ {
+ // See comments in PrimitiveProperty.
+ typedef bool NativeType;
+ typedef bool NativeFallback;
+
+ typedef decltype(&Self::HasValue) HasValue_t;
+ static constexpr HasValue_t InValue = &Self::HasValue;
+ static constexpr HasValue_t FromValue = &Self::HasValue;
+ };
+
+ // Statically cast from bool to jboolean (unsigned char); it works
+ // since false and JNI_FALSE have the same value (0), and true and
+ // JNI_TRUE have the same value (1).
+ typedef PrimitiveProperty<
+ bool, jni::BooleanArray, jboolean, jbooleanArray,
+ &JS::Value::isBoolean, &JS::Value::toBoolean,
+ &JNIEnv::NewBooleanArray, &JNIEnv::SetBooleanArrayRegion>
+ BooleanProperty;
+
+ typedef PrimitiveProperty<
+ double, jni::DoubleArray, jdouble, jdoubleArray,
+ &JS::Value::isNumber, &JS::Value::toNumber,
+ &JNIEnv::NewDoubleArray, &JNIEnv::SetDoubleArrayRegion>
+ DoubleProperty;
+
+ typedef PrimitiveProperty<
+ int32_t, jni::IntArray, jint, jintArray,
+ &JS::Value::isInt32, &JS::Value::toInt32,
+ &JNIEnv::NewIntArray, &JNIEnv::SetIntArrayRegion>
+ IntProperty;
+
+ typedef BaseObjectProperty<
+ jni::String, &Self::StringInValue, &Self::StringFromValue>
+ StringProperty;
+
+ typedef BaseObjectProperty<
+ sdk::Bundle, &Self::ObjectInValue, &Self::BundleFromValue>
+ BundleProperty;
+
+ typedef BaseObjectProperty<
+ NativeJSObject, &Self::ObjectInValue, &Self::ObjectFromValue>
+ ObjectProperty;
+
+ typedef ArrayProperty<BooleanProperty> BooleanArrayProperty;
+ typedef ArrayProperty<DoubleProperty> DoubleArrayProperty;
+ typedef ArrayProperty<IntProperty> IntArrayProperty;
+ typedef ArrayProperty<StringProperty> StringArrayProperty;
+ typedef ArrayProperty<BundleProperty> BundleArrayProperty;
+ typedef ArrayProperty<ObjectProperty> ObjectArrayProperty;
+
+ template<class Prop>
+ typename Prop::NativeType
+ GetProperty(jni::String::Param name,
+ typename Prop::NativeFallback* fallback = nullptr)
+ {
+ if (!CheckThread() || !CheckObject()) {
+ return typename Prop::NativeType();
+ }
+
+ const JSJNIString nameStr(mEnv, name);
+ JS::RootedValue val(mJSContext);
+
+ if (!CheckJNIArgument(name) ||
+ !CheckJSCall(JS_GetUCProperty(
+ mJSContext, mJSObject, nameStr, nameStr.Length(), &val))) {
+ return typename Prop::NativeType();
+ }
+
+ // Strictly, null is different from undefined in JS. However, in
+ // practice, null is often used to indicate a property doesn't exist in
+ // the same manner as undefined. Therefore, we treat null in the same
+ // way as undefined when checking property existence (bug 1014965).
+ if (val.isUndefined() || val.isNull()) {
+ if (fallback) {
+ return mozilla::Move(*fallback);
+ }
+ jni::ThrowException(
+ "org/mozilla/gecko/util/NativeJSObject$InvalidPropertyException",
+ "Property does not exist");
+ return typename Prop::NativeType();
+ }
+
+ if (!CheckProperty(Prop::InValue, val)) {
+ return typename Prop::NativeType();
+ }
+ return (this->*Prop::FromValue)(val);
+ }
+
+ NativeJSObject::LocalRef CreateChild(JS::HandleObject object)
+ {
+ auto instance = NativeJSObject::New();
+ mozilla::UniquePtr<NativeJSContainerImpl> impl(
+ new NativeJSContainerImpl(instance.Env(), mJSContext, object));
+
+ ObjectBase::AttachNative(instance, mozilla::Move(impl));
+ if (!mChildren.append(NativeJSObject::GlobalRef(instance))) {
+ MOZ_CRASH();
+ }
+ return instance;
+ }
+
+ NativeJSContainerImpl(JNIEnv* env, JSContext* cx, JS::HandleObject object)
+ : mEnv(env)
+ , mJSContext(cx)
+ , mJSObject(cx, object)
+ {}
+
+public:
+ ~NativeJSContainerImpl()
+ {
+ // Dispose of all children on destruction. The children will in turn
+ // dispose any of their children (i.e. our grandchildren) and so on.
+ NativeJSObject::LocalRef child(mEnv);
+ for (size_t i = 0; i < mChildren.length(); i++) {
+ child = mChildren[i];
+ ObjectBase::GetNative(child)->ObjectBase::DisposeNative(child);
+ }
+ }
+
+ static NativeJSContainer::LocalRef
+ CreateInstance(JSContext* cx, JS::HandleObject object)
+ {
+ auto instance = NativeJSContainer::New();
+ mozilla::UniquePtr<NativeJSContainerImpl> impl(
+ new NativeJSContainerImpl(instance.Env(), cx, object));
+
+ ContainerBase::AttachNative(instance, mozilla::Move(impl));
+ return instance;
+ }
+
+ // NativeJSContainer methods
+
+ void DisposeNative(const NativeJSContainer::LocalRef& instance)
+ {
+ if (!CheckThread()) {
+ return;
+ }
+ ContainerBase::DisposeNative(instance);
+ }
+
+ NativeJSContainer::LocalRef Clone()
+ {
+ if (!CheckThread()) {
+ return nullptr;
+ }
+ return CreateInstance(mJSContext, mJSObject);
+ }
+
+ // NativeJSObject methods
+
+ bool GetBoolean(jni::String::Param name)
+ {
+ return GetProperty<BooleanProperty>(name);
+ }
+
+ bool OptBoolean(jni::String::Param name, bool fallback)
+ {
+ return GetProperty<BooleanProperty>(name, &fallback);
+ }
+
+ jni::BooleanArray::LocalRef
+ GetBooleanArray(jni::String::Param name)
+ {
+ return GetProperty<BooleanArrayProperty>(name);
+ }
+
+ jni::BooleanArray::LocalRef
+ OptBooleanArray(jni::String::Param name, jni::BooleanArray::Param fallback)
+ {
+ return GetProperty<BooleanArrayProperty>(name, &fallback);
+ }
+
+ jni::Object::LocalRef
+ GetBundle(jni::String::Param name)
+ {
+ return GetProperty<BundleProperty>(name);
+ }
+
+ jni::Object::LocalRef
+ OptBundle(jni::String::Param name, jni::Object::Param fallback)
+ {
+ // Because the GetProperty expects a sdk::Bundle::Param,
+ // we have to do conversions here from jni::Object::Param.
+ const auto& fb = sdk::Bundle::Ref::From(fallback.Get());
+ return GetProperty<BundleProperty>(name, &fb);
+ }
+
+ jni::ObjectArray::LocalRef
+ GetBundleArray(jni::String::Param name)
+ {
+ return GetProperty<BundleArrayProperty>(name);
+ }
+
+ jni::ObjectArray::LocalRef
+ OptBundleArray(jni::String::Param name, jni::ObjectArray::Param fallback)
+ {
+ return GetProperty<BundleArrayProperty>(name, &fallback);
+ }
+
+ double GetDouble(jni::String::Param name)
+ {
+ return GetProperty<DoubleProperty>(name);
+ }
+
+ double OptDouble(jni::String::Param name, double fallback)
+ {
+ return GetProperty<DoubleProperty>(name, &fallback);
+ }
+
+ jni::DoubleArray::LocalRef
+ GetDoubleArray(jni::String::Param name)
+ {
+ return GetProperty<DoubleArrayProperty>(name);
+ }
+
+ jni::DoubleArray::LocalRef
+ OptDoubleArray(jni::String::Param name, jni::DoubleArray::Param fallback)
+ {
+ jni::DoubleArray::LocalRef fb(fallback);
+ return GetProperty<DoubleArrayProperty>(name, &fb);
+ }
+
+ int GetInt(jni::String::Param name)
+ {
+ return GetProperty<IntProperty>(name);
+ }
+
+ int OptInt(jni::String::Param name, int fallback)
+ {
+ return GetProperty<IntProperty>(name, &fallback);
+ }
+
+ jni::IntArray::LocalRef
+ GetIntArray(jni::String::Param name)
+ {
+ return GetProperty<IntArrayProperty>(name);
+ }
+
+ jni::IntArray::LocalRef
+ OptIntArray(jni::String::Param name, jni::IntArray::Param fallback)
+ {
+ jni::IntArray::LocalRef fb(fallback);
+ return GetProperty<IntArrayProperty>(name, &fb);
+ }
+
+ NativeJSObject::LocalRef
+ GetObject(jni::String::Param name)
+ {
+ return GetProperty<ObjectProperty>(name);
+ }
+
+ NativeJSObject::LocalRef
+ OptObject(jni::String::Param name, NativeJSObject::Param fallback)
+ {
+ return GetProperty<ObjectProperty>(name, &fallback);
+ }
+
+ jni::ObjectArray::LocalRef
+ GetObjectArray(jni::String::Param name)
+ {
+ return GetProperty<ObjectArrayProperty>(name);
+ }
+
+ jni::ObjectArray::LocalRef
+ OptObjectArray(jni::String::Param name, jni::ObjectArray::Param fallback)
+ {
+ return GetProperty<ObjectArrayProperty>(name, &fallback);
+ }
+
+ jni::String::LocalRef
+ GetString(jni::String::Param name)
+ {
+ return GetProperty<StringProperty>(name);
+ }
+
+ jni::String::LocalRef
+ OptString(jni::String::Param name, jni::String::Param fallback)
+ {
+ return GetProperty<StringProperty>(name, &fallback);
+ }
+
+ jni::ObjectArray::LocalRef
+ GetStringArray(jni::String::Param name)
+ {
+ return GetProperty<StringArrayProperty>(name);
+ }
+
+ jni::ObjectArray::LocalRef
+ OptStringArray(jni::String::Param name, jni::ObjectArray::Param fallback)
+ {
+ return GetProperty<StringArrayProperty>(name, &fallback);
+ }
+
+ bool Has(jni::String::Param name)
+ {
+ bool no = false;
+ // Fallback to false indicating no such property.
+ return GetProperty<HasProperty>(name, &no);
+ }
+
+ jni::Object::LocalRef ToBundle()
+ {
+ if (!CheckThread() || !CheckObject()) {
+ return nullptr;
+ }
+ return BundleFromValue(mJSObject);
+ }
+
+private:
+ static bool AppendJSON(const char16_t* buf, uint32_t len, void* data)
+ {
+ static_cast<nsAutoString*>(data)->Append(buf, len);
+ return true;
+ }
+
+public:
+ jni::String::LocalRef ToString()
+ {
+ if (!CheckThread() || !CheckObject()) {
+ return nullptr;
+ }
+
+ JS::RootedValue value(mJSContext, JS::ObjectValue(*mJSObject));
+ nsAutoString json;
+ if (!CheckJSCall(JS_Stringify(mJSContext, &value, nullptr,
+ JS::NullHandleValue, AppendJSON, &json))) {
+ return nullptr;
+ }
+ return jni::StringParam(json, mEnv);
+ }
+};
+
+
+// Define the "static constexpr" members of our property types (e.g.
+// PrimitiveProperty<>::InValue). This is tricky because there are a lot of
+// template parameters, so we use macros to make it simpler.
+
+#define DEFINE_PRIMITIVE_PROPERTY_MEMBER(Name) \
+ template<typename U, typename UA, typename V, typename VA, \
+ bool (JS::Value::*I)() const, \
+ U (JS::Value::*T)() const, \
+ VA (JNIEnv::*N)(jsize), \
+ void (JNIEnv::*S)(VA, jsize, jsize, const V*)> \
+ constexpr typename NativeJSContainerImpl \
+ ::PrimitiveProperty<U, UA, V, VA, I, T, N, S>::Name##_t \
+ NativeJSContainerImpl::PrimitiveProperty<U, UA, V, VA, I, T, N, S>::Name
+
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(NewJNIArray);
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(SetJNIArrayRegion);
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(InValue);
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(FromValue);
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(NewArray);
+
+#undef DEFINE_PRIMITIVE_PROPERTY_MEMBER
+
+#define DEFINE_OBJECT_PROPERTY_MEMBER(Name) \
+ template<class C, \
+ bool (NativeJSContainerImpl::*I)(JS::HandleValue) const, \
+ typename C::LocalRef (NativeJSContainerImpl::*F)(JS::HandleValue)> \
+ constexpr typename NativeJSContainerImpl \
+ ::BaseObjectProperty<C, I, F>::Name##_t \
+ NativeJSContainerImpl::BaseObjectProperty<C, I, F>::Name
+
+DEFINE_OBJECT_PROPERTY_MEMBER(InValue);
+DEFINE_OBJECT_PROPERTY_MEMBER(FromValue);
+DEFINE_OBJECT_PROPERTY_MEMBER(NewArray);
+
+#undef DEFINE_OBJECT_PROPERTY_MEMBER
+
+template<class P> constexpr typename NativeJSContainerImpl::ArrayProperty<P>
+ ::InValue_t NativeJSContainerImpl::ArrayProperty<P>::InValue;
+template<class P> constexpr typename NativeJSContainerImpl::ArrayProperty<P>
+ ::FromValue_t NativeJSContainerImpl::ArrayProperty<P>::FromValue;
+
+constexpr NativeJSContainerImpl::HasProperty::HasValue_t
+ NativeJSContainerImpl::HasProperty::InValue;
+constexpr NativeJSContainerImpl::HasProperty::HasValue_t
+ NativeJSContainerImpl::HasProperty::FromValue;
+
+
+NativeJSContainer::LocalRef
+CreateNativeJSContainer(JSContext* cx, JS::HandleObject object)
+{
+ return NativeJSContainerImpl::CreateInstance(cx, object);
+}
+
+} // namespace widget
+} // namespace mozilla
+
diff --git a/widget/android/NativeJSContainer.h b/widget/android/NativeJSContainer.h
new file mode 100644
index 000000000..bebf75f1b
--- /dev/null
+++ b/widget/android/NativeJSContainer.h
@@ -0,0 +1,22 @@
+/* -*- Mode: c++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NativeJSObject_h__
+#define NativeJSObject_h__
+
+#include "GeneratedJNIWrappers.h"
+#include "jsapi.h"
+
+namespace mozilla {
+namespace widget {
+
+java::NativeJSContainer::LocalRef
+CreateNativeJSContainer(JSContext* cx, JS::HandleObject object);
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // NativeJSObject_h__
+
diff --git a/widget/android/PrefsHelper.h b/widget/android/PrefsHelper.h
new file mode 100644
index 000000000..b053c979a
--- /dev/null
+++ b/widget/android/PrefsHelper.h
@@ -0,0 +1,324 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "GeneratedJNINatives.h"
+#include "MainThreadUtils.h"
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsVariant.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 = nsIDataType::VTYPE_EMPTY;
+ if (NS_FAILED(aVariant->GetDataType(&varType))) {
+ return false;
+ }
+
+ 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)) {
+ rv = aVariant->GetDataType(&varType);
+ }
+
+ // 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;
+ nsAdoptingString strVal;
+
+ for (jni::Object::LocalRef& nameRef : nameRefArray) {
+ jni::String::LocalRef nameStr(mozilla::Move(nameRef));
+ const nsCString& name = nameStr->ToCString();
+
+ int32_t type = java::PrefsHelper::PREF_INVALID;
+ bool boolVal = false;
+ int32_t intVal = 0;
+
+ 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;
+ strVal = Preferences::GetLocalizedString(name.get());
+ if (!strVal) {
+ strVal = Preferences::GetString(name.get());
+ }
+ 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(mozilla::Move(nameRef));
+ MOZ_ALWAYS_SUCCEEDS(Preferences::AddStrongObserver(
+ appShell, nameStr->ToCString().get()));
+ }
+ }
+
+ 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(mozilla::Move(nameRef));
+ MOZ_ALWAYS_SUCCEEDS(Preferences::RemoveObserver(
+ appShell, nameStr->ToCString().get()));
+ }
+ }
+
+ static void OnPrefChange(const char16_t* aData)
+ {
+ const nsCString& name = NS_LossyConvertUTF16toASCII(aData);
+
+ int32_t type = -1;
+ bool boolVal = false;
+ int32_t intVal = false;
+ nsAdoptingString 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;
+ strVal = Preferences::GetLocalizedString(name.get());
+ if (!strVal) {
+ strVal = Preferences::GetString(name.get());
+ }
+ 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
+
+#endif // PrefsHelper_h
diff --git a/widget/android/bindings/AndroidRect-classes.txt b/widget/android/bindings/AndroidRect-classes.txt
new file mode 100644
index 000000000..cbacca81e
--- /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/Bundle-classes.txt b/widget/android/bindings/Bundle-classes.txt
new file mode 100644
index 000000000..9f535cddb
--- /dev/null
+++ b/widget/android/bindings/Bundle-classes.txt
@@ -0,0 +1 @@
+android.os.Bundle
diff --git a/widget/android/bindings/KeyEvent-classes.txt b/widget/android/bindings/KeyEvent-classes.txt
new file mode 100644
index 000000000..b73e621ef
--- /dev/null
+++ b/widget/android/bindings/KeyEvent-classes.txt
@@ -0,0 +1 @@
+android.view.KeyEvent
diff --git a/widget/android/bindings/Makefile.in b/widget/android/bindings/Makefile.in
new file mode 100644
index 000000000..ea66e6022
--- /dev/null
+++ b/widget/android/bindings/Makefile.in
@@ -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/.
+
+# Bug 1099345 - The SDK's lint code (used by the code generator) does not enjoy
+# concurrent access to a cache that it generates.
+.NOTPARALLEL:
+
+annotation_processor_jar_files := \
+ $(DEPTH)/build/annotationProcessors/annotationProcessors.jar \
+ $(ANDROID_TOOLS)/lib/lint.jar \
+ $(ANDROID_TOOLS)/lib/lint-checks.jar \
+ $(NULL)
+
+sdk_processor := \
+ $(JAVA) \
+ -Dcom.android.tools.lint.bindir='$(ANDROID_TOOLS)' \
+ -classpath $(subst $(NULL) ,:,$(strip $(annotation_processor_jar_files))) \
+ org.mozilla.gecko.annotationProcessors.SDKProcessor
+
+# For the benefit of readers: the following pattern rule says that,
+# for example, MediaCodec.cpp and MediaCodec.h can be produced from
+# MediaCodec-classes.txt. This formulation invokes the SDK processor
+# at most once.
+
+%.cpp %.h: $(ANDROID_SDK)/android.jar %-classes.txt $(annotation_processor_jar_files)
+ $(sdk_processor) $(ANDROID_SDK)/android.jar $(srcdir)/$*-classes.txt $(CURDIR) $* 16
diff --git a/widget/android/bindings/MediaCodec-classes.txt b/widget/android/bindings/MediaCodec-classes.txt
new file mode 100644
index 000000000..ea26029af
--- /dev/null
+++ b/widget/android/bindings/MediaCodec-classes.txt
@@ -0,0 +1,5 @@
+android.media.MediaCodec
+android.media.MediaCodec$BufferInfo
+android.media.MediaCodec$CryptoInfo
+android.media.MediaDrm$KeyStatus
+android.media.MediaFormat
diff --git a/widget/android/bindings/MotionEvent-classes.txt b/widget/android/bindings/MotionEvent-classes.txt
new file mode 100644
index 000000000..dd38dc000
--- /dev/null
+++ b/widget/android/bindings/MotionEvent-classes.txt
@@ -0,0 +1 @@
+android.view.MotionEvent
diff --git a/widget/android/bindings/SurfaceTexture-classes.txt b/widget/android/bindings/SurfaceTexture-classes.txt
new file mode 100644
index 000000000..5c7ca8968
--- /dev/null
+++ b/widget/android/bindings/SurfaceTexture-classes.txt
@@ -0,0 +1,2 @@
+android.graphics.SurfaceTexture
+android.view.Surface
diff --git a/widget/android/bindings/ViewConfiguration-classes.txt b/widget/android/bindings/ViewConfiguration-classes.txt
new file mode 100644
index 000000000..e6601e65f
--- /dev/null
+++ b/widget/android/bindings/ViewConfiguration-classes.txt
@@ -0,0 +1 @@
+android.view.ViewConfiguration
diff --git a/widget/android/bindings/moz.build b/widget/android/bindings/moz.build
new file mode 100644
index 000000000..1bd71fa95
--- /dev/null
+++ b/widget/android/bindings/moz.build
@@ -0,0 +1,41 @@
+# -*- 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/.
+
+# 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 = [
+ 'AndroidRect',
+ 'Bundle',
+ 'KeyEvent',
+ 'MediaCodec',
+ 'MotionEvent',
+ 'SurfaceTexture',
+ 'ViewConfiguration'
+]
+
+SOURCES += ['!%s.cpp' % stem for stem in generated]
+
+EXPORTS += ['!%s.h' % stem for stem in generated]
+
+# We'd like to add these to a future GENERATED_EXPORTS list, but for now we mark
+# them as generated here and manually install them in Makefile.in.
+GENERATED_FILES += [stem + '.h' for stem in generated]
+
+# There is an unfortunate race condition when using generated SOURCES and
+# pattern rules (see Makefile.in) that manifests itself as a VPATH resolution
+# conflict: MediaCodec.o looks for MediaCodec.cpp and $(CURDIR)/MediaCodec.cpp,
+# and the pattern rule is matched but doesn't resolve both sources, causing a
+# failure. Adding the SOURCES to GENERATED_FILES causes the sources
+# to be built at export time, which is before MediaCodec.o needs them; and by
+# the time MediaCodec.o is built, the source is in place and the VPATH
+# resolution works as expected.
+GENERATED_FILES += [f[1:] for f in SOURCES]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/widget/android',
+]
diff --git a/widget/android/fennec/FennecJNINatives.h b/widget/android/fennec/FennecJNINatives.h
new file mode 100644
index 000000000..9d0cdd77c
--- /dev/null
+++ b/widget/android/fennec/FennecJNINatives.h
@@ -0,0 +1,244 @@
+// GENERATED CODE
+// Generated by the Java program at /build/annotationProcessors at compile time
+// from annotations on Java methods. To update, change the annotations on the
+// corresponding Java methods and rerun the build. Manually updating this file
+// will cause your build to fail.
+
+#ifndef FennecJNINatives_h
+#define FennecJNINatives_h
+
+#include "FennecJNIWrappers.h"
+#include "mozilla/jni/Natives.h"
+
+namespace mozilla {
+namespace java {
+
+template<class Impl>
+class ANRReporter::Natives : public mozilla::jni::NativeImpl<ANRReporter, Impl>
+{
+public:
+ static const JNINativeMethod methods[3];
+};
+
+template<class Impl>
+const JNINativeMethod ANRReporter::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<ANRReporter::GetNativeStack_t>(
+ mozilla::jni::NativeStub<ANRReporter::GetNativeStack_t, Impl>
+ ::template Wrap<&Impl::GetNativeStack>),
+
+ mozilla::jni::MakeNativeMethod<ANRReporter::ReleaseNativeStack_t>(
+ mozilla::jni::NativeStub<ANRReporter::ReleaseNativeStack_t, Impl>
+ ::template Wrap<&Impl::ReleaseNativeStack>),
+
+ mozilla::jni::MakeNativeMethod<ANRReporter::RequestNativeStack_t>(
+ mozilla::jni::NativeStub<ANRReporter::RequestNativeStack_t, Impl>
+ ::template Wrap<&Impl::RequestNativeStack>)
+};
+
+template<class Impl>
+class GeckoJavaSampler::Natives : public mozilla::jni::NativeImpl<GeckoJavaSampler, Impl>
+{
+public:
+ static const JNINativeMethod methods[1];
+};
+
+template<class Impl>
+const JNINativeMethod GeckoJavaSampler::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<GeckoJavaSampler::GetProfilerTime_t>(
+ mozilla::jni::NativeStub<GeckoJavaSampler::GetProfilerTime_t, Impl>
+ ::template Wrap<&Impl::GetProfilerTime>)
+};
+
+template<class Impl>
+class MemoryMonitor::Natives : public mozilla::jni::NativeImpl<MemoryMonitor, Impl>
+{
+public:
+ static const JNINativeMethod methods[1];
+};
+
+template<class Impl>
+const JNINativeMethod MemoryMonitor::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<MemoryMonitor::DispatchMemoryPressure_t>(
+ mozilla::jni::NativeStub<MemoryMonitor::DispatchMemoryPressure_t, Impl>
+ ::template Wrap<&Impl::DispatchMemoryPressure>)
+};
+
+template<class Impl>
+class PresentationMediaPlayerManager::Natives : public mozilla::jni::NativeImpl<PresentationMediaPlayerManager, Impl>
+{
+public:
+ static const JNINativeMethod methods[3];
+};
+
+template<class Impl>
+const JNINativeMethod PresentationMediaPlayerManager::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<PresentationMediaPlayerManager::AddPresentationSurface_t>(
+ mozilla::jni::NativeStub<PresentationMediaPlayerManager::AddPresentationSurface_t, Impl>
+ ::template Wrap<&Impl::AddPresentationSurface>),
+
+ mozilla::jni::MakeNativeMethod<PresentationMediaPlayerManager::InvalidateAndScheduleComposite_t>(
+ mozilla::jni::NativeStub<PresentationMediaPlayerManager::InvalidateAndScheduleComposite_t, Impl>
+ ::template Wrap<&Impl::InvalidateAndScheduleComposite>),
+
+ mozilla::jni::MakeNativeMethod<PresentationMediaPlayerManager::RemovePresentationSurface_t>(
+ mozilla::jni::NativeStub<PresentationMediaPlayerManager::RemovePresentationSurface_t, Impl>
+ ::template Wrap<&Impl::RemovePresentationSurface>)
+};
+
+template<class Impl>
+class ScreenManagerHelper::Natives : public mozilla::jni::NativeImpl<ScreenManagerHelper, Impl>
+{
+public:
+ static const JNINativeMethod methods[2];
+};
+
+template<class Impl>
+const JNINativeMethod ScreenManagerHelper::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<ScreenManagerHelper::AddDisplay_t>(
+ mozilla::jni::NativeStub<ScreenManagerHelper::AddDisplay_t, Impl>
+ ::template Wrap<&Impl::AddDisplay>),
+
+ mozilla::jni::MakeNativeMethod<ScreenManagerHelper::RemoveDisplay_t>(
+ mozilla::jni::NativeStub<ScreenManagerHelper::RemoveDisplay_t, Impl>
+ ::template Wrap<&Impl::RemoveDisplay>)
+};
+
+template<class Impl>
+class Telemetry::Natives : public mozilla::jni::NativeImpl<Telemetry, Impl>
+{
+public:
+ static const JNINativeMethod methods[5];
+};
+
+template<class Impl>
+const JNINativeMethod Telemetry::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<Telemetry::AddHistogram_t>(
+ mozilla::jni::NativeStub<Telemetry::AddHistogram_t, Impl>
+ ::template Wrap<&Impl::AddHistogram>),
+
+ mozilla::jni::MakeNativeMethod<Telemetry::AddKeyedHistogram_t>(
+ mozilla::jni::NativeStub<Telemetry::AddKeyedHistogram_t, Impl>
+ ::template Wrap<&Impl::AddKeyedHistogram>),
+
+ mozilla::jni::MakeNativeMethod<Telemetry::AddUIEvent_t>(
+ mozilla::jni::NativeStub<Telemetry::AddUIEvent_t, Impl>
+ ::template Wrap<&Impl::AddUIEvent>),
+
+ mozilla::jni::MakeNativeMethod<Telemetry::StartUISession_t>(
+ mozilla::jni::NativeStub<Telemetry::StartUISession_t, Impl>
+ ::template Wrap<&Impl::StartUISession>),
+
+ mozilla::jni::MakeNativeMethod<Telemetry::StopUISession_t>(
+ mozilla::jni::NativeStub<Telemetry::StopUISession_t, Impl>
+ ::template Wrap<&Impl::StopUISession>)
+};
+
+template<class Impl>
+class ThumbnailHelper::Natives : public mozilla::jni::NativeImpl<ThumbnailHelper, Impl>
+{
+public:
+ static const JNINativeMethod methods[1];
+};
+
+template<class Impl>
+const JNINativeMethod ThumbnailHelper::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<ThumbnailHelper::RequestThumbnail_t>(
+ mozilla::jni::NativeStub<ThumbnailHelper::RequestThumbnail_t, Impl>
+ ::template Wrap<&Impl::RequestThumbnail>)
+};
+
+template<class Impl>
+class ZoomedView::Natives : public mozilla::jni::NativeImpl<ZoomedView, Impl>
+{
+public:
+ static const JNINativeMethod methods[1];
+};
+
+template<class Impl>
+const JNINativeMethod ZoomedView::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<ZoomedView::RequestZoomedViewData_t>(
+ mozilla::jni::NativeStub<ZoomedView::RequestZoomedViewData_t, Impl>
+ ::template Wrap<&Impl::RequestZoomedViewData>)
+};
+
+template<class Impl>
+class CodecProxy::NativeCallbacks::Natives : public mozilla::jni::NativeImpl<NativeCallbacks, Impl>
+{
+public:
+ static const JNINativeMethod methods[5];
+};
+
+template<class Impl>
+const JNINativeMethod CodecProxy::NativeCallbacks::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::DisposeNative_t>(
+ mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::DisposeNative_t, Impl>
+ ::template Wrap<&Impl::DisposeNative>),
+
+ mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::OnError_t>(
+ mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::OnError_t, Impl>
+ ::template Wrap<&Impl::OnError>),
+
+ mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::OnInputExhausted_t>(
+ mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::OnInputExhausted_t, Impl>
+ ::template Wrap<&Impl::OnInputExhausted>),
+
+ mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::OnOutput_t>(
+ mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::OnOutput_t, Impl>
+ ::template Wrap<&Impl::OnOutput>),
+
+ mozilla::jni::MakeNativeMethod<CodecProxy::NativeCallbacks::OnOutputFormatChanged_t>(
+ mozilla::jni::NativeStub<CodecProxy::NativeCallbacks::OnOutputFormatChanged_t, Impl>
+ ::template Wrap<&Impl::OnOutputFormatChanged>)
+};
+
+template<class Impl>
+class MediaDrmProxy::NativeMediaDrmProxyCallbacks::Natives : public mozilla::jni::NativeImpl<NativeMediaDrmProxyCallbacks, Impl>
+{
+public:
+ static const JNINativeMethod methods[7];
+};
+
+template<class Impl>
+const JNINativeMethod MediaDrmProxy::NativeMediaDrmProxyCallbacks::Natives<Impl>::methods[] = {
+
+ mozilla::jni::MakeNativeMethod<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnRejectPromise_t>(
+ mozilla::jni::NativeStub<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnRejectPromise_t, Impl>
+ ::template Wrap<&Impl::OnRejectPromise>),
+
+ mozilla::jni::MakeNativeMethod<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionBatchedKeyChanged_t>(
+ mozilla::jni::NativeStub<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionBatchedKeyChanged_t, Impl>
+ ::template Wrap<&Impl::OnSessionBatchedKeyChanged>),
+
+ mozilla::jni::MakeNativeMethod<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionClosed_t>(
+ mozilla::jni::NativeStub<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionClosed_t, Impl>
+ ::template Wrap<&Impl::OnSessionClosed>),
+
+ mozilla::jni::MakeNativeMethod<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionCreated_t>(
+ mozilla::jni::NativeStub<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionCreated_t, Impl>
+ ::template Wrap<&Impl::OnSessionCreated>),
+
+ mozilla::jni::MakeNativeMethod<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionError_t>(
+ mozilla::jni::NativeStub<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionError_t, Impl>
+ ::template Wrap<&Impl::OnSessionError>),
+
+ mozilla::jni::MakeNativeMethod<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionMessage_t>(
+ mozilla::jni::NativeStub<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionMessage_t, Impl>
+ ::template Wrap<&Impl::OnSessionMessage>),
+
+ mozilla::jni::MakeNativeMethod<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionUpdated_t>(
+ mozilla::jni::NativeStub<MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionUpdated_t, Impl>
+ ::template Wrap<&Impl::OnSessionUpdated>)
+};
+
+} /* java */
+} /* mozilla */
+#endif // FennecJNINatives_h
diff --git a/widget/android/fennec/FennecJNIWrappers.cpp b/widget/android/fennec/FennecJNIWrappers.cpp
new file mode 100644
index 000000000..f8be6833b
--- /dev/null
+++ b/widget/android/fennec/FennecJNIWrappers.cpp
@@ -0,0 +1,443 @@
+// GENERATED CODE
+// Generated by the Java program at /build/annotationProcessors at compile time
+// from annotations on Java methods. To update, change the annotations on the
+// corresponding Java methods and rerun the build. Manually updating this file
+// will cause your build to fail.
+
+#include "FennecJNIWrappers.h"
+#include "mozilla/jni/Accessors.h"
+
+namespace mozilla {
+namespace java {
+
+const char ANRReporter::name[] =
+ "org/mozilla/gecko/ANRReporter";
+
+constexpr char ANRReporter::GetNativeStack_t::name[];
+constexpr char ANRReporter::GetNativeStack_t::signature[];
+
+constexpr char ANRReporter::ReleaseNativeStack_t::name[];
+constexpr char ANRReporter::ReleaseNativeStack_t::signature[];
+
+constexpr char ANRReporter::RequestNativeStack_t::name[];
+constexpr char ANRReporter::RequestNativeStack_t::signature[];
+
+const char DownloadsIntegration::name[] =
+ "org/mozilla/gecko/DownloadsIntegration";
+
+constexpr char DownloadsIntegration::GetTemporaryDownloadDirectory_t::name[];
+constexpr char DownloadsIntegration::GetTemporaryDownloadDirectory_t::signature[];
+
+auto DownloadsIntegration::GetTemporaryDownloadDirectory() -> mozilla::jni::String::LocalRef
+{
+ return mozilla::jni::Method<GetTemporaryDownloadDirectory_t>::Call(DownloadsIntegration::Context(), nullptr);
+}
+
+constexpr char DownloadsIntegration::ScanMedia_t::name[];
+constexpr char DownloadsIntegration::ScanMedia_t::signature[];
+
+auto DownloadsIntegration::ScanMedia(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> void
+{
+ return mozilla::jni::Method<ScanMedia_t>::Call(DownloadsIntegration::Context(), nullptr, a0, a1);
+}
+
+const char GeckoJavaSampler::name[] =
+ "org/mozilla/gecko/GeckoJavaSampler";
+
+constexpr char GeckoJavaSampler::GetFrameName_t::name[];
+constexpr char GeckoJavaSampler::GetFrameName_t::signature[];
+
+auto GeckoJavaSampler::GetFrameName(int32_t a0, int32_t a1, int32_t a2) -> mozilla::jni::String::LocalRef
+{
+ return mozilla::jni::Method<GetFrameName_t>::Call(GeckoJavaSampler::Context(), nullptr, a0, a1, a2);
+}
+
+constexpr char GeckoJavaSampler::GetProfilerTime_t::name[];
+constexpr char GeckoJavaSampler::GetProfilerTime_t::signature[];
+
+constexpr char GeckoJavaSampler::GetSampleTime_t::name[];
+constexpr char GeckoJavaSampler::GetSampleTime_t::signature[];
+
+auto GeckoJavaSampler::GetSampleTime(int32_t a0, int32_t a1) -> double
+{
+ return mozilla::jni::Method<GetSampleTime_t>::Call(GeckoJavaSampler::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoJavaSampler::GetThreadName_t::name[];
+constexpr char GeckoJavaSampler::GetThreadName_t::signature[];
+
+auto GeckoJavaSampler::GetThreadName(int32_t a0) -> mozilla::jni::String::LocalRef
+{
+ return mozilla::jni::Method<GetThreadName_t>::Call(GeckoJavaSampler::Context(), nullptr, a0);
+}
+
+constexpr char GeckoJavaSampler::Pause_t::name[];
+constexpr char GeckoJavaSampler::Pause_t::signature[];
+
+auto GeckoJavaSampler::Pause() -> void
+{
+ return mozilla::jni::Method<Pause_t>::Call(GeckoJavaSampler::Context(), nullptr);
+}
+
+constexpr char GeckoJavaSampler::Start_t::name[];
+constexpr char GeckoJavaSampler::Start_t::signature[];
+
+auto GeckoJavaSampler::Start(int32_t a0, int32_t a1) -> void
+{
+ return mozilla::jni::Method<Start_t>::Call(GeckoJavaSampler::Context(), nullptr, a0, a1);
+}
+
+constexpr char GeckoJavaSampler::Stop_t::name[];
+constexpr char GeckoJavaSampler::Stop_t::signature[];
+
+auto GeckoJavaSampler::Stop() -> void
+{
+ return mozilla::jni::Method<Stop_t>::Call(GeckoJavaSampler::Context(), nullptr);
+}
+
+constexpr char GeckoJavaSampler::Unpause_t::name[];
+constexpr char GeckoJavaSampler::Unpause_t::signature[];
+
+auto GeckoJavaSampler::Unpause() -> void
+{
+ return mozilla::jni::Method<Unpause_t>::Call(GeckoJavaSampler::Context(), nullptr);
+}
+
+const char MemoryMonitor::name[] =
+ "org/mozilla/gecko/MemoryMonitor";
+
+constexpr char MemoryMonitor::DispatchMemoryPressure_t::name[];
+constexpr char MemoryMonitor::DispatchMemoryPressure_t::signature[];
+
+const char PresentationMediaPlayerManager::name[] =
+ "org/mozilla/gecko/PresentationMediaPlayerManager";
+
+constexpr char PresentationMediaPlayerManager::AddPresentationSurface_t::name[];
+constexpr char PresentationMediaPlayerManager::AddPresentationSurface_t::signature[];
+
+constexpr char PresentationMediaPlayerManager::InvalidateAndScheduleComposite_t::name[];
+constexpr char PresentationMediaPlayerManager::InvalidateAndScheduleComposite_t::signature[];
+
+constexpr char PresentationMediaPlayerManager::RemovePresentationSurface_t::name[];
+constexpr char PresentationMediaPlayerManager::RemovePresentationSurface_t::signature[];
+
+const char ScreenManagerHelper::name[] =
+ "org/mozilla/gecko/ScreenManagerHelper";
+
+constexpr char ScreenManagerHelper::AddDisplay_t::name[];
+constexpr char ScreenManagerHelper::AddDisplay_t::signature[];
+
+constexpr char ScreenManagerHelper::RemoveDisplay_t::name[];
+constexpr char ScreenManagerHelper::RemoveDisplay_t::signature[];
+
+const char Telemetry::name[] =
+ "org/mozilla/gecko/Telemetry";
+
+constexpr char Telemetry::AddHistogram_t::name[];
+constexpr char Telemetry::AddHistogram_t::signature[];
+
+constexpr char Telemetry::AddKeyedHistogram_t::name[];
+constexpr char Telemetry::AddKeyedHistogram_t::signature[];
+
+constexpr char Telemetry::AddUIEvent_t::name[];
+constexpr char Telemetry::AddUIEvent_t::signature[];
+
+constexpr char Telemetry::StartUISession_t::name[];
+constexpr char Telemetry::StartUISession_t::signature[];
+
+constexpr char Telemetry::StopUISession_t::name[];
+constexpr char Telemetry::StopUISession_t::signature[];
+
+const char ThumbnailHelper::name[] =
+ "org/mozilla/gecko/ThumbnailHelper";
+
+constexpr char ThumbnailHelper::NotifyThumbnail_t::name[];
+constexpr char ThumbnailHelper::NotifyThumbnail_t::signature[];
+
+auto ThumbnailHelper::NotifyThumbnail(mozilla::jni::ByteBuffer::Param a0, mozilla::jni::Object::Param a1, bool a2, bool a3) -> void
+{
+ return mozilla::jni::Method<NotifyThumbnail_t>::Call(ThumbnailHelper::Context(), nullptr, a0, a1, a2, a3);
+}
+
+constexpr char ThumbnailHelper::RequestThumbnail_t::name[];
+constexpr char ThumbnailHelper::RequestThumbnail_t::signature[];
+
+const char ZoomedView::name[] =
+ "org/mozilla/gecko/ZoomedView";
+
+constexpr char ZoomedView::RequestZoomedViewData_t::name[];
+constexpr char ZoomedView::RequestZoomedViewData_t::signature[];
+
+const char AudioFocusAgent::name[] =
+ "org/mozilla/gecko/media/AudioFocusAgent";
+
+constexpr char AudioFocusAgent::NotifyStartedPlaying_t::name[];
+constexpr char AudioFocusAgent::NotifyStartedPlaying_t::signature[];
+
+auto AudioFocusAgent::NotifyStartedPlaying() -> void
+{
+ return mozilla::jni::Method<NotifyStartedPlaying_t>::Call(AudioFocusAgent::Context(), nullptr);
+}
+
+constexpr char AudioFocusAgent::NotifyStoppedPlaying_t::name[];
+constexpr char AudioFocusAgent::NotifyStoppedPlaying_t::signature[];
+
+auto AudioFocusAgent::NotifyStoppedPlaying() -> void
+{
+ return mozilla::jni::Method<NotifyStoppedPlaying_t>::Call(AudioFocusAgent::Context(), nullptr);
+}
+
+const char CodecProxy::name[] =
+ "org/mozilla/gecko/media/CodecProxy";
+
+constexpr char CodecProxy::Create_t::name[];
+constexpr char CodecProxy::Create_t::signature[];
+
+auto CodecProxy::Create(mozilla::jni::Object::Param a0, mozilla::jni::Object::Param a1, mozilla::jni::Object::Param a2) -> CodecProxy::LocalRef
+{
+ return mozilla::jni::Method<Create_t>::Call(CodecProxy::Context(), nullptr, a0, a1, a2);
+}
+
+constexpr char CodecProxy::Flush_t::name[];
+constexpr char CodecProxy::Flush_t::signature[];
+
+auto CodecProxy::Flush() const -> bool
+{
+ return mozilla::jni::Method<Flush_t>::Call(CodecProxy::mCtx, nullptr);
+}
+
+constexpr char CodecProxy::Input_t::name[];
+constexpr char CodecProxy::Input_t::signature[];
+
+auto CodecProxy::Input(mozilla::jni::ByteBuffer::Param a0, mozilla::jni::Object::Param a1, mozilla::jni::Object::Param a2) const -> bool
+{
+ return mozilla::jni::Method<Input_t>::Call(CodecProxy::mCtx, nullptr, a0, a1, a2);
+}
+
+constexpr char CodecProxy::Release_t::name[];
+constexpr char CodecProxy::Release_t::signature[];
+
+auto CodecProxy::Release() const -> bool
+{
+ return mozilla::jni::Method<Release_t>::Call(CodecProxy::mCtx, nullptr);
+}
+
+const char CodecProxy::NativeCallbacks::name[] =
+ "org/mozilla/gecko/media/CodecProxy$NativeCallbacks";
+
+constexpr char CodecProxy::NativeCallbacks::New_t::name[];
+constexpr char CodecProxy::NativeCallbacks::New_t::signature[];
+
+auto CodecProxy::NativeCallbacks::New() -> NativeCallbacks::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(NativeCallbacks::Context(), nullptr);
+}
+
+constexpr char CodecProxy::NativeCallbacks::DisposeNative_t::name[];
+constexpr char CodecProxy::NativeCallbacks::DisposeNative_t::signature[];
+
+constexpr char CodecProxy::NativeCallbacks::OnError_t::name[];
+constexpr char CodecProxy::NativeCallbacks::OnError_t::signature[];
+
+constexpr char CodecProxy::NativeCallbacks::OnInputExhausted_t::name[];
+constexpr char CodecProxy::NativeCallbacks::OnInputExhausted_t::signature[];
+
+constexpr char CodecProxy::NativeCallbacks::OnOutput_t::name[];
+constexpr char CodecProxy::NativeCallbacks::OnOutput_t::signature[];
+
+constexpr char CodecProxy::NativeCallbacks::OnOutputFormatChanged_t::name[];
+constexpr char CodecProxy::NativeCallbacks::OnOutputFormatChanged_t::signature[];
+
+const char MediaDrmProxy::name[] =
+ "org/mozilla/gecko/media/MediaDrmProxy";
+
+constexpr char MediaDrmProxy::CanDecode_t::name[];
+constexpr char MediaDrmProxy::CanDecode_t::signature[];
+
+auto MediaDrmProxy::CanDecode(mozilla::jni::String::Param a0) -> bool
+{
+ return mozilla::jni::Method<CanDecode_t>::Call(MediaDrmProxy::Context(), nullptr, a0);
+}
+
+constexpr char MediaDrmProxy::IsCryptoSchemeSupported_t::name[];
+constexpr char MediaDrmProxy::IsCryptoSchemeSupported_t::signature[];
+
+auto MediaDrmProxy::IsCryptoSchemeSupported(mozilla::jni::String::Param a0, mozilla::jni::String::Param a1) -> bool
+{
+ return mozilla::jni::Method<IsCryptoSchemeSupported_t>::Call(MediaDrmProxy::Context(), nullptr, a0, a1);
+}
+
+constexpr char MediaDrmProxy::CloseSession_t::name[];
+constexpr char MediaDrmProxy::CloseSession_t::signature[];
+
+auto MediaDrmProxy::CloseSession(int32_t a0, mozilla::jni::String::Param a1) const -> void
+{
+ return mozilla::jni::Method<CloseSession_t>::Call(MediaDrmProxy::mCtx, nullptr, a0, a1);
+}
+
+constexpr char MediaDrmProxy::Create_t::name[];
+constexpr char MediaDrmProxy::Create_t::signature[];
+
+auto MediaDrmProxy::Create(mozilla::jni::String::Param a0, mozilla::jni::Object::Param a1, bool a2) -> MediaDrmProxy::LocalRef
+{
+ return mozilla::jni::Method<Create_t>::Call(MediaDrmProxy::Context(), nullptr, a0, a1, a2);
+}
+
+constexpr char MediaDrmProxy::CreateSession_t::name[];
+constexpr char MediaDrmProxy::CreateSession_t::signature[];
+
+auto MediaDrmProxy::CreateSession(int32_t a0, int32_t a1, mozilla::jni::String::Param a2, mozilla::jni::ByteArray::Param a3) const -> void
+{
+ return mozilla::jni::Method<CreateSession_t>::Call(MediaDrmProxy::mCtx, nullptr, a0, a1, a2, a3);
+}
+
+constexpr char MediaDrmProxy::Destroy_t::name[];
+constexpr char MediaDrmProxy::Destroy_t::signature[];
+
+auto MediaDrmProxy::Destroy() const -> void
+{
+ return mozilla::jni::Method<Destroy_t>::Call(MediaDrmProxy::mCtx, nullptr);
+}
+
+constexpr char MediaDrmProxy::IsSchemeSupported_t::name[];
+constexpr char MediaDrmProxy::IsSchemeSupported_t::signature[];
+
+auto MediaDrmProxy::IsSchemeSupported(mozilla::jni::String::Param a0) -> bool
+{
+ return mozilla::jni::Method<IsSchemeSupported_t>::Call(MediaDrmProxy::Context(), nullptr, a0);
+}
+
+constexpr char MediaDrmProxy::UpdateSession_t::name[];
+constexpr char MediaDrmProxy::UpdateSession_t::signature[];
+
+auto MediaDrmProxy::UpdateSession(int32_t a0, mozilla::jni::String::Param a1, mozilla::jni::ByteArray::Param a2) const -> void
+{
+ return mozilla::jni::Method<UpdateSession_t>::Call(MediaDrmProxy::mCtx, nullptr, a0, a1, a2);
+}
+
+const char16_t MediaDrmProxy::AAC[] = u"audio/mp4a-latm";
+
+const char16_t MediaDrmProxy::AVC[] = u"video/avc";
+
+const char16_t MediaDrmProxy::OPUS[] = u"audio/opus";
+
+const char16_t MediaDrmProxy::VORBIS[] = u"audio/vorbis";
+
+const char16_t MediaDrmProxy::VP8[] = u"video/x-vnd.on2.vp8";
+
+const char16_t MediaDrmProxy::VP9[] = u"video/x-vnd.on2.vp9";
+
+const char MediaDrmProxy::NativeMediaDrmProxyCallbacks::name[] =
+ "org/mozilla/gecko/media/MediaDrmProxy$NativeMediaDrmProxyCallbacks";
+
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::New_t::name[];
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::New_t::signature[];
+
+auto MediaDrmProxy::NativeMediaDrmProxyCallbacks::New() -> NativeMediaDrmProxyCallbacks::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(NativeMediaDrmProxyCallbacks::Context(), nullptr);
+}
+
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnRejectPromise_t::name[];
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnRejectPromise_t::signature[];
+
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionBatchedKeyChanged_t::name[];
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionBatchedKeyChanged_t::signature[];
+
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionClosed_t::name[];
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionClosed_t::signature[];
+
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionCreated_t::name[];
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionCreated_t::signature[];
+
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionError_t::name[];
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionError_t::signature[];
+
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionMessage_t::name[];
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionMessage_t::signature[];
+
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionUpdated_t::name[];
+constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::OnSessionUpdated_t::signature[];
+
+const char Sample::name[] =
+ "org/mozilla/gecko/media/Sample";
+
+constexpr char Sample::WriteToByteBuffer_t::name[];
+constexpr char Sample::WriteToByteBuffer_t::signature[];
+
+auto Sample::WriteToByteBuffer(mozilla::jni::ByteBuffer::Param a0) const -> void
+{
+ return mozilla::jni::Method<WriteToByteBuffer_t>::Call(Sample::mCtx, nullptr, a0);
+}
+
+constexpr char Sample::Info_t::name[];
+constexpr char Sample::Info_t::signature[];
+
+auto Sample::Info() const -> mozilla::jni::Object::LocalRef
+{
+ return mozilla::jni::Field<Info_t>::Get(Sample::mCtx, nullptr);
+}
+
+auto Sample::Info(mozilla::jni::Object::Param a0) const -> void
+{
+ return mozilla::jni::Field<Info_t>::Set(Sample::mCtx, nullptr, a0);
+}
+
+const char SessionKeyInfo::name[] =
+ "org/mozilla/gecko/media/SessionKeyInfo";
+
+constexpr char SessionKeyInfo::New_t::name[];
+constexpr char SessionKeyInfo::New_t::signature[];
+
+auto SessionKeyInfo::New(mozilla::jni::ByteArray::Param a0, int32_t a1) -> SessionKeyInfo::LocalRef
+{
+ return mozilla::jni::Constructor<New_t>::Call(SessionKeyInfo::Context(), nullptr, a0, a1);
+}
+
+constexpr char SessionKeyInfo::KeyId_t::name[];
+constexpr char SessionKeyInfo::KeyId_t::signature[];
+
+auto SessionKeyInfo::KeyId() const -> mozilla::jni::ByteArray::LocalRef
+{
+ return mozilla::jni::Field<KeyId_t>::Get(SessionKeyInfo::mCtx, nullptr);
+}
+
+auto SessionKeyInfo::KeyId(mozilla::jni::ByteArray::Param a0) const -> void
+{
+ return mozilla::jni::Field<KeyId_t>::Set(SessionKeyInfo::mCtx, nullptr, a0);
+}
+
+constexpr char SessionKeyInfo::Status_t::name[];
+constexpr char SessionKeyInfo::Status_t::signature[];
+
+auto SessionKeyInfo::Status() const -> int32_t
+{
+ return mozilla::jni::Field<Status_t>::Get(SessionKeyInfo::mCtx, nullptr);
+}
+
+auto SessionKeyInfo::Status(int32_t a0) const -> void
+{
+ return mozilla::jni::Field<Status_t>::Set(SessionKeyInfo::mCtx, nullptr, a0);
+}
+
+const char Restrictions::name[] =
+ "org/mozilla/gecko/restrictions/Restrictions";
+
+constexpr char Restrictions::IsAllowed_t::name[];
+constexpr char Restrictions::IsAllowed_t::signature[];
+
+auto Restrictions::IsAllowed(int32_t a0, mozilla::jni::String::Param a1) -> bool
+{
+ return mozilla::jni::Method<IsAllowed_t>::Call(Restrictions::Context(), nullptr, a0, a1);
+}
+
+constexpr char Restrictions::IsUserRestricted_t::name[];
+constexpr char Restrictions::IsUserRestricted_t::signature[];
+
+auto Restrictions::IsUserRestricted() -> bool
+{
+ return mozilla::jni::Method<IsUserRestricted_t>::Call(Restrictions::Context(), nullptr);
+}
+
+} /* java */
+} /* mozilla */
diff --git a/widget/android/fennec/FennecJNIWrappers.h b/widget/android/fennec/FennecJNIWrappers.h
new file mode 100644
index 000000000..bb94cd142
--- /dev/null
+++ b/widget/android/fennec/FennecJNIWrappers.h
@@ -0,0 +1,1469 @@
+// GENERATED CODE
+// Generated by the Java program at /build/annotationProcessors at compile time
+// from annotations on Java methods. To update, change the annotations on the
+// corresponding Java methods and rerun the build. Manually updating this file
+// will cause your build to fail.
+
+#ifndef FennecJNIWrappers_h
+#define FennecJNIWrappers_h
+
+#include "mozilla/jni/Refs.h"
+
+namespace mozilla {
+namespace java {
+
+class ANRReporter : public mozilla::jni::ObjectBase<ANRReporter>
+{
+public:
+ static const char name[];
+
+ explicit ANRReporter(const Context& ctx) : ObjectBase<ANRReporter>(ctx) {}
+
+ struct GetNativeStack_t {
+ typedef ANRReporter Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getNativeStack";
+ static constexpr char signature[] =
+ "()Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct ReleaseNativeStack_t {
+ typedef ANRReporter Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "releaseNativeStack";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct RequestNativeStack_t {
+ typedef ANRReporter Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "requestNativeStack";
+ static constexpr char signature[] =
+ "(Z)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class DownloadsIntegration : public mozilla::jni::ObjectBase<DownloadsIntegration>
+{
+public:
+ static const char name[];
+
+ explicit DownloadsIntegration(const Context& ctx) : ObjectBase<DownloadsIntegration>(ctx) {}
+
+ struct GetTemporaryDownloadDirectory_t {
+ typedef DownloadsIntegration Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getTemporaryDownloadDirectory";
+ static constexpr char signature[] =
+ "()Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetTemporaryDownloadDirectory() -> mozilla::jni::String::LocalRef;
+
+ struct ScanMedia_t {
+ typedef DownloadsIntegration Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "scanMedia";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto ScanMedia(mozilla::jni::String::Param, mozilla::jni::String::Param) -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+
+};
+
+class GeckoJavaSampler : public mozilla::jni::ObjectBase<GeckoJavaSampler>
+{
+public:
+ static const char name[];
+
+ explicit GeckoJavaSampler(const Context& ctx) : ObjectBase<GeckoJavaSampler>(ctx) {}
+
+ struct GetFrameName_t {
+ typedef GeckoJavaSampler Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "getFrameName";
+ static constexpr char signature[] =
+ "(III)Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetFrameName(int32_t, int32_t, int32_t) -> mozilla::jni::String::LocalRef;
+
+ struct GetProfilerTime_t {
+ typedef GeckoJavaSampler Owner;
+ typedef double ReturnType;
+ typedef double SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "getProfilerTime";
+ static constexpr char signature[] =
+ "()D";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct GetSampleTime_t {
+ typedef GeckoJavaSampler Owner;
+ typedef double ReturnType;
+ typedef double SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "getSampleTime";
+ static constexpr char signature[] =
+ "(II)D";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetSampleTime(int32_t, int32_t) -> double;
+
+ struct GetThreadName_t {
+ typedef GeckoJavaSampler Owner;
+ typedef mozilla::jni::String::LocalRef ReturnType;
+ typedef mozilla::jni::String::Param SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "getThreadName";
+ static constexpr char signature[] =
+ "(I)Ljava/lang/String;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto GetThreadName(int32_t) -> mozilla::jni::String::LocalRef;
+
+ struct Pause_t {
+ typedef GeckoJavaSampler Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "pause";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Pause() -> void;
+
+ struct Start_t {
+ typedef GeckoJavaSampler Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "start";
+ static constexpr char signature[] =
+ "(II)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Start(int32_t, int32_t) -> void;
+
+ struct Stop_t {
+ typedef GeckoJavaSampler Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "stop";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Stop() -> void;
+
+ struct Unpause_t {
+ typedef GeckoJavaSampler Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "unpause";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Unpause() -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class MemoryMonitor : public mozilla::jni::ObjectBase<MemoryMonitor>
+{
+public:
+ static const char name[];
+
+ explicit MemoryMonitor(const Context& ctx) : ObjectBase<MemoryMonitor>(ctx) {}
+
+ struct DispatchMemoryPressure_t {
+ typedef MemoryMonitor Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "dispatchMemoryPressure";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+
+ template<class Impl> class Natives;
+};
+
+class PresentationMediaPlayerManager : public mozilla::jni::ObjectBase<PresentationMediaPlayerManager>
+{
+public:
+ static const char name[];
+
+ explicit PresentationMediaPlayerManager(const Context& ctx) : ObjectBase<PresentationMediaPlayerManager>(ctx) {}
+
+ struct AddPresentationSurface_t {
+ typedef PresentationMediaPlayerManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "addPresentationSurface";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/GeckoView;Landroid/view/Surface;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct InvalidateAndScheduleComposite_t {
+ typedef PresentationMediaPlayerManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "invalidateAndScheduleComposite";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/GeckoView;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct RemovePresentationSurface_t {
+ typedef PresentationMediaPlayerManager Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "removePresentationSurface";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::UI;
+
+ template<class Impl> class Natives;
+};
+
+class ScreenManagerHelper : public mozilla::jni::ObjectBase<ScreenManagerHelper>
+{
+public:
+ static const char name[];
+
+ explicit ScreenManagerHelper(const Context& ctx) : ObjectBase<ScreenManagerHelper>(ctx) {}
+
+ struct AddDisplay_t {
+ typedef ScreenManagerHelper Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ int32_t,
+ float> Args;
+ static constexpr char name[] = "addDisplay";
+ static constexpr char signature[] =
+ "(IIIF)I";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct RemoveDisplay_t {
+ typedef ScreenManagerHelper Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "removeDisplay";
+ static constexpr char signature[] =
+ "(I)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class Telemetry : public mozilla::jni::ObjectBase<Telemetry>
+{
+public:
+ static const char name[];
+
+ explicit Telemetry(const Context& ctx) : ObjectBase<Telemetry>(ctx) {}
+
+ struct AddHistogram_t {
+ typedef Telemetry Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ int32_t> Args;
+ static constexpr char name[] = "nativeAddHistogram";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;I)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct AddKeyedHistogram_t {
+ typedef Telemetry Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ int32_t> Args;
+ static constexpr char name[] = "nativeAddKeyedHistogram";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;I)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct AddUIEvent_t {
+ typedef Telemetry Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ int64_t,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "nativeAddUiEvent";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct StartUISession_t {
+ typedef Telemetry Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ int64_t> Args;
+ static constexpr char name[] = "nativeStartUiSession";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;J)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct StopUISession_t {
+ typedef Telemetry Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param,
+ int64_t> Args;
+ static constexpr char name[] = "nativeStopUiSession";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;J)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class ThumbnailHelper : public mozilla::jni::ObjectBase<ThumbnailHelper>
+{
+public:
+ static const char name[];
+
+ explicit ThumbnailHelper(const Context& ctx) : ObjectBase<ThumbnailHelper>(ctx) {}
+
+ struct NotifyThumbnail_t {
+ typedef ThumbnailHelper Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteBuffer::Param,
+ mozilla::jni::Object::Param,
+ bool,
+ bool> Args;
+ static constexpr char name[] = "notifyThumbnail";
+ static constexpr char signature[] =
+ "(Ljava/nio/ByteBuffer;Lorg/mozilla/gecko/Tab;ZZ)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto NotifyThumbnail(mozilla::jni::ByteBuffer::Param, mozilla::jni::Object::Param, bool, bool) -> void;
+
+ struct RequestThumbnail_t {
+ typedef ThumbnailHelper Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteBuffer::Param,
+ mozilla::jni::Object::Param,
+ int32_t,
+ int32_t,
+ int32_t> Args;
+ static constexpr char name[] = "requestThumbnailLocked";
+ static constexpr char signature[] =
+ "(Ljava/nio/ByteBuffer;Lorg/mozilla/gecko/Tab;III)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::PROXY;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class ZoomedView : public mozilla::jni::ObjectBase<ZoomedView>
+{
+public:
+ static const char name[];
+
+ explicit ZoomedView(const Context& ctx) : ObjectBase<ZoomedView>(ctx) {}
+
+ struct RequestZoomedViewData_t {
+ typedef ZoomedView Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteBuffer::Param,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ int32_t,
+ float> Args;
+ static constexpr char name[] = "requestZoomedViewData";
+ static constexpr char signature[] =
+ "(Ljava/nio/ByteBuffer;IIIIIF)V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class AudioFocusAgent : public mozilla::jni::ObjectBase<AudioFocusAgent>
+{
+public:
+ static const char name[];
+
+ explicit AudioFocusAgent(const Context& ctx) : ObjectBase<AudioFocusAgent>(ctx) {}
+
+ struct NotifyStartedPlaying_t {
+ typedef AudioFocusAgent Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "notifyStartedPlaying";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto NotifyStartedPlaying() -> void;
+
+ struct NotifyStoppedPlaying_t {
+ typedef AudioFocusAgent Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "notifyStoppedPlaying";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto NotifyStoppedPlaying() -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+
+};
+
+class CodecProxy : public mozilla::jni::ObjectBase<CodecProxy>
+{
+public:
+ static const char name[];
+
+ explicit CodecProxy(const Context& ctx) : ObjectBase<CodecProxy>(ctx) {}
+
+ class NativeCallbacks;
+
+ struct Create_t {
+ typedef CodecProxy Owner;
+ typedef CodecProxy::LocalRef ReturnType;
+ typedef CodecProxy::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param,
+ mozilla::jni::Object::Param,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "create";
+ static constexpr char signature[] =
+ "(Landroid/media/MediaFormat;Landroid/view/Surface;Lorg/mozilla/gecko/media/CodecProxy$Callbacks;)Lorg/mozilla/gecko/media/CodecProxy;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Create(mozilla::jni::Object::Param, mozilla::jni::Object::Param, mozilla::jni::Object::Param) -> CodecProxy::LocalRef;
+
+ struct Flush_t {
+ typedef CodecProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "flush";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Flush() const -> bool;
+
+ struct Input_t {
+ typedef CodecProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteBuffer::Param,
+ mozilla::jni::Object::Param,
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "input";
+ static constexpr char signature[] =
+ "(Ljava/nio/ByteBuffer;Landroid/media/MediaCodec$BufferInfo;Landroid/media/MediaCodec$CryptoInfo;)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Input(mozilla::jni::ByteBuffer::Param, mozilla::jni::Object::Param, mozilla::jni::Object::Param) const -> bool;
+
+ struct Release_t {
+ typedef CodecProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "release";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Release() const -> bool;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class CodecProxy::NativeCallbacks : public mozilla::jni::ObjectBase<NativeCallbacks>
+{
+public:
+ static const char name[];
+
+ explicit NativeCallbacks(const Context& ctx) : ObjectBase<NativeCallbacks>(ctx) {}
+
+ struct New_t {
+ typedef NativeCallbacks Owner;
+ typedef NativeCallbacks::LocalRef ReturnType;
+ typedef NativeCallbacks::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New() -> NativeCallbacks::LocalRef;
+
+ struct DisposeNative_t {
+ typedef NativeCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "disposeNative";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OnError_t {
+ typedef NativeCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ bool> Args;
+ static constexpr char name[] = "onError";
+ static constexpr char signature[] =
+ "(Z)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OnInputExhausted_t {
+ typedef NativeCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "onInputExhausted";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OnOutput_t {
+ typedef NativeCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "onOutput";
+ static constexpr char signature[] =
+ "(Lorg/mozilla/gecko/media/Sample;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ struct OnOutputFormatChanged_t {
+ typedef NativeCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::Object::Param> Args;
+ static constexpr char name[] = "onOutputFormatChanged";
+ static constexpr char signature[] =
+ "(Landroid/media/MediaFormat;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class MediaDrmProxy : public mozilla::jni::ObjectBase<MediaDrmProxy>
+{
+public:
+ static const char name[];
+
+ explicit MediaDrmProxy(const Context& ctx) : ObjectBase<MediaDrmProxy>(ctx) {}
+
+ class NativeMediaDrmProxyCallbacks;
+
+ struct CanDecode_t {
+ typedef MediaDrmProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "CanDecode";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto CanDecode(mozilla::jni::String::Param) -> bool;
+
+ struct IsCryptoSchemeSupported_t {
+ typedef MediaDrmProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "IsCryptoSchemeSupported";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Ljava/lang/String;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto IsCryptoSchemeSupported(mozilla::jni::String::Param, mozilla::jni::String::Param) -> bool;
+
+ struct CloseSession_t {
+ typedef MediaDrmProxy Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "closeSession";
+ static constexpr char signature[] =
+ "(ILjava/lang/String;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto CloseSession(int32_t, mozilla::jni::String::Param) const -> void;
+
+ struct Create_t {
+ typedef MediaDrmProxy Owner;
+ typedef MediaDrmProxy::LocalRef ReturnType;
+ typedef MediaDrmProxy::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param,
+ mozilla::jni::Object::Param,
+ bool> Args;
+ static constexpr char name[] = "create";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;Lorg/mozilla/gecko/media/MediaDrmProxy$Callbacks;Z)Lorg/mozilla/gecko/media/MediaDrmProxy;";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto Create(mozilla::jni::String::Param, mozilla::jni::Object::Param, bool) -> MediaDrmProxy::LocalRef;
+
+ struct CreateSession_t {
+ typedef MediaDrmProxy Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ mozilla::jni::String::Param,
+ mozilla::jni::ByteArray::Param> Args;
+ static constexpr char name[] = "createSession";
+ static constexpr char signature[] =
+ "(IILjava/lang/String;[B)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto CreateSession(int32_t, int32_t, mozilla::jni::String::Param, mozilla::jni::ByteArray::Param) const -> void;
+
+ struct Destroy_t {
+ typedef MediaDrmProxy Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "destroy";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Destroy() const -> void;
+
+ struct IsSchemeSupported_t {
+ typedef MediaDrmProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "isSchemeSupported";
+ static constexpr char signature[] =
+ "(Ljava/lang/String;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto IsSchemeSupported(mozilla::jni::String::Param) -> bool;
+
+ struct UpdateSession_t {
+ typedef MediaDrmProxy Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::String::Param,
+ mozilla::jni::ByteArray::Param> Args;
+ static constexpr char name[] = "updateSession";
+ static constexpr char signature[] =
+ "(ILjava/lang/String;[B)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto UpdateSession(int32_t, mozilla::jni::String::Param, mozilla::jni::ByteArray::Param) const -> void;
+
+ static const char16_t AAC[];
+
+ static const char16_t AVC[];
+
+ static const char16_t OPUS[];
+
+ static const char16_t VORBIS[];
+
+ static const char16_t VP8[];
+
+ static const char16_t VP9[];
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class MediaDrmProxy::NativeMediaDrmProxyCallbacks : public mozilla::jni::ObjectBase<NativeMediaDrmProxyCallbacks>
+{
+public:
+ static const char name[];
+
+ explicit NativeMediaDrmProxyCallbacks(const Context& ctx) : ObjectBase<NativeMediaDrmProxyCallbacks>(ctx) {}
+
+ struct New_t {
+ typedef NativeMediaDrmProxyCallbacks Owner;
+ typedef NativeMediaDrmProxyCallbacks::LocalRef ReturnType;
+ typedef NativeMediaDrmProxyCallbacks::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "()V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New() -> NativeMediaDrmProxyCallbacks::LocalRef;
+
+ struct OnRejectPromise_t {
+ typedef NativeMediaDrmProxyCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "onRejectPromise";
+ static constexpr char signature[] =
+ "(ILjava/lang/String;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnSessionBatchedKeyChanged_t {
+ typedef NativeMediaDrmProxyCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteArray::Param,
+ mozilla::jni::ObjectArray::Param> Args;
+ static constexpr char name[] = "onSessionBatchedKeyChanged";
+ static constexpr char signature[] =
+ "([B[Lorg/mozilla/gecko/media/SessionKeyInfo;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnSessionClosed_t {
+ typedef NativeMediaDrmProxyCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::ByteArray::Param> Args;
+ static constexpr char name[] = "onSessionClosed";
+ static constexpr char signature[] =
+ "(I[B)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnSessionCreated_t {
+ typedef NativeMediaDrmProxyCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ int32_t,
+ mozilla::jni::ByteArray::Param,
+ mozilla::jni::ByteArray::Param> Args;
+ static constexpr char name[] = "onSessionCreated";
+ static constexpr char signature[] =
+ "(II[B[B)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnSessionError_t {
+ typedef NativeMediaDrmProxyCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteArray::Param,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "onSessionError";
+ static constexpr char signature[] =
+ "([BLjava/lang/String;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnSessionMessage_t {
+ typedef NativeMediaDrmProxyCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteArray::Param,
+ int32_t,
+ mozilla::jni::ByteArray::Param> Args;
+ static constexpr char name[] = "onSessionMessage";
+ static constexpr char signature[] =
+ "([BI[B)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ struct OnSessionUpdated_t {
+ typedef NativeMediaDrmProxyCallbacks Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::ByteArray::Param> Args;
+ static constexpr char name[] = "onSessionUpdated";
+ static constexpr char signature[] =
+ "(I[B)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::GECKO;
+ };
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+ template<class Impl> class Natives;
+};
+
+class Sample : public mozilla::jni::ObjectBase<Sample>
+{
+public:
+ static const char name[];
+
+ explicit Sample(const Context& ctx) : ObjectBase<Sample>(ctx) {}
+
+ struct WriteToByteBuffer_t {
+ typedef Sample Owner;
+ typedef void ReturnType;
+ typedef void SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteBuffer::Param> Args;
+ static constexpr char name[] = "writeToByteBuffer";
+ static constexpr char signature[] =
+ "(Ljava/nio/ByteBuffer;)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto WriteToByteBuffer(mozilla::jni::ByteBuffer::Param) const -> void;
+
+ struct Info_t {
+ typedef Sample Owner;
+ typedef mozilla::jni::Object::LocalRef ReturnType;
+ typedef mozilla::jni::Object::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "info";
+ static constexpr char signature[] =
+ "Landroid/media/MediaCodec$BufferInfo;";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Info() const -> mozilla::jni::Object::LocalRef;
+
+ auto Info(mozilla::jni::Object::Param) const -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class SessionKeyInfo : public mozilla::jni::ObjectBase<SessionKeyInfo>
+{
+public:
+ static const char name[];
+
+ explicit SessionKeyInfo(const Context& ctx) : ObjectBase<SessionKeyInfo>(ctx) {}
+
+ struct New_t {
+ typedef SessionKeyInfo Owner;
+ typedef SessionKeyInfo::LocalRef ReturnType;
+ typedef SessionKeyInfo::Param SetterType;
+ typedef mozilla::jni::Args<
+ mozilla::jni::ByteArray::Param,
+ int32_t> Args;
+ static constexpr char name[] = "<init>";
+ static constexpr char signature[] =
+ "([BI)V";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto New(mozilla::jni::ByteArray::Param, int32_t) -> SessionKeyInfo::LocalRef;
+
+ struct KeyId_t {
+ typedef SessionKeyInfo Owner;
+ typedef mozilla::jni::ByteArray::LocalRef ReturnType;
+ typedef mozilla::jni::ByteArray::Param SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "keyId";
+ static constexpr char signature[] =
+ "[B";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto KeyId() const -> mozilla::jni::ByteArray::LocalRef;
+
+ auto KeyId(mozilla::jni::ByteArray::Param) const -> void;
+
+ struct Status_t {
+ typedef SessionKeyInfo Owner;
+ typedef int32_t ReturnType;
+ typedef int32_t SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "status";
+ static constexpr char signature[] =
+ "I";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto Status() const -> int32_t;
+
+ auto Status(int32_t) const -> void;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+
+};
+
+class Restrictions : public mozilla::jni::ObjectBase<Restrictions>
+{
+public:
+ static const char name[];
+
+ explicit Restrictions(const Context& ctx) : ObjectBase<Restrictions>(ctx) {}
+
+ struct IsAllowed_t {
+ typedef Restrictions Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ int32_t,
+ mozilla::jni::String::Param> Args;
+ static constexpr char name[] = "isAllowed";
+ static constexpr char signature[] =
+ "(ILjava/lang/String;)Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto IsAllowed(int32_t, mozilla::jni::String::Param) -> bool;
+
+ struct IsUserRestricted_t {
+ typedef Restrictions Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<> Args;
+ static constexpr char name[] = "isUserRestricted";
+ static constexpr char signature[] =
+ "()Z";
+ static const bool isStatic = true;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ static auto IsUserRestricted() -> bool;
+
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::GECKO;
+
+};
+
+} /* java */
+} /* mozilla */
+#endif // FennecJNIWrappers_h
diff --git a/widget/android/fennec/MemoryMonitor.h b/widget/android/fennec/MemoryMonitor.h
new file mode 100644
index 000000000..c87538424
--- /dev/null
+++ b/widget/android/fennec/MemoryMonitor.h
@@ -0,0 +1,27 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 MemoryMonitor_h
+#define MemoryMonitor_h
+
+#include "FennecJNINatives.h"
+#include "nsMemoryPressure.h"
+
+namespace mozilla {
+
+class MemoryMonitor final
+ : public java::MemoryMonitor::Natives<MemoryMonitor>
+{
+public:
+ static void
+ DispatchMemoryPressure()
+ {
+ NS_DispatchMemoryPressure(MemoryPressureState::MemPressure_New);
+ }
+};
+
+} // namespace mozilla
+
+#endif // MemoryMonitor_h
diff --git a/widget/android/fennec/Telemetry.h b/widget/android/fennec/Telemetry.h
new file mode 100644
index 000000000..c72735496
--- /dev/null
+++ b/widget/android/fennec/Telemetry.h
@@ -0,0 +1,100 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 "FennecJNINatives.h"
+#include "nsAppShell.h"
+#include "nsIAndroidBridge.h"
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace widget {
+
+class Telemetry final
+ : public java::Telemetry::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/fennec/ThumbnailHelper.h b/widget/android/fennec/ThumbnailHelper.h
new file mode 100644
index 000000000..08fae787e
--- /dev/null
+++ b/widget/android/fennec/ThumbnailHelper.h
@@ -0,0 +1,302 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 ThumbnailHelper_h
+#define ThumbnailHelper_h
+
+#include "AndroidBridge.h"
+#include "FennecJNINatives.h"
+#include "gfxPlatform.h"
+#include "mozIDOMWindow.h"
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIChannel.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDOMClientRect.h"
+#include "nsIDocShell.h"
+#include "nsIHttpChannel.h"
+#include "nsIPresShell.h"
+#include "nsIURI.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+
+class ThumbnailHelper final
+ : public java::ThumbnailHelper::Natives<ThumbnailHelper>
+ , public java::ZoomedView::Natives<ThumbnailHelper>
+{
+ ThumbnailHelper() = delete;
+
+ static already_AddRefed<mozIDOMWindowProxy>
+ GetWindowForTab(int32_t aTabId)
+ {
+ nsAppShell* const appShell = nsAppShell::Get();
+ if (!appShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIAndroidBrowserApp> browserApp = appShell->GetBrowserApp();
+ if (!browserApp) {
+ return nullptr;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ nsCOMPtr<nsIBrowserTab> tab;
+
+ if (NS_FAILED(browserApp->GetBrowserTab(aTabId, getter_AddRefs(tab))) ||
+ !tab ||
+ NS_FAILED(tab->GetWindow(getter_AddRefs(window))) ||
+ !window) {
+ return nullptr;
+ }
+
+ return window.forget();
+ }
+
+ // Decides if we should store thumbnails for a given docshell based on the
+ // presence of a Cache-Control: no-store header and the
+ // "browser.cache.disk_cache_ssl" pref.
+ static bool
+ ShouldStoreThumbnail(nsIDocShell* docShell)
+ {
+ nsCOMPtr<nsIChannel> channel;
+ if (NS_FAILED(docShell->GetCurrentDocumentChannel(
+ getter_AddRefs(channel))) || !channel) {
+ return false;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
+ if (!httpChannel) {
+ // Allow storing non-HTTP thumbnails.
+ return true;
+ }
+
+ // Don't store thumbnails for sites that didn't load or have
+ // Cache-Control: no-store.
+ uint32_t responseStatus = 0;
+ bool isNoStoreResponse = false;
+
+ if (NS_FAILED(httpChannel->GetResponseStatus(&responseStatus)) ||
+ (responseStatus / 100) != 2 ||
+ NS_FAILED(httpChannel->IsNoStoreResponse(&isNoStoreResponse)) ||
+ isNoStoreResponse) {
+ return false;
+ }
+
+ // Deny storage if we're viewing a HTTPS page with a 'Cache-Control'
+ // header having a value that is not 'public', unless enabled by user.
+ nsCOMPtr<nsIURI> uri;
+ bool isHttps = false;
+
+ if (NS_FAILED(channel->GetURI(getter_AddRefs(uri))) ||
+ !uri ||
+ NS_FAILED(uri->SchemeIs("https", &isHttps))) {
+ return false;
+ }
+
+ if (!isHttps ||
+ Preferences::GetBool("browser.cache.disk_cache_ssl", false)) {
+ // Allow storing non-HTTPS thumbnails, and HTTPS ones if enabled by
+ // user.
+ return true;
+ }
+
+ nsAutoCString cacheControl;
+ if (NS_FAILED(httpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("Cache-Control"), cacheControl))) {
+ return false;
+ }
+
+ if (cacheControl.IsEmpty() ||
+ cacheControl.LowerCaseEqualsLiteral("public")) {
+ // Allow no cache-control, or public cache-control.
+ return true;
+ }
+ return false;
+ }
+
+ // Return a non-null nsIDocShell to indicate success.
+ static already_AddRefed<nsIDocShell>
+ GetThumbnailAndDocShell(mozIDOMWindowProxy* aWindow,
+ jni::ByteBuffer::Param aData,
+ int32_t aThumbWidth, int32_t aThumbHeight,
+ const CSSRect& aPageRect, float aZoomFactor)
+ {
+ nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(aWindow);
+ nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
+ RefPtr<nsPresContext> presContext;
+
+ if (!docShell || NS_FAILED(docShell->GetPresContext(
+ getter_AddRefs(presContext))) || !presContext) {
+ return nullptr;
+ }
+
+ uint8_t* const data = static_cast<uint8_t*>(aData->Address());
+ if (!data) {
+ return nullptr;
+ }
+
+ const bool is24bit = !AndroidBridge::Bridge() ||
+ AndroidBridge::Bridge()->GetScreenDepth() == 24;
+ const uint32_t stride = aThumbWidth * (is24bit ? 4 : 2);
+
+ RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->CreateDrawTargetForData(
+ data,
+ IntSize(aThumbWidth, aThumbHeight),
+ stride,
+ is24bit ? SurfaceFormat::B8G8R8A8
+ : SurfaceFormat::R5G6B5_UINT16);
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(context); // checked the draw target above
+
+ context->SetMatrix(context->CurrentMatrix().Scale(
+ aZoomFactor * float(aThumbWidth) / aPageRect.width,
+ aZoomFactor * float(aThumbHeight) / aPageRect.height));
+
+ const nsRect drawRect(
+ nsPresContext::CSSPixelsToAppUnits(aPageRect.x),
+ nsPresContext::CSSPixelsToAppUnits(aPageRect.y),
+ nsPresContext::CSSPixelsToAppUnits(aPageRect.width),
+ nsPresContext::CSSPixelsToAppUnits(aPageRect.height));
+ const uint32_t renderDocFlags =
+ nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
+ nsIPresShell::RENDER_DOCUMENT_RELATIVE;
+ const nscolor bgColor = NS_RGB(255, 255, 255);
+
+ if (NS_FAILED(presShell->RenderDocument(
+ drawRect, renderDocFlags, bgColor, context))) {
+ return nullptr;
+ }
+
+ if (is24bit) {
+ gfxUtils::ConvertBGRAtoRGBA(data, stride * aThumbHeight);
+ }
+
+ return docShell.forget();
+ }
+
+public:
+ static void Init()
+ {
+ java::ThumbnailHelper::Natives<ThumbnailHelper>::Init();
+ java::ZoomedView::Natives<ThumbnailHelper>::Init();
+ }
+
+ template<class Functor>
+ static void OnNativeCall(Functor&& aCall)
+ {
+ class IdleEvent : public nsAppShell::LambdaEvent<Functor>
+ {
+ using Base = nsAppShell::LambdaEvent<Functor>;
+
+ public:
+ IdleEvent(Functor&& aCall)
+ : Base(Forward<Functor>(aCall))
+ {}
+
+ void Run() override
+ {
+ MessageLoop::current()->PostIdleTask(
+ NS_NewRunnableFunction(Move(Base::lambda)));
+ }
+ };
+
+ // Invoke RequestThumbnail on the main thread when the thread is idle.
+ nsAppShell::PostEvent(MakeUnique<IdleEvent>(Forward<Functor>(aCall)));
+ }
+
+ static void
+ RequestThumbnail(jni::ByteBuffer::Param aData, jni::Object::Param aTab,
+ int32_t aTabId, int32_t aWidth, int32_t aHeight)
+ {
+ nsCOMPtr<mozIDOMWindowProxy> window = GetWindowForTab(aTabId);
+ if (!window || !aData) {
+ java::ThumbnailHelper::NotifyThumbnail(
+ aData, aTab, /* success */ false, /* store */ false);
+ return;
+ }
+
+ // take a screenshot, as wide as possible, proportional to the destination size
+ nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
+ nsCOMPtr<nsIDOMClientRect> rect;
+ float pageLeft = 0.0f, pageTop = 0.0f, pageWidth = 0.0f, pageHeight = 0.0f;
+
+ if (!utils ||
+ NS_FAILED(utils->GetRootBounds(getter_AddRefs(rect))) ||
+ !rect ||
+ NS_FAILED(rect->GetLeft(&pageLeft)) ||
+ NS_FAILED(rect->GetTop(&pageTop)) ||
+ NS_FAILED(rect->GetWidth(&pageWidth)) ||
+ NS_FAILED(rect->GetHeight(&pageHeight)) ||
+ int32_t(pageWidth) == 0 || int32_t(pageHeight) == 0) {
+ java::ThumbnailHelper::NotifyThumbnail(
+ aData, aTab, /* success */ false, /* store */ false);
+ return;
+ }
+
+ const float aspectRatio = float(aWidth) / float(aHeight);
+ if (pageWidth / aspectRatio < pageHeight) {
+ pageHeight = pageWidth / aspectRatio;
+ } else {
+ pageWidth = pageHeight * aspectRatio;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = GetThumbnailAndDocShell(
+ window, aData, aWidth, aHeight,
+ CSSRect(pageLeft, pageTop, pageWidth, pageHeight),
+ /* aZoomFactor */ 1.0f);
+ const bool success = !!docShell;
+ const bool store = success ? ShouldStoreThumbnail(docShell) : false;
+
+ java::ThumbnailHelper::NotifyThumbnail(aData, aTab, success, store);
+ }
+
+ static void
+ RequestZoomedViewData(jni::ByteBuffer::Param aData, int32_t aTabId,
+ int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight, float aScale)
+ {
+ nsCOMPtr<mozIDOMWindowProxy> window = GetWindowForTab(aTabId);
+ if (!window || !aData) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(window);
+ nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
+ RefPtr<nsPresContext> presContext;
+
+ if (!docShell || NS_FAILED(docShell->GetPresContext(
+ getter_AddRefs(presContext))) || !presContext) {
+ return;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
+ LayoutDeviceRect rect = LayoutDeviceRect(aX, aY, aWidth, aHeight);
+ const float resolution = presShell->GetCumulativeResolution();
+ rect.Scale(1.0f / LayoutDeviceToLayerScale(resolution).scale);
+
+ docShell = GetThumbnailAndDocShell(
+ window, aData, aWidth, aHeight, CSSRect::FromAppUnits(
+ rect.ToAppUnits(rect, presContext->AppUnitsPerDevPixel())),
+ aScale);
+
+ if (docShell) {
+ java::LayerView::UpdateZoomedView(aData);
+ }
+ }
+};
+
+} // namespace mozilla
+
+#endif // ThumbnailHelper_h
diff --git a/widget/android/fennec/moz.build b/widget/android/fennec/moz.build
new file mode 100644
index 000000000..0d6a8e0cd
--- /dev/null
+++ b/widget/android/fennec/moz.build
@@ -0,0 +1,21 @@
+# -*- 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/.
+
+EXPORTS += [
+ 'FennecJNINatives.h',
+ 'FennecJNIWrappers.h',
+]
+
+UNIFIED_SOURCES += [
+ 'FennecJNIWrappers.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/widget',
+ '/widget/android',
+]
diff --git a/widget/android/jni/Accessors.h b/widget/android/jni/Accessors.h
new file mode 100644
index 000000000..fe2cbc9d4
--- /dev/null
+++ b/widget/android/jni/Accessors.h
@@ -0,0 +1,274 @@
+#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
+{
+ Value(jboolean z) { val.z = z; }
+ Value(jbyte b) { val.b = b; }
+ Value(jchar c) { val.c = c; }
+ Value(jshort s) { val.s = s; }
+ Value(jint i) { val.i = i; }
+ Value(jlong j) { val.j = j; }
+ Value(jfloat f) { val.f = f; }
+ Value(jdouble d) { val.d = d; }
+ 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.RawClassRef(), 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.RawClassRef(), 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.RawClassRef(), 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.RawClassRef(), 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.RawClassRef(), 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;
+
+
+// Define the sClassRef member declared in Refs.h and
+// used by Method and Field above.
+template<class C, typename T> jclass Context<C, T>::sClassRef;
+
+} // namespace jni
+} // namespace mozilla
+
+#endif // mozilla_jni_Accessors_h__
diff --git a/widget/android/jni/Natives.h b/widget/android/jni/Natives.h
new file mode 100644
index 000000000..511d96a87
--- /dev/null
+++ b/widget/android/jni/Natives.h
@@ -0,0 +1,707 @@
+#ifndef mozilla_jni_Natives_h__
+#define mozilla_jni_Natives_h__
+
+#include <jni.h>
+
+#include "mozilla/IndexSequence.h"
+#include "mozilla/Move.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/jni/Accessors.h"
+#include "mozilla/jni/Refs.h"
+#include "mozilla/jni/Types.h"
+#include "mozilla/jni/Utils.h"
+
+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. To attach a Java instance to a C++ instance, pass in a pointer
+ * to the C++ class (i.e. MyClass*).
+ *
+ * class MyClass : public SupportsWeakPtr<MyClass>
+ * , public MyJavaClass::Natives<MyClass>
+ * {
+ * // ...
+ *
+ * public:
+ * MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MyClass)
+ * using MyJavaClass::Natives<MyClass>::Dispose;
+ *
+ * void AttachTo(const MyJavaClass::LocalRef& instance)
+ * {
+ * MyJavaClass::Natives<MyClass>::AttachInstance(instance, this);
+ *
+ * // "instance" does NOT own "this", so the C++ object
+ * // lifetime is separate from the Java object lifetime.
+ * }
+ * };
+ *
+ * * If the C++ class doesn't inherit from mozilla::SupportsWeakPtr, 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>::Dispose;
+ *
+ * static void AttachTo(const MyJavaClass::LocalRef& instance)
+ * {
+ * MyJavaClass::Natives<MyClass>::AttachInstance(
+ * instance, mozilla::MakeUnique<MyClass>());
+ *
+ * // "instance" owns the newly created C++ object, so the C++
+ * // object is destroyed as soon as instance.dispose() is called.
+ * }
+ * };
+ */
+
+namespace detail {
+
+inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle)
+{
+ if (!handle) {
+ if (!env->ExceptionCheck()) {
+ ThrowException(env, "java/lang/NullPointerException",
+ "Null native pointer");
+ }
+ return 0;
+ }
+ return handle;
+}
+
+template<class Impl, bool UseWeakPtr = mozilla::IsBaseOf<
+ SupportsWeakPtr<Impl>, Impl>::value /* = false */>
+struct NativePtr
+{
+ static Impl* Get(JNIEnv* env, jobject instance)
+ {
+ return reinterpret_cast<Impl*>(CheckNativeHandle(
+ env, GetNativeHandle(env, instance)));
+ }
+
+ template<class LocalRef>
+ static Impl* Get(const LocalRef& instance)
+ {
+ return Get(instance.Env(), instance.Get());
+ }
+
+ 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());
+ }
+
+ template<class LocalRef>
+ static void Clear(const LocalRef& instance)
+ {
+ UniquePtr<Impl> ptr(reinterpret_cast<Impl*>(
+ 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 NativePtr<Impl, /* UseWeakPtr = */ true>
+{
+ static Impl* Get(JNIEnv* env, jobject instance)
+ {
+ const auto ptr = reinterpret_cast<WeakPtr<Impl>*>(
+ CheckNativeHandle(env, GetNativeHandle(env, instance)));
+ if (!ptr) {
+ return nullptr;
+ }
+
+ Impl* const impl = *ptr;
+ if (!impl) {
+ ThrowException(env, "java/lang/NullPointerException",
+ "Native object already released");
+ }
+ return impl;
+ }
+
+ template<class LocalRef>
+ static Impl* Get(const LocalRef& instance)
+ {
+ return Get(instance.Env(), instance.Get());
+ }
+
+ template<class LocalRef>
+ static void Set(const LocalRef& instance, Impl* ptr)
+ {
+ Clear(instance);
+ SetNativeHandle(instance.Env(), instance.Get(),
+ reinterpret_cast<uintptr_t>(new WeakPtr<Impl>(ptr)));
+ MOZ_CATCH_JNI_EXCEPTION(instance.Env());
+ }
+
+ template<class LocalRef>
+ static void Clear(const LocalRef& instance)
+ {
+ const auto ptr = reinterpret_cast<WeakPtr<Impl>*>(
+ 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
+
+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(mozilla::Move(call)) {}
+ * virtual void run() override { mCall(); }
+ * };
+ *
+ * public:
+ * template<class Functor>
+ * static void OnNativeCall(Functor&& call)
+ * {
+ * RunOnAnotherThread(new ProxyRunnable(mozilla::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 : public AbstractCall
+{
+ // "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.
+ typedef typename mozilla::Conditional<IsStatic,
+ Class, Owner>::Type ThisArgClass;
+ typedef typename mozilla::Conditional<IsStatic,
+ jclass, jobject>::Type ThisArgJNIType;
+
+ // Type signature of the destination C++ function, which matches the
+ // Method template parameter in NativeStubImpl::Wrap.
+ typedef typename mozilla::Conditional<IsStatic,
+ typename mozilla::Conditional<HasThisArg,
+ void (*) (const Class::LocalRef&, Args...),
+ void (*) (Args...)>::Type,
+ typename mozilla::Conditional<HasThisArg,
+ void (Impl::*) (const typename Owner::LocalRef&, Args...),
+ void (Impl::*) (Args...)>::Type>::Type NativeCallType;
+
+ // 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>
+ typename mozilla::EnableIf<Static && ThisArg, void>::Type
+ Call(const Class::LocalRef& cls,
+ mozilla::IndexSequence<Indices...>) const
+ {
+ (*mNativeCall)(cls, mozilla::Get<Indices>(mArgs)...);
+ }
+
+ template<bool Static, bool ThisArg, size_t... Indices>
+ typename mozilla::EnableIf<Static && !ThisArg, void>::Type
+ Call(const Class::LocalRef& cls,
+ mozilla::IndexSequence<Indices...>) const
+ {
+ (*mNativeCall)(mozilla::Get<Indices>(mArgs)...);
+ }
+
+ template<bool Static, bool ThisArg, size_t... Indices>
+ typename mozilla::EnableIf<!Static && ThisArg, void>::Type
+ Call(const typename Owner::LocalRef& inst,
+ mozilla::IndexSequence<Indices...>) const
+ {
+ Impl* const impl = NativePtr<Impl>::Get(inst);
+ MOZ_CATCH_JNI_EXCEPTION(inst.Env());
+ (impl->*mNativeCall)(inst, mozilla::Get<Indices>(mArgs)...);
+ }
+
+ template<bool Static, bool ThisArg, size_t... Indices>
+ typename mozilla::EnableIf<!Static && !ThisArg, void>::Type
+ Call(const typename Owner::LocalRef& inst,
+ mozilla::IndexSequence<Indices...>) const
+ {
+ Impl* const impl = NativePtr<Impl>::Get(inst);
+ MOZ_CATCH_JNI_EXCEPTION(inst.Env());
+ (impl->*mNativeCall)(mozilla::Get<Indices>(mArgs)...);
+ }
+
+ template<size_t... Indices>
+ void Clear(JNIEnv* env, mozilla::IndexSequence<Indices...>)
+ {
+ int dummy[] = {
+ (ProxyArg<Args>::Clear(env, Get<Indices>(mArgs)), 0)...
+ };
+ mozilla::Unused << dummy;
+ }
+
+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; }
+
+ // 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()() override
+ {
+ JNIEnv* const env = GetEnvForThread();
+ typename ThisArgClass::LocalRef thisArg(env, mThisArg);
+ Call<IsStatic, HasThisArg>(
+ thisArg, typename IndexSequenceFor<Args...>::Type());
+
+ // 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, typename IndexSequenceFor<Args...>::Type());
+ mThisArg.Clear(env);
+ }
+};
+
+template<class Impl, bool HasThisArg, typename... Args>
+struct Dispatcher
+{
+ template<class Traits, bool IsStatic = Traits::isStatic,
+ typename... ProxyArgs>
+ static typename EnableIf<
+ Traits::dispatchTarget == DispatchTarget::PROXY, void>::Type
+ Run(ProxyArgs&&... args)
+ {
+ Impl::OnNativeCall(ProxyNativeCall<
+ Impl, typename Traits::Owner, IsStatic,
+ HasThisArg, Args...>(Forward<ProxyArgs>(args)...));
+ }
+
+ template<class Traits, bool IsStatic = Traits::isStatic,
+ typename ThisArg, typename... ProxyArgs>
+ static typename EnableIf<
+ Traits::dispatchTarget == DispatchTarget::GECKO, void>::Type
+ 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.
+ DispatchToGeckoThread(MakeUnique<ProxyNativeCall<
+ Impl, typename Traits::Owner, IsStatic, HasThisArg,
+ Args...>>(HasThisArg || !IsStatic ? thisArg : nullptr,
+ Forward<ProxyArgs>(args)...));
+ }
+
+ template<class Traits, bool IsStatic = false, typename... ProxyArgs>
+ static typename EnableIf<
+ Traits::dispatchTarget == DispatchTarget::CURRENT, void>::Type
+ Run(ProxyArgs&&... args) {}
+};
+
+} // 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 = mozilla::IsVoid<ReturnType>::value;
+
+ struct VoidType { using JNIType = void; };
+ using ReturnJNIType = typename Conditional<
+ isVoid, VoidType, TypeAdapter<ReturnType>>::Type::JNIType;
+
+ using ReturnTypeForNonVoidInstance = typename Conditional<
+ !isStatic && !isVoid, ReturnType, VoidType>::Type;
+ using ReturnTypeForVoidInstance = typename Conditional<
+ !isStatic && isVoid, ReturnType, VoidType&>::Type;
+ using ReturnTypeForNonVoidStatic = typename Conditional<
+ isStatic && !isVoid, ReturnType, VoidType>::Type;
+ using ReturnTypeForVoidStatic = typename Conditional<
+ isStatic && isVoid, ReturnType, VoidType&>::Type;
+
+ 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);
+
+ Impl* const impl = NativePtr<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);
+
+ Impl* const impl = NativePtr<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;
+ }
+
+ Impl* const impl = NativePtr<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;
+ }
+
+ Impl* const impl = NativePtr<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);
+ (Impl::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<Impl>* ptr)
+ {
+ static_assert(mozilla::IsBaseOf<SupportsWeakPtr<Impl>, Impl>::value,
+ "Attach with UniquePtr&& when not using WeakPtr");
+ return NativePtr<Impl>::Set(instance, static_cast<Impl*>(ptr));
+ }
+
+ static void AttachNative(const typename Cls::LocalRef& instance,
+ UniquePtr<Impl>&& ptr)
+ {
+ static_assert(!mozilla::IsBaseOf<SupportsWeakPtr<Impl>, Impl>::value,
+ "Attach with SupportsWeakPtr* when using WeakPtr");
+ return NativePtr<Impl>::Set(instance, mozilla::Move(ptr));
+ }
+
+ // Get the C++ instance associated with a Java instance.
+ // There is always a pending exception if the return value is nullptr.
+ static Impl* GetNative(const typename Cls::LocalRef& instance) {
+ return NativePtr<Impl>::Get(instance);
+ }
+
+ static void DisposeNative(const typename Cls::LocalRef& instance) {
+ NativePtr<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 000000000..9f54ee5cd
--- /dev/null
+++ b/widget/android/jni/Refs.h
@@ -0,0 +1,953 @@
+#ifndef mozilla_jni_Refs_h__
+#define mozilla_jni_Refs_h__
+
+#include <jni.h>
+
+#include "mozilla/Move.h"
+#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 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;
+
+ // 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;
+ }
+
+ 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);
+ }
+
+ // 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:
+ static jclass RawClassRef()
+ {
+ return sClassRef;
+ }
+
+ 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;
+ }
+
+ bool operator==(const Ref& other) const
+ {
+ // Treat two references of the same object as being the same.
+ return Ref::mInstance == other.mInstance || JNI_FALSE !=
+ mEnv->IsSameObject(Ref::mInstance, other.mInstance);
+ }
+
+ 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);
+ }
+};
+
+
+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 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)
+ {}
+};
+
+
+// Define bindings for built-in types.
+using String = TypedObject<jstring>;
+using Class = TypedObject<jclass>;
+using Throwable = TypedObject<jthrowable>;
+
+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
+
+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(mozilla::Move(ref));
+ return swap(newRef);
+ }
+
+ template<class C>
+ LocalRef<Cls>& operator=(GenericLocalRef<C>&& ref)
+ {
+ LocalRef<Cls> newRef(mozilla::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 DependentRef : public Cls::Ref
+{
+ using Ref = typename Cls::Ref;
+
+public:
+ 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());
+ 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 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 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 jsize len = size_t(Base::Env()->GetArrayLength(Base::Instance()));
+
+ nsTArray<ElementType> array((size_t(len)));
+ array.SetLength(size_t(len));
+ (Base::Env()->*detail::TypeAdapter<ElementType>::GetArray)(
+ Base::Instance(), 0, len,
+ reinterpret_cast<JNIElemType*>(array.Elements()));
+ return array;
+ }
+
+ 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<>
+class TypedObject<jobjectArray>
+ : public ObjectBase<TypedObject<jobjectArray>, jobjectArray>
+{
+ using Base = ObjectBase<TypedObject<jobjectArray>, jobjectArray>;
+
+public:
+ 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 = mozilla::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 = mozilla::Move(objRef));
+ } else if (clsRef) {
+ *globalRef = clsRef;
+ }
+ }
+};
+
+template<class Cls>
+ReturnToGlobal<Cls> ReturnTo(GlobalRef<Cls>* ref)
+{
+ return ReturnToGlobal<Cls>(ref);
+}
+
+} // 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 000000000..a083d3e50
--- /dev/null
+++ b/widget/android/jni/Types.h
@@ -0,0 +1,140 @@
+#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
+
+template<class Cls> constexpr jobject
+ (JNIEnv::*TypeAdapter<LocalRef<Cls>>::Call)(jobject, jmethodID, jvalue*) MOZ_JNICALL_ABI;
+template<class Cls> constexpr jobject
+ (JNIEnv::*TypeAdapter<LocalRef<Cls>>::StaticCall)(jclass, jmethodID, jvalue*) 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 000000000..145f7e9ea
--- /dev/null
+++ b/widget/android/jni/Utils.cpp
@@ -0,0 +1,301 @@
+#include "Utils.h"
+#include "Types.h"
+
+#include <android/log.h>
+#include <pthread.h>
+
+#include "mozilla/Assertions.h"
+
+#include "GeneratedJNIWrappers.h"
+#include "nsAppShell.h"
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
+namespace mozilla {
+namespace jni {
+
+namespace detail {
+
+#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName, ABIName) \
+ \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Call) \
+ (jobject, jmethodID, jvalue*) MOZ_JNICALL_ABI; \
+ constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticCall) \
+ (jclass, jmethodID, jvalue*) 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<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";
+
+
+JNIEnv* sGeckoThreadEnv;
+
+namespace {
+
+JavaVM* sJavaVM;
+pthread_key_t sThreadEnvKey;
+jclass sOOMErrorClass;
+jobject sClassLoader;
+jmethodID sClassLoaderLoadClass;
+bool sIsFennec;
+
+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);
+
+ auto geckoAppClass = Class::LocalRef::Adopt(
+ aEnv->FindClass("org/mozilla/gecko/GeckoApp"));
+ aEnv->ExceptionClear();
+ sIsFennec = !!geckoAppClass;
+}
+
+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;
+
+#ifdef MOZ_CRASHREPORTER
+ result &= NS_SUCCEEDED(CrashReporter::AnnotateCrashReport(
+ NS_LITERAL_CSTRING("JavaStackTrace"),
+ String::Ref::From(aStack)->ToCString()));
+#endif // MOZ_CRASHREPORTER
+
+ 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));
+ 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 && 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) {
+ return classRef.Forget();
+ }
+
+ __android_log_print(
+ ANDROID_LOG_ERROR, "Gecko",
+ ">>> FATAL JNI ERROR! FindClass(className=\"%s\") failed. "
+ "Did ProGuard optimize away something it shouldn't have?",
+ aClassName);
+ aEnv->ExceptionDescribe();
+ MOZ_CRASH("Cannot find JNI class");
+ return nullptr;
+}
+
+void DispatchToGeckoThread(UniquePtr<AbstractCall>&& aCall)
+{
+ class AbstractCallEvent : public nsAppShell::Event
+ {
+ UniquePtr<AbstractCall> mCall;
+
+ public:
+ AbstractCallEvent(UniquePtr<AbstractCall>&& aCall)
+ : mCall(Move(aCall))
+ {}
+
+ void Run() override
+ {
+ (*mCall)();
+ }
+ };
+
+ nsAppShell::PostEvent(MakeUnique<AbstractCallEvent>(Move(aCall)));
+}
+
+bool IsFennec()
+{
+ return sIsFennec;
+}
+
+} // jni
+} // mozilla
diff --git a/widget/android/jni/Utils.h b/widget/android/jni/Utils.h
new file mode 100644
index 000000000..38e0b6b0c
--- /dev/null
+++ b/widget/android/jni/Utils.h
@@ -0,0 +1,147 @@
+#ifndef mozilla_jni_Utils_h__
+#define mozilla_jni_Utils_h__
+
+#include <jni.h>
+
+#include "mozilla/UniquePtr.h"
+
+#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
+#define MOZ_CHECK_JNI
+#endif
+
+#ifdef MOZ_CHECK_JNI
+#include <pthread.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. Method must
+ // return void.
+ GECKO,
+};
+
+
+extern JNIEnv* sGeckoThreadEnv;
+
+inline bool IsAvailable()
+{
+ return !!sGeckoThreadEnv;
+}
+
+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 = ::pthread_equal(::pthread_self(), \
+ ::getJavaUiThread()); \
+ 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);
+
+struct AbstractCall
+{
+ virtual ~AbstractCall() {}
+ virtual void operator()() = 0;
+};
+
+void DispatchToGeckoThread(UniquePtr<AbstractCall>&& aCall);
+
+/**
+ * Returns whether Gecko is running in a Fennec environment, as determined by
+ * the presence of the GeckoApp class.
+ */
+bool IsFennec();
+
+} // jni
+} // 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 000000000..31d7d32e6
--- /dev/null
+++ b/widget/android/jni/moz.build
@@ -0,0 +1,24 @@
+# -*- 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/.
+
+EXPORTS.mozilla.jni += [
+ 'Accessors.h',
+ 'Natives.h',
+ 'Refs.h',
+ 'Types.h',
+ 'Utils.h',
+]
+
+UNIFIED_SOURCES += [
+ 'Utils.cpp',
+]
+
+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 000000000..e80ed01c1
--- /dev/null
+++ b/widget/android/moz.build
@@ -0,0 +1,73 @@
+# -*- 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 += [
+ 'bindings',
+ 'fennec',
+ 'jni',
+]
+
+XPIDL_SOURCES += [
+ 'nsIAndroidBridge.idl',
+]
+
+XPIDL_MODULE = 'widget_android'
+
+EXPORTS += [
+ 'AndroidBridge.h',
+ 'AndroidJavaWrappers.h',
+ 'AndroidJNIWrapper.h',
+ 'GeneratedJNINatives.h',
+ 'GeneratedJNIWrappers.h',
+]
+
+EXPORTS.mozilla.widget += [
+ 'AndroidCompositorWidget.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AndroidAlerts.cpp',
+ 'AndroidBridge.cpp',
+ 'AndroidCompositorWidget.cpp',
+ 'AndroidContentController.cpp',
+ 'AndroidJavaWrappers.cpp',
+ 'AndroidJNI.cpp',
+ 'AndroidJNIWrapper.cpp',
+ 'ANRReporter.cpp',
+ 'GeneratedJNIWrappers.cpp',
+ 'GfxInfo.cpp',
+ 'NativeJSContainer.cpp',
+ 'nsAndroidProtocolHandler.cpp',
+ 'nsAppShell.cpp',
+ 'nsClipboard.cpp',
+ 'nsDeviceContextAndroid.cpp',
+ 'nsIdleServiceAndroid.cpp',
+ 'nsLookAndFeel.cpp',
+ 'nsPrintOptionsAndroid.cpp',
+ 'nsScreenManagerAndroid.cpp',
+ 'nsWidgetFactory.cpp',
+ 'nsWindow.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/docshell/base',
+ '/dom/base',
+ '/dom/system/android',
+ '/netwerk/base',
+ '/netwerk/cache',
+ '/widget',
+]
+
+CXXFLAGS += ['-Wno-error=shadow']
+
+OS_LIBS += ['android']
+
+#DEFINES['DEBUG_WIDGETS'] = True
+
diff --git a/widget/android/nsAndroidProtocolHandler.cpp b/widget/android/nsAndroidProtocolHandler.cpp
new file mode 100644
index 000000000..e59a5d5cf
--- /dev/null
+++ b/widget/android/nsAndroidProtocolHandler.cpp
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 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 "nsIIOService.h"
+#include "nsIStandardURL.h"
+#include "nsIURL.h"
+#include "android/log.h"
+#include "nsBaseChannel.h"
+#include "AndroidBridge.h"
+#include "GeneratedJNIWrappers.h"
+
+using namespace mozilla;
+
+class AndroidInputStream : public nsIInputStream
+{
+public:
+ 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::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIStandardURL> surl(do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = surl->Init(nsIStandardURL::URLTYPE_STANDARD, -1, aSpec, aCharset, aBaseURI);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(surl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ surl->SetMutable(false);
+
+ NS_ADDREF(*result = url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::NewChannel2(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;
+}
+
+NS_IMETHODIMP
+nsAndroidProtocolHandler::NewChannel(nsIURI* aURI,
+ nsIChannel* *aResult)
+{
+ return NewChannel2(aURI, nullptr, aResult);
+}
diff --git a/widget/android/nsAndroidProtocolHandler.h b/widget/android/nsAndroidProtocolHandler.h
new file mode 100644
index 000000000..11705dd0a
--- /dev/null
+++ b/widget/android/nsAndroidProtocolHandler.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 000000000..fefd711d0
--- /dev/null
+++ b/widget/android/nsAppShell.cpp
@@ -0,0 +1,688 @@
+/* -*- Mode: c++; tab-width: 40; 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 "nsAppShell.h"
+
+#include "base/basictypes.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "mozilla/Hal.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "nsWindow.h"
+#include "nsThreadUtils.h"
+#include "nsICommandLineRunner.h"
+#include "nsIObserverService.h"
+#include "nsIAppStartup.h"
+#include "nsIGeolocationProvider.h"
+#include "nsCacheService.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMClientRectList.h"
+#include "nsIDOMClientRect.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIURIFixup.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsCDefaultURIFixup.h"
+#include "nsToolkitCompsCID.h"
+#include "nsGeoPosition.h"
+
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Hal.h"
+#include "prenv.h"
+
+#include "AndroidBridge.h"
+#include "AndroidBridgeUtilities.h"
+#include "GeneratedJNINatives.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
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsICrashReporter.h"
+#include "nsExceptionHandler.h"
+#endif
+
+#include "AndroidAlerts.h"
+#include "ANRReporter.h"
+#include "GeckoBatteryManager.h"
+#include "GeckoNetworkManager.h"
+#include "GeckoScreenOrientation.h"
+#include "PrefsHelper.h"
+#include "fennec/MemoryMonitor.h"
+#include "fennec/Telemetry.h"
+#include "fennec/ThumbnailHelper.h"
+
+#ifdef DEBUG_ANDROID_EVENTS
+#define EVLOG(args...) ALOG(args)
+#else
+#define EVLOG(args...) do { } while (0)
+#endif
+
+using namespace mozilla;
+
+nsIGeolocationUpdate *gLocationCallback = nullptr;
+
+nsAppShell* nsAppShell::sAppShell;
+StaticAutoPtr<Mutex> nsAppShell::sAppShellLock;
+
+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;
+ }
+ specConn->SpeculativeConnect(uri, nullptr);
+ }
+
+ static void WaitOnGecko()
+ {
+ struct NoOpEvent : nsAppShell::Event {
+ void Run() override {}
+ };
+ nsAppShell::SyncRunEvent(NoOpEvent());
+ }
+
+ 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);
+
+ NS_NAMED_LITERAL_STRING(minimize, "heap-minimize");
+ obsServ->NotifyObservers(nullptr, "memory-pressure", minimize.get());
+
+ // 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
+ nsIPrefService* prefs = Preferences::GetService();
+ if (prefs) {
+ prefs->SavePrefFile(nullptr);
+ }
+ }
+
+ 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()
+ {
+ if (!AndroidBridge::Bridge()) {
+ return -1;
+ }
+
+ return AndroidBridge::Bridge()->RunDelayedUiThreadTasks();
+ }
+};
+
+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 SyncNotifyObservers(jni::String::Param aTopic,
+ jni::String::Param aData)
+ {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NotifyObservers(aTopic, aData);
+ }
+
+ 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 OnSensorChanged(int32_t aType, float aX, float aY, float aZ,
+ float aW, int32_t aAccuracy, 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::SensorAccuracyType(aAccuracy));
+ hal::NotifySensorChange(sdata);
+ }
+
+ static void OnLocationChanged(double aLatitude, double aLongitude,
+ double aAltitude, float aAccuracy,
+ float aBearing, float aSpeed, int64_t aTime)
+ {
+ if (!gLocationCallback) {
+ return;
+ }
+
+ RefPtr<nsIDOMGeoPosition> geoPosition(
+ new nsGeoPosition(aLatitude, aLongitude, aAltitude, aAccuracy,
+ aAccuracy, aBearing, aSpeed, aTime));
+ gLocationCallback->Update(geoPosition);
+ }
+
+ static void NotifyUriVisited(jni::String::Param aUri)
+ {
+#ifdef MOZ_ANDROID_HISTORY
+ nsCOMPtr<IHistory> history = services::GetHistoryService();
+ nsCOMPtr<nsIURI> visitedURI;
+ if (history &&
+ NS_SUCCEEDED(NS_NewURI(getter_AddRefs(visitedURI),
+ aUri->ToString()))) {
+ history->NotifyVisited(visitedURI);
+ }
+#endif
+ }
+
+ static void NotifyAlertListener(jni::String::Param aName,
+ jni::String::Param aTopic,
+ jni::String::Param aCookie)
+ {
+ if (!aName || !aTopic || !aCookie) {
+ return;
+ }
+
+ AndroidAlerts::NotifyListener(
+ aName->ToString(), aTopic->ToCString().get(),
+ aCookie->ToString().get());
+ }
+
+ static void OnFullScreenPluginHidden(jni::Object::Param aView)
+ {
+ nsPluginInstanceOwner::ExitFullScreen(aView.Get());
+ }
+};
+
+nsAppShell::nsAppShell()
+ : mSyncRunFinished(*(sAppShellLock = new Mutex("nsAppShell")),
+ "nsAppShell.SyncRun")
+ , mSyncRunQuit(false)
+{
+ {
+ MutexAutoLock lock(*sAppShellLock);
+ sAppShell = this;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (jni::IsAvailable()) {
+ // Initialize JNI and Set the corresponding state in GeckoThread.
+ AndroidBridge::ConstructBridge();
+ GeckoAppShellSupport::Init();
+ GeckoThreadSupport::Init();
+ mozilla::GeckoBatteryManager::Init();
+ mozilla::GeckoNetworkManager::Init();
+ mozilla::GeckoScreenOrientation::Init();
+ mozilla::PrefsHelper::Init();
+ nsWindow::InitNatives();
+
+ if (jni::IsFennec()) {
+ mozilla::ANRReporter::Init();
+ mozilla::MemoryMonitor::Init();
+ mozilla::widget::Telemetry::Init();
+ mozilla::ThumbnailHelper::Init();
+ }
+
+ java::GeckoThread::SetState(java::GeckoThread::State::JNI_READY());
+ }
+
+ sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+
+ if (sPowerManagerService) {
+ sWakeLockListener = new WakeLockListener();
+ } else {
+ NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+nsAppShell::~nsAppShell()
+{
+ {
+ MutexAutoLock lock(*sAppShellLock);
+ sAppShell = nullptr;
+ }
+
+ while (mEventQueue.Pop(/* mayWait */ false)) {
+ NS_WARNING("Discarded event on shutdown");
+ }
+
+ if (sPowerManagerService) {
+ sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
+
+ sPowerManagerService = nullptr;
+ sWakeLockListener = nullptr;
+ }
+
+ if (jni::IsAvailable()) {
+ AndroidBridge::DeconstructBridge();
+ }
+}
+
+void
+nsAppShell::NotifyNativeEvent()
+{
+ mEventQueue.Signal();
+}
+
+#define PREFNAME_COALESCE_TOUCHES "dom.event.touch.coalescing.enabled"
+static const char* kObservedPrefs[] = {
+ PREFNAME_COALESCE_TOUCHES,
+ nullptr
+};
+
+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, "profile-after-change", false);
+ obsServ->AddObserver(this, "chrome-document-loaded", false);
+ obsServ->AddObserver(this, "quit-application-granted", false);
+ obsServ->AddObserver(this, "xpcom-shutdown", false);
+ }
+
+ if (sPowerManagerService)
+ sPowerManagerService->AddWakeLockListener(sWakeLockListener);
+
+ Preferences::AddStrongObservers(this, kObservedPrefs);
+ mAllowCoalescingTouches = Preferences::GetBool(PREFNAME_COALESCE_TOUCHES, true);
+ 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, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) &&
+ aData &&
+ nsDependentString(aData).Equals(NS_LITERAL_STRING(PREFNAME_COALESCE_TOUCHES))) {
+ mAllowCoalescingTouches = Preferences::GetBool(PREFNAME_COALESCE_TOUCHES, true);
+ return NS_OK;
+
+ } else if (!strcmp(aTopic, "browser-delayed-startup-finished")) {
+ NS_CreateServicesFromCategory("browser-delayed-startup-finished", nullptr,
+ "browser-delayed-startup-finished");
+
+ } else if (!strcmp(aTopic, "profile-after-change")) {
+ if (jni::IsAvailable()) {
+ // See if we want to force 16-bit color before doing anything
+ if (Preferences::GetBool("gfx.android.rgb16.force", false)) {
+ java::GeckoAppShell::SetScreenDepthOverride(16);
+ }
+
+ 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 =
+ do_GetService(NS_APPSTARTUP_CONTRACTID);
+ if (appStartup) {
+ appStartup->EnterLastWindowClosingSurvivalArea();
+ }
+ }
+ removeObserver = true;
+
+ } else if (!strcmp(aTopic, "chrome-document-loaded")) {
+ if (jni::IsAvailable()) {
+ // Our first window has loaded, assume any JS initialization has run.
+ java::GeckoThread::CheckAndSetState(
+ java::GeckoThread::State::PROFILE_READY(),
+ java::GeckoThread::State::RUNNING());
+ }
+ removeObserver = true;
+
+ } else if (!strcmp(aTopic, "quit-application-granted")) {
+ if (jni::IsAvailable()) {
+ java::GeckoThread::SetState(
+ java::GeckoThread::State::EXITING());
+
+ // 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 =
+ do_GetService(NS_APPSTARTUP_CONTRACTID);
+ if (appStartup) {
+ appStartup->ExitLastWindowClosingSurvivalArea();
+ }
+ }
+ removeObserver = true;
+
+ } else if (!strcmp(aTopic, "nsPref:changed")) {
+ if (jni::IsAvailable()) {
+ mozilla::PrefsHelper::OnPrefChange(aData);
+ }
+ }
+
+ 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);
+
+ PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent",
+ js::ProfileEntry::Category::EVENTS);
+
+ 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() &&
+ AndroidBridge::Bridge()->PumpMessageLoop()) {
+ return true;
+ }
+
+ PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent::Wait",
+ js::ProfileEntry::Category::EVENTS);
+ mozilla::HangMonitor::Suspend();
+
+ curEvent = mEventQueue.Pop(/* mayWait */ true);
+ }
+ }
+
+ if (!curEvent)
+ return false;
+
+ mozilla::HangMonitor::NotifyActivity(curEvent->ActivityType());
+
+ curEvent->Run();
+ return true;
+}
+
+void
+nsAppShell::SyncRunEvent(Event&& event,
+ UniquePtr<Event>(*eventFactory)(UniquePtr<Event>&&))
+{
+ // 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;
+ }
+
+ bool finished = false;
+ auto runAndNotify = [&event, &finished] {
+ mozilla::MutexAutoLock shellLock(*sAppShellLock);
+ nsAppShell* const appShell = sAppShell;
+ if (MOZ_UNLIKELY(!appShell || appShell->mSyncRunQuit)) {
+ return;
+ }
+ event.Run();
+ finished = true;
+ appShell->mSyncRunFinished.NotifyAll();
+ };
+
+ UniquePtr<Event> runAndNotifyEvent = mozilla::MakeUnique<
+ LambdaEvent<decltype(runAndNotify)>>(mozilla::Move(runAndNotify));
+
+ if (eventFactory) {
+ runAndNotifyEvent = (*eventFactory)(mozilla::Move(runAndNotifyEvent));
+ }
+
+ appShell->mEventQueue.Post(mozilla::Move(runAndNotifyEvent));
+
+ while (!finished && MOZ_LIKELY(sAppShell && !sAppShell->mSyncRunQuit)) {
+ appShell->mSyncRunFinished.Wait();
+ }
+}
+
+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 = do_GetService(NS_URIFIXUP_CONTRACTID);
+ if (fixup && NS_SUCCEEDED(
+ fixup->CreateFixupURI(aUriStr, 0, nullptr, 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();
+}
+
+}
diff --git a/widget/android/nsAppShell.h b/widget/android/nsAppShell.h
new file mode 100644
index 000000000..42453999d
--- /dev/null
+++ b/widget/android/nsAppShell.h
@@ -0,0 +1,223 @@
+/* -*- Mode: c++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAppShell_h__
+#define nsAppShell_h__
+
+#include "mozilla/HangMonitor.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Move.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/jni/Natives.h"
+#include "nsBaseAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIAndroidBridge.h"
+
+namespace mozilla {
+bool ProcessNextEvent();
+void NotifyEvent();
+}
+
+class nsWindow;
+
+class nsAppShell :
+ public nsBaseAppShell
+{
+public:
+ struct Event : mozilla::LinkedListElement<Event>
+ {
+ typedef mozilla::HangMonitor::ActivityType Type;
+
+ 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 Type ActivityType() const
+ {
+ return Type::kGeneralActivity;
+ }
+ };
+
+ template<typename T>
+ class LambdaEvent : public Event
+ {
+ protected:
+ T lambda;
+
+ public:
+ LambdaEvent(T&& l) : lambda(mozilla::Move(l)) {}
+ void Run() override { return lambda(); }
+ };
+
+ class ProxyEvent : public Event
+ {
+ protected:
+ mozilla::UniquePtr<Event> baseEvent;
+
+ public:
+ ProxyEvent(mozilla::UniquePtr<Event>&& event)
+ : baseEvent(mozilla::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(mozilla::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>>(
+ mozilla::Move(lambda)));
+ }
+
+ // Post a event and wait for it to finish running on the Gecko thread.
+ static void SyncRunEvent(Event&& event,
+ mozilla::UniquePtr<Event>(*eventFactory)(
+ mozilla::UniquePtr<Event>&&) = nullptr);
+
+ 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;
+
+ 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 detroyed, this lambda will not be called either.
+ nsAppShell* const appShell;
+
+ public:
+ 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:
+ 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()) {
+ // Ownership of event object transfers to the queue.
+ mozilla::Unused << event.release();
+ }
+ lock.NotifyAll();
+ }
+
+ mozilla::UniquePtr<Event> Pop(bool mayWait)
+ {
+ mozilla::MonitorAutoLock lock(mMonitor);
+
+ if (mayWait && mQueue.isEmpty()) {
+ lock.Wait();
+ }
+ // Ownership of event object transfers to the return value.
+ return mozilla::UniquePtr<Event>(mQueue.popFirst());
+ }
+
+ } mEventQueue;
+
+ mozilla::CondVar mSyncRunFinished;
+ bool mSyncRunQuit;
+
+ bool mAllowCoalescingTouches;
+
+ 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 000000000..5d70ae16b
--- /dev/null
+++ b/widget/android/nsClipboard.cpp
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ContentChild.h"
+#include "nsClipboard.h"
+#include "nsISupportsPrimitives.h"
+#include "AndroidBridge.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+using mozilla::dom::ContentChild;
+
+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;
+
+ nsCOMPtr<nsISupports> tmp;
+ uint32_t len;
+ nsresult rv = aTransferable->GetTransferData(kUnicodeMime, getter_AddRefs(tmp),
+ &len);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(tmp);
+ // No support for non-text data
+ NS_ENSURE_TRUE(supportsString, NS_ERROR_NOT_IMPLEMENTED);
+ nsAutoString buffer;
+ supportsString->GetData(buffer);
+
+ java::Clipboard::SetText(buffer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
+{
+ if (aWhichClipboard != kGlobalClipboard)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsAutoString buffer;
+ if (!AndroidBridge::Bridge())
+ return NS_ERROR_NOT_IMPLEMENTED;
+ if (!AndroidBridge::Bridge()->GetClipboardText(buffer))
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dataWrapper->SetData(buffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If our data flavor has already been added, this will fail. But we don't care
+ aTransferable->AddDataFlavor(kUnicodeMime);
+
+ nsCOMPtr<nsISupports> nsisupportsDataWrapper =
+ do_QueryInterface(dataWrapper);
+ rv = aTransferable->SetTransferData(kUnicodeMime, nsisupportsDataWrapper,
+ buffer.Length() * sizeof(char16_t));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard != kGlobalClipboard)
+ return NS_ERROR_NOT_IMPLEMENTED;
+ java::Clipboard::ClearText();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const char **aFlavorList,
+ uint32_t aLength, int32_t aWhichClipboard,
+ bool *aHasText)
+{
+ *aHasText = false;
+ if (aWhichClipboard != kGlobalClipboard)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ for (uint32_t k = 0; k < aLength; k++) {
+ if (strcmp(aFlavorList[k], kUnicodeMime) == 0) {
+ *aHasText = java::Clipboard::HasText();
+ break;
+ }
+ }
+
+ 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 000000000..657a8dea8
--- /dev/null
+++ b/widget/android/nsClipboard.h
@@ -0,0 +1,23 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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 000000000..4c952957e
--- /dev/null
+++ b/widget/android/nsDeviceContextAndroid.cpp
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeviceContextAndroid.h"
+
+#include "mozilla/gfx/PrintTargetPDF.h"
+#include "mozilla/RefPtr.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()
+{
+ nsXPIDLString targetPath;
+ nsCOMPtr<nsIFile> destFile;
+ mPrintSettings->GetToFileName(getter_Copies(targetPath));
+
+ nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(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 000000000..7abe38ca4
--- /dev/null
+++ b/widget/android/nsDeviceContextAndroid.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/. */
+#include "nsIDeviceContextSpec.h"
+#include "nsCOMPtr.h"
+
+class nsDeviceContextSpecAndroid final : public nsIDeviceContextSpec
+{
+private:
+ ~nsDeviceContextSpecAndroid() {}
+
+public:
+ NS_DECL_ISUPPORTS
+
+ virtual 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;
+};
diff --git a/widget/android/nsIAndroidBridge.idl b/widget/android/nsIAndroidBridge.idl
new file mode 100644
index 000000000..91b1a3d52
--- /dev/null
+++ b/widget/android/nsIAndroidBridge.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 "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+[scriptable, uuid(e8420a7b-659b-4325-968b-a114a6a067aa)]
+interface nsIBrowserTab : nsISupports {
+ readonly attribute mozIDOMWindowProxy window;
+ readonly attribute float scale;
+};
+
+[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(1beb70d3-70f3-4742-98cc-a3d301b26c0c)]
+interface nsIAndroidBridge : nsISupports
+{
+ [implicit_jscontext] void handleGeckoMessage(in jsval message);
+ attribute nsIAndroidBrowserApp browserApp;
+ void contentDocumentChanged(in mozIDOMWindowProxy window);
+ boolean isContentDocumentDisplayed(in mozIDOMWindowProxy window);
+};
diff --git a/widget/android/nsIdleServiceAndroid.cpp b/widget/android/nsIdleServiceAndroid.cpp
new file mode 100644
index 000000000..1992d8043
--- /dev/null
+++ b/widget/android/nsIdleServiceAndroid.cpp
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIdleServiceAndroid.h"
+#include "nsIServiceManager.h"
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceAndroid, nsIdleService)
+
+bool
+nsIdleServiceAndroid::PollIdleTime(uint32_t *aIdleTime)
+{
+ return false;
+}
+
+bool
+nsIdleServiceAndroid::UsePollMode()
+{
+ return false;
+}
diff --git a/widget/android/nsIdleServiceAndroid.h b/widget/android/nsIdleServiceAndroid.h
new file mode 100644
index 000000000..28bca0553
--- /dev/null
+++ b/widget/android/nsIdleServiceAndroid.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIdleServiceAndroid_h__
+#define nsIdleServiceAndroid_h__
+
+#include "nsIdleService.h"
+
+class nsIdleServiceAndroid : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsIdleServiceAndroid> GetInstance()
+ {
+ RefPtr<nsIdleService> idleService = nsIdleService::GetInstance();
+ if (!idleService) {
+ idleService = new nsIdleServiceAndroid();
+ }
+
+ return idleService.forget().downcast<nsIdleServiceAndroid>();
+ }
+
+protected:
+ nsIdleServiceAndroid() { }
+ virtual ~nsIdleServiceAndroid() { }
+ bool UsePollMode() override;
+};
+
+#endif // nsIdleServiceAndroid_h__
diff --git a/widget/android/nsLookAndFeel.cpp b/widget/android/nsLookAndFeel.cpp
new file mode 100644
index 000000000..770b52a24
--- /dev/null
+++ b/widget/android/nsLookAndFeel.cpp
@@ -0,0 +1,500 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ContentChild.h"
+#include "nsStyleConsts.h"
+#include "nsXULAppAPI.h"
+#include "nsLookAndFeel.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+using mozilla::dom::ContentChild;
+
+bool nsLookAndFeel::mInitializedSystemColors = false;
+AndroidSystemColors nsLookAndFeel::mSystemColors;
+
+bool nsLookAndFeel::mInitializedShowPassword = false;
+bool nsLookAndFeel::mShowPassword = true;
+
+static const char16_t UNICODE_BULLET = 0x2022;
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel()
+{
+}
+
+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 (mInitializedSystemColors)
+ return NS_OK;
+
+ if (!AndroidBridge::Bridge())
+ return NS_ERROR_FAILURE;
+
+ AndroidBridge::Bridge()->GetSystemColors(&mSystemColors);
+
+ mInitializedSystemColors = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsLookAndFeel::CallRemoteGetSystemColors()
+{
+ // An array has to be used to get data from remote process
+ InfallibleTArray<uint32_t> colors;
+ uint32_t colorsCount = sizeof(AndroidSystemColors) / sizeof(nscolor);
+
+ if (!ContentChild::GetSingleton()->SendGetSystemColors(colorsCount, &colors))
+ return NS_ERROR_FAILURE;
+
+ NS_ASSERTION(colors.Length() == colorsCount, "System colors array is incomplete");
+ if (colors.Length() == 0)
+ return NS_ERROR_FAILURE;
+
+ if (colors.Length() < colorsCount)
+ colorsCount = colors.Length();
+
+ // Array elements correspond to the members of mSystemColors structure,
+ // so just copy the memory block
+ memcpy(&mSystemColors, colors.Elements(), sizeof(nscolor) * colorsCount);
+
+ mInitializedSystemColors = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
+{
+ nsresult rv = NS_OK;
+
+ if (!mInitializedSystemColors) {
+ if (XRE_IsParentProcess())
+ rv = GetSystemColors();
+ else
+ rv = CallRemoteGetSystemColors();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // 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 eColorID_WindowBackground:
+ aColor = NS_RGB(0xFF, 0xFF, 0xFF);
+ break;
+ case eColorID_WindowForeground:
+ aColor = mSystemColors.textColorPrimary;
+ break;
+ case eColorID_WidgetBackground:
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_WidgetForeground:
+ aColor = mSystemColors.colorForeground;
+ break;
+ case eColorID_WidgetSelectBackground:
+ aColor = mSystemColors.textColorHighlight;
+ break;
+ case eColorID_WidgetSelectForeground:
+ aColor = mSystemColors.textColorPrimaryInverse;
+ break;
+ case eColorID_Widget3DHighlight:
+ aColor = LIGHT_GRAY_COLOR;
+ break;
+ case eColorID_Widget3DShadow:
+ aColor = DARK_GRAY_COLOR;
+ break;
+ case eColorID_TextBackground:
+ // not used?
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_TextForeground:
+ // not used?
+ aColor = mSystemColors.textColorPrimary;
+ break;
+ case eColorID_TextSelectBackground:
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ // still used
+ aColor = mSystemColors.textColorHighlight;
+ break;
+ case eColorID_TextSelectForeground:
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ // still used
+ aColor = mSystemColors.textColorPrimaryInverse;
+ break;
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_SpellCheckerUnderline:
+ aColor = RED_COLOR;
+ break;
+
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case eColorID_activeborder:
+ // active window border
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_activecaption:
+ // active window caption background
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_appworkspace:
+ // MDI background color
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_background:
+ // desktop background
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_captiontext:
+ // text in active window caption, size box, and scrollbar arrow box (!)
+ aColor = mSystemColors.colorForeground;
+ break;
+ case eColorID_graytext:
+ // disabled text in windows, menus, etc.
+ aColor = mSystemColors.textColorTertiary;
+ break;
+ case eColorID_highlight:
+ // background of selected item
+ aColor = mSystemColors.textColorHighlight;
+ break;
+ case eColorID_highlighttext:
+ // text of selected item
+ aColor = mSystemColors.textColorPrimaryInverse;
+ break;
+ case eColorID_inactiveborder:
+ // inactive window border
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_inactivecaption:
+ // inactive window caption
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_inactivecaptiontext:
+ // text in inactive window caption
+ aColor = mSystemColors.textColorTertiary;
+ break;
+ case eColorID_infobackground:
+ // tooltip background color
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_infotext:
+ // tooltip text color
+ aColor = mSystemColors.colorForeground;
+ break;
+ case eColorID_menu:
+ // menu background
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID_menutext:
+ // menu text
+ aColor = mSystemColors.colorForeground;
+ break;
+ case eColorID_scrollbar:
+ // scrollbar gray area
+ aColor = mSystemColors.colorBackground;
+ break;
+
+ case eColorID_threedface:
+ case eColorID_buttonface:
+ // 3-D face color
+ aColor = mSystemColors.colorBackground;
+ break;
+
+ case eColorID_buttontext:
+ // text on push buttons
+ aColor = mSystemColors.colorForeground;
+ break;
+
+ case eColorID_buttonhighlight:
+ // 3-D highlighted edge color
+ case eColorID_threedhighlight:
+ // 3-D highlighted outer edge color
+ aColor = LIGHT_GRAY_COLOR;
+ break;
+
+ case eColorID_threedlightshadow:
+ // 3-D highlighted inner edge color
+ aColor = mSystemColors.colorBackground;
+ break;
+
+ case eColorID_buttonshadow:
+ // 3-D shadow edge color
+ case eColorID_threedshadow:
+ // 3-D shadow inner edge color
+ aColor = GRAY_COLOR;
+ break;
+
+ case eColorID_threeddarkshadow:
+ // 3-D shadow outer edge color
+ aColor = BLACK_COLOR;
+ break;
+
+ case eColorID_window:
+ case eColorID_windowframe:
+ aColor = mSystemColors.colorBackground;
+ break;
+
+ case eColorID_windowtext:
+ aColor = mSystemColors.textColorPrimary;
+ break;
+
+ case eColorID__moz_eventreerow:
+ case eColorID__moz_field:
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID__moz_fieldtext:
+ aColor = mSystemColors.textColorPrimary;
+ break;
+ case eColorID__moz_dialog:
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID__moz_dialogtext:
+ aColor = mSystemColors.colorForeground;
+ break;
+ case eColorID__moz_dragtargetzone:
+ aColor = mSystemColors.textColorHighlight;
+ break;
+ case eColorID__moz_buttondefault:
+ // default button border color
+ aColor = BLACK_COLOR;
+ break;
+ case eColorID__moz_buttonhoverface:
+ aColor = BG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_buttonhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ aColor = mSystemColors.textColorHighlight;
+ break;
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aColor = mSystemColors.textColorPrimaryInverse;
+ break;
+ case eColorID__moz_menuhover:
+ aColor = BG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_menuhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_oddtreerow:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID__moz_comboboxtext:
+ aColor = mSystemColors.colorForeground;
+ break;
+ case eColorID__moz_combobox:
+ aColor = mSystemColors.colorBackground;
+ break;
+ case eColorID__moz_menubartext:
+ aColor = mSystemColors.colorForeground;
+ break;
+ case eColorID__moz_menubarhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ default:
+ /* default color is BLACK */
+ aColor = 0;
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return rv;
+}
+
+
+nsresult
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ nsresult rv = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ rv = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ aResult = 500;
+ break;
+
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+
+ case eIntID_SubmenuDelay:
+ aResult = 200;
+ break;
+
+ case eIntID_TooltipDelay:
+ aResult = 500;
+ break;
+
+ case eIntID_MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+
+ case eIntID_ScrollArrowStyle:
+ aResult = eScrollArrowStyle_Single;
+ break;
+
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+
+ case eIntID_TouchEnabled:
+ aResult = 1;
+ break;
+
+ case eIntID_ColorPickerAvailable:
+ aResult = 1;
+ break;
+
+ case eIntID_WindowsDefaultTheme:
+ case eIntID_WindowsThemeIdentifier:
+ case eIntID_OperatingSystemVersionIdentifier:
+ aResult = 0;
+ rv = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+
+ case eIntID_ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+
+ case eIntID_ContextMenuOffsetVertical:
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+
+ default:
+ aResult = 0;
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult rv = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ rv = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+
+ default:
+ aResult = -1.0;
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ return rv;
+}
+
+/*virtual*/
+bool
+nsLookAndFeel::GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ aFontName.AssignLiteral("\"Droid Sans\"");
+ aFontStyle.style = NS_FONT_STYLE_NORMAL;
+ aFontStyle.weight = NS_FONT_WEIGHT_NORMAL;
+ aFontStyle.stretch = NS_FONT_STRETCH_NORMAL;
+ aFontStyle.size = 9.0 * 96.0f / 72.0f * aDevPixPerCSSPixel;
+ aFontStyle.systemFont = true;
+ return true;
+}
+
+/*virtual*/
+bool
+nsLookAndFeel::GetEchoPasswordImpl()
+{
+ if (!mInitializedShowPassword) {
+ if (XRE_IsParentProcess()) {
+ mShowPassword = java::GeckoAppShell::GetShowPasswordSetting();
+ } else {
+ ContentChild::GetSingleton()->SendGetShowPasswordSetting(&mShowPassword);
+ }
+ mInitializedShowPassword = true;
+ }
+ 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;
+}
diff --git a/widget/android/nsLookAndFeel.h b/widget/android/nsLookAndFeel.h
new file mode 100644
index 000000000..9b33279a8
--- /dev/null
+++ b/widget/android/nsLookAndFeel.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "nsXPLookAndFeel.h"
+#include "AndroidBridge.h"
+
+class nsLookAndFeel: public nsXPLookAndFeel
+{
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aName, gfxFontStyle& aStyle,
+ float aDevPixPerCSSPixel);
+ virtual bool GetEchoPasswordImpl();
+ virtual uint32_t GetPasswordMaskDelayImpl();
+ virtual char16_t GetPasswordCharacterImpl();
+
+protected:
+ static bool mInitializedSystemColors;
+ static mozilla::AndroidSystemColors mSystemColors;
+ static bool mInitializedShowPassword;
+ static bool mShowPassword;
+
+ nsresult GetSystemColors();
+ nsresult CallRemoteGetSystemColors();
+};
+
+#endif
diff --git a/widget/android/nsPrintOptionsAndroid.cpp b/widget/android/nsPrintOptionsAndroid.cpp
new file mode 100644
index 000000000..03afba827
--- /dev/null
+++ b/widget/android/nsPrintOptionsAndroid.cpp
@@ -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 "nsPrintOptionsAndroid.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");
+
+ }
+};
+
+nsPrintOptionsAndroid::nsPrintOptionsAndroid()
+{
+}
+
+nsPrintOptionsAndroid::~nsPrintOptionsAndroid()
+{
+}
+
+nsresult
+nsPrintOptionsAndroid::_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;
+}
diff --git a/widget/android/nsPrintOptionsAndroid.h b/widget/android/nsPrintOptionsAndroid.h
new file mode 100644
index 000000000..27c4cce83
--- /dev/null
+++ b/widget/android/nsPrintOptionsAndroid.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 nsPrintOptionsAndroid_h__
+#define nsPrintOptionsAndroid_h__
+
+#include "nsPrintOptionsImpl.h"
+#include "nsIPrintSettings.h"
+
+//*****************************************************************************
+//*** nsPrintOptions
+//*****************************************************************************
+class nsPrintOptionsAndroid : public nsPrintOptions
+{
+public:
+ nsPrintOptionsAndroid();
+ virtual ~nsPrintOptionsAndroid();
+
+ nsresult _CreatePrintSettings(nsIPrintSettings** _retval) override;
+};
+
+#endif /* nsPrintOptionsAndroid_h__ */
diff --git a/widget/android/nsScreenManagerAndroid.cpp b/widget/android/nsScreenManagerAndroid.cpp
new file mode 100644
index 000000000..4a79b9dab
--- /dev/null
+++ b/widget/android/nsScreenManagerAndroid.cpp
@@ -0,0 +1,264 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set sw=4 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/. */
+
+#define MOZ_FATAL_ASSERTIONS_FOR_THREAD_SAFETY
+
+#include "mozilla/SyncRunnable.h"
+#include "nsScreenManagerAndroid.h"
+#include "nsServiceManagerUtils.h"
+#include "AndroidRect.h"
+#include "FennecJNINatives.h"
+#include "nsAppShell.h"
+#include "nsThreadUtils.h"
+
+#include <android/log.h>
+#include <mozilla/jni/Refs.h>
+
+#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "nsScreenManagerAndroid", ## args)
+
+using namespace mozilla;
+using namespace mozilla::java;
+
+static uint32_t sScreenId = 0;
+const uint32_t PRIMARY_SCREEN_ID = 0;
+
+nsScreenAndroid::nsScreenAndroid(DisplayType aDisplayType, nsIntRect aRect)
+ : mId(sScreenId++)
+ , mDisplayType(aDisplayType)
+ , mRect(aRect)
+ , mDensity(0.0)
+{
+ // ensure that the ID of the primary screen would be PRIMARY_SCREEN_ID.
+ if (mDisplayType == DisplayType::DISPLAY_PRIMARY) {
+ mId = PRIMARY_SCREEN_ID;
+ }
+}
+
+nsScreenAndroid::~nsScreenAndroid()
+{
+}
+
+float
+nsScreenAndroid::GetDensity() {
+ if (mDensity != 0.0) {
+ return mDensity;
+ }
+ if (mDisplayType == DisplayType::DISPLAY_PRIMARY) {
+ mDensity = mozilla::jni::IsAvailable() ? GeckoAppShell::GetDensity()
+ : 1.0; // xpcshell most likely
+ return mDensity;
+ }
+ return 1.0;
+}
+
+NS_IMETHODIMP
+nsScreenAndroid::GetId(uint32_t *outId)
+{
+ *outId = mId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenAndroid::GetRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ if (mDisplayType != DisplayType::DISPLAY_PRIMARY) {
+ *outLeft = mRect.x;
+ *outTop = mRect.y;
+ *outWidth = mRect.width;
+ *outHeight = mRect.height;
+
+ return NS_OK;
+ }
+
+ if (!mozilla::jni::IsAvailable()) {
+ // xpcshell most likely
+ *outLeft = *outTop = *outWidth = *outHeight = 0;
+ return NS_ERROR_FAILURE;
+ }
+
+ java::sdk::Rect::LocalRef rect = java::GeckoAppShell::GetScreenSize();
+ rect->Left(outLeft);
+ rect->Top(outTop);
+ rect->Width(outWidth);
+ rect->Height(outHeight);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsScreenAndroid::GetAvailRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ return GetRect(outLeft, outTop, outWidth, outHeight);
+}
+
+
+
+NS_IMETHODIMP
+nsScreenAndroid::GetPixelDepth(int32_t *aPixelDepth)
+{
+ if (!mozilla::jni::IsAvailable()) {
+ // xpcshell most likely
+ *aPixelDepth = 16;
+ return NS_ERROR_FAILURE;
+ }
+
+ *aPixelDepth = java::GeckoAppShell::GetScreenDepth();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsScreenAndroid::GetColorDepth(int32_t *aColorDepth)
+{
+ return GetPixelDepth(aColorDepth);
+}
+
+
+void
+nsScreenAndroid::ApplyMinimumBrightness(uint32_t aBrightness)
+{
+ if (mDisplayType == DisplayType::DISPLAY_PRIMARY &&
+ mozilla::jni::IsAvailable()) {
+ java::GeckoAppShell::SetKeepScreenOn(aBrightness == BRIGHTNESS_FULL);
+ }
+}
+
+class nsScreenManagerAndroid::ScreenManagerHelperSupport final
+ : public ScreenManagerHelper::Natives<ScreenManagerHelperSupport>
+{
+public:
+ typedef ScreenManagerHelper::Natives<ScreenManagerHelperSupport> Base;
+
+ static int32_t AddDisplay(int32_t aDisplayType, int32_t aWidth, int32_t aHeight, float aDensity) {
+ int32_t screenId = -1; // return value
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ SyncRunnable::DispatchToThread(mainThread, NS_NewRunnableFunction(
+ [&aDisplayType, &aWidth, &aHeight, &aDensity, &screenId] {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ MOZ_ASSERT(screenMgr, "Failed to get nsIScreenManager");
+
+ RefPtr<nsScreenManagerAndroid> screenMgrAndroid =
+ (nsScreenManagerAndroid*) screenMgr.get();
+ RefPtr<nsScreenAndroid> screen =
+ screenMgrAndroid->AddScreen(static_cast<DisplayType>(aDisplayType),
+ nsIntRect(0, 0, aWidth, aHeight));
+ MOZ_ASSERT(screen);
+ screen->SetDensity(aDensity);
+ screenId = static_cast<int32_t>(screen->GetId());
+ }).take());
+ return screenId;
+ }
+
+ static void RemoveDisplay(int32_t aScreenId) {
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ SyncRunnable::DispatchToThread(mainThread, NS_NewRunnableFunction(
+ [&aScreenId] {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ MOZ_ASSERT(screenMgr, "Failed to get nsIScreenManager");
+
+ RefPtr<nsScreenManagerAndroid> screenMgrAndroid =
+ (nsScreenManagerAndroid*) screenMgr.get();
+ screenMgrAndroid->RemoveScreen(aScreenId);
+ }).take());
+ }
+};
+
+NS_IMPL_ISUPPORTS(nsScreenManagerAndroid, nsIScreenManager)
+
+nsScreenManagerAndroid::nsScreenManagerAndroid()
+{
+ if (mozilla::jni::IsAvailable()) {
+ ScreenManagerHelperSupport::Base::Init();
+ }
+ nsCOMPtr<nsIScreen> screen = AddScreen(DisplayType::DISPLAY_PRIMARY);
+ MOZ_ASSERT(screen);
+}
+
+nsScreenManagerAndroid::~nsScreenManagerAndroid()
+{
+}
+
+NS_IMETHODIMP
+nsScreenManagerAndroid::GetPrimaryScreen(nsIScreen **outScreen)
+{
+ ScreenForId(PRIMARY_SCREEN_ID, outScreen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerAndroid::ScreenForId(uint32_t aId,
+ nsIScreen **outScreen)
+{
+ for (size_t i = 0; i < mScreens.Length(); ++i) {
+ if (aId == mScreens[i]->GetId()) {
+ nsCOMPtr<nsIScreen> screen = (nsIScreen*) mScreens[i];
+ screen.forget(outScreen);
+ return NS_OK;
+ }
+ }
+
+ *outScreen = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerAndroid::ScreenForRect(int32_t inLeft,
+ int32_t inTop,
+ int32_t inWidth,
+ int32_t inHeight,
+ nsIScreen **outScreen)
+{
+ // Not support to query non-primary screen with rect.
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+nsScreenManagerAndroid::ScreenForNativeWidget(void *aWidget, nsIScreen **outScreen)
+{
+ // Not support to query non-primary screen with native widget.
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+nsScreenManagerAndroid::GetNumberOfScreens(uint32_t *aNumberOfScreens)
+{
+ *aNumberOfScreens = mScreens.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerAndroid::GetSystemDefaultScale(float *aDefaultScale)
+{
+ *aDefaultScale = 1.0f;
+ return NS_OK;
+}
+
+already_AddRefed<nsScreenAndroid>
+nsScreenManagerAndroid::AddScreen(DisplayType aDisplayType, nsIntRect aRect)
+{
+ ALOG("nsScreenManagerAndroid: add %s screen",
+ (aDisplayType == DisplayType::DISPLAY_PRIMARY ? "PRIMARY" :
+ (aDisplayType == DisplayType::DISPLAY_EXTERNAL ? "EXTERNAL" :
+ "VIRTUAL")));
+ RefPtr<nsScreenAndroid> screen = new nsScreenAndroid(aDisplayType, aRect);
+ mScreens.AppendElement(screen);
+ return screen.forget();
+}
+
+void
+nsScreenManagerAndroid::RemoveScreen(uint32_t aScreenId)
+{
+ for (size_t i = 0; i < mScreens.Length(); i++) {
+ if (aScreenId == mScreens[i]->GetId()) {
+ mScreens.RemoveElementAt(i);
+ }
+ }
+}
diff --git a/widget/android/nsScreenManagerAndroid.h b/widget/android/nsScreenManagerAndroid.h
new file mode 100644
index 000000000..cf8bfb245
--- /dev/null
+++ b/widget/android/nsScreenManagerAndroid.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: ts=4 sw=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 nsScreenManagerAndroid_h___
+#define nsScreenManagerAndroid_h___
+
+#include "nsCOMPtr.h"
+
+#include "nsBaseScreen.h"
+#include "nsIScreenManager.h"
+#include "nsRect.h"
+#include "mozilla/WidgetUtils.h"
+
+class nsScreenAndroid final : public nsBaseScreen
+{
+public:
+ nsScreenAndroid(DisplayType aDisplayType, nsIntRect aRect);
+ ~nsScreenAndroid();
+
+ NS_IMETHOD GetId(uint32_t* aId) override;
+ 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;
+
+ uint32_t GetId() const { return mId; };
+ DisplayType GetDisplayType() const { return mDisplayType; }
+
+ void SetDensity(double aDensity) { mDensity = aDensity; }
+ float GetDensity();
+
+protected:
+ virtual void ApplyMinimumBrightness(uint32_t aBrightness) override;
+
+private:
+ uint32_t mId;
+ DisplayType mDisplayType;
+ nsIntRect mRect;
+ float mDensity;
+};
+
+class nsScreenManagerAndroid final : public nsIScreenManager
+{
+private:
+ ~nsScreenManagerAndroid();
+
+public:
+ class ScreenManagerHelperSupport;
+
+ nsScreenManagerAndroid();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+ already_AddRefed<nsScreenAndroid> AddScreen(DisplayType aDisplayType,
+ nsIntRect aRect = nsIntRect());
+ void RemoveScreen(uint32_t aScreenId);
+
+protected:
+ nsTArray<RefPtr<nsScreenAndroid>> mScreens;
+};
+
+#endif /* nsScreenManagerAndroid_h___ */
diff --git a/widget/android/nsWidgetFactory.cpp b/widget/android/nsWidgetFactory.cpp
new file mode 100644
index 000000000..bd930e4a4
--- /dev/null
+++ b/widget/android/nsWidgetFactory.cpp
@@ -0,0 +1,132 @@
+/* -*- 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 "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+#include "AndroidBridge.h"
+
+#include "nsWindow.h"
+#include "nsLookAndFeel.h"
+#include "nsAppShellSingleton.h"
+#include "nsScreenManagerAndroid.h"
+
+#include "nsIdleServiceAndroid.h"
+#include "nsClipboard.h"
+#include "nsClipboardHelper.h"
+#include "nsTransferable.h"
+#include "nsPrintOptionsAndroid.h"
+#include "nsPrintSession.h"
+#include "nsDeviceContextAndroid.h"
+#include "nsHTMLFormatConverter.h"
+#include "nsXULAppAPI.h"
+#include "nsAndroidProtocolHandler.h"
+
+#include "nsToolkitCompsCID.h"
+#include "AndroidAlerts.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerAndroid)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceAndroid, nsIdleServiceAndroid::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsAndroid, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecAndroid)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAndroidBridge)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAndroidProtocolHandler)
+
+#include "GfxInfo.h"
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(AndroidAlerts)
+}
+}
+
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
+NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+NS_DEFINE_NAMED_CID(NS_ANDROIDBRIDGE_CID);
+NS_DEFINE_NAMED_CID(NS_ANDROIDPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_CHILD_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor },
+ { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerAndroidConstructor },
+ { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceAndroidConstructor },
+ { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
+ { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor },
+ { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
+ { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsAndroidConstructor },
+ { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor },
+ { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecAndroidConstructor },
+ { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
+ { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
+ { &kNS_ANDROIDBRIDGE_CID, false, nullptr, nsAndroidBridgeConstructor },
+ { &kNS_ANDROIDPROTOCOLHANDLER_CID, false, nullptr, nsAndroidProtocolHandlerConstructor },
+ { &kNS_SYSTEMALERTSSERVICE_CID, false, nullptr, mozilla::widget::AndroidAlertsConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/android;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/child_window/android;1", &kNS_CHILD_CID },
+ { "@mozilla.org/widget/appshell/android;1", &kNS_APPSHELL_CID },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID },
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
+ { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
+ { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { "@mozilla.org/android/bridge;1", &kNS_ANDROIDBRIDGE_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "android", &kNS_ANDROIDPROTOCOLHANDLER_CID },
+ { NS_SYSTEMALERTSERVICE_CONTRACTID, &kNS_SYSTEMALERTSSERVICE_CID },
+ { nullptr }
+};
+
+static void
+nsWidgetAndroidModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ mozilla::widget::WidgetUtils::Shutdown();
+
+ nsLookAndFeel::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ nullptr,
+ nullptr,
+ nsAppShellInit,
+ nsWidgetAndroidModuleDtor
+};
+
+NSMODULE_DEFN(nsWidgetAndroidModule) = &kWidgetModule;
diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp
new file mode 100644
index 000000000..9423a4a26
--- /dev/null
+++ b/widget/android/nsWindow.cpp
@@ -0,0 +1,3638 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * vim: set sw=4 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 <unistd.h>
+
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextComposition.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/WeakPtr.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/layers/RenderTrace.h"
+#include <algorithm>
+
+using mozilla::dom::ContentParent;
+using mozilla::dom::ContentChild;
+using mozilla::Unused;
+
+#include "nsWindow.h"
+
+#include "nsIBaseWindow.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIObserverService.h"
+#include "nsISelection.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIWidgetListener.h"
+#include "nsIWindowWatcher.h"
+#include "nsIXULWindow.h"
+
+#include "nsAppShell.h"
+#include "nsFocusManager.h"
+#include "nsIdleService.h"
+#include "nsLayoutUtils.h"
+#include "nsViewManager.h"
+
+#include "WidgetUtils.h"
+
+#include "nsIDOMSimpleGestureEvent.h"
+
+#include "nsGkAtoms.h"
+#include "nsWidgetsCID.h"
+#include "nsGfxCIID.h"
+
+#include "gfxContext.h"
+
+#include "Layers.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/layers/AsyncCompositionManager.h"
+#include "mozilla/layers/APZEventState.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "ScopedGLHelpers.h"
+#include "mozilla/layers/CompositorOGL.h"
+#include "AndroidContentController.h"
+
+#include "nsTArray.h"
+
+#include "AndroidBridge.h"
+#include "AndroidBridgeUtilities.h"
+#include "android_npapi.h"
+#include "FennecJNINatives.h"
+#include "GeneratedJNINatives.h"
+#include "KeyEvent.h"
+#include "MotionEvent.h"
+
+#include "imgIEncoder.h"
+
+#include "nsString.h"
+#include "GeckoProfiler.h" // For PROFILER_LABEL
+#include "nsIXULRuntime.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+using namespace mozilla::java;
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
+
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorSession.h"
+#include "mozilla/layers/LayerTransactionParent.h"
+#include "mozilla/Services.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;
+
+// Sync with GeckoEditableView class
+static const int IME_MONITOR_CURSOR_ONE_SHOT = 1;
+static const int IME_MONITOR_CURSOR_START_MONITOR = 2;
+static const int IME_MONITOR_CURSOR_END_MONITOR = 3;
+
+static Modifiers GetModifiers(int32_t metaState);
+
+template<typename Lambda, bool IsStatic, typename InstanceType, class Impl>
+class nsWindow::WindowEvent : public nsAppShell::LambdaEvent<Lambda>
+{
+ typedef nsAppShell::Event Event;
+ typedef nsAppShell::LambdaEvent<Lambda> Base;
+
+ bool IsStaleCall()
+ {
+ if (IsStatic) {
+ // Static calls are never stale.
+ return false;
+ }
+
+ JNIEnv* const env = mozilla::jni::GetEnvForThread();
+
+ const auto natives = reinterpret_cast<mozilla::WeakPtr<Impl>*>(
+ jni::GetNativeHandle(env, mInstance.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+
+ // The call is stale if the nsWindow 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 natives is nullptr here; we return false but the
+ // native call will throw an error.
+ return natives && !natives->get();
+ }
+
+ const InstanceType mInstance;
+ const Event::Type mEventType;
+
+public:
+ WindowEvent(Lambda&& aLambda,
+ InstanceType&& aInstance,
+ Event::Type aEventType = Event::Type::kGeneralActivity)
+ : Base(mozilla::Move(aLambda))
+ , mInstance(mozilla::Move(aInstance))
+ , mEventType(aEventType)
+ {}
+
+ WindowEvent(Lambda&& aLambda,
+ Event::Type aEventType = Event::Type::kGeneralActivity)
+ : Base(mozilla::Move(aLambda))
+ , mInstance(Base::lambda.GetThisArg())
+ , mEventType(aEventType)
+ {}
+
+ void Run() override
+ {
+ if (!IsStaleCall()) {
+ return Base::Run();
+ }
+ }
+
+ Event::Type ActivityType() const override
+ {
+ return mEventType;
+ }
+};
+
+template<class Impl>
+template<class Instance, typename... Args> void
+nsWindow::NativePtr<Impl>::Attach(Instance aInstance, nsWindow* aWindow,
+ Args&&... aArgs)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mPtr && !mImpl);
+
+ auto impl = mozilla::MakeUnique<Impl>(
+ this, aWindow, mozilla::Forward<Args>(aArgs)...);
+ mImpl = impl.get();
+
+ Impl::AttachNative(aInstance, mozilla::Move(impl));
+}
+
+template<class Impl> void
+nsWindow::NativePtr<Impl>::Detach()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPtr && mImpl);
+
+ mImpl->OnDetach();
+ {
+ Locked implLock(*this);
+ mImpl = nullptr;
+ }
+
+ typename WindowPtr<Impl>::Locked lock(*mPtr);
+ mPtr->mWindow = nullptr;
+ mPtr->mPtr = nullptr;
+ mPtr = nullptr;
+}
+
+template<class Impl>
+class nsWindow::NativePtr<Impl>::Locked final : private MutexAutoLock
+{
+ Impl* const mImpl;
+
+public:
+ Locked(NativePtr<Impl>& aPtr)
+ : MutexAutoLock(aPtr.mImplLock)
+ , mImpl(aPtr.mImpl)
+ {}
+
+ operator Impl*() const { return mImpl; }
+ Impl* operator->() const { return mImpl; }
+};
+
+template<class Impl>
+class nsWindow::WindowPtr final
+{
+ friend NativePtr<Impl>;
+
+ NativePtr<Impl>* mPtr;
+ nsWindow* mWindow;
+ Mutex mWindowLock;
+
+public:
+ class Locked final : private MutexAutoLock
+ {
+ nsWindow* const mWindow;
+
+ public:
+ Locked(WindowPtr<Impl>& aPtr)
+ : MutexAutoLock(aPtr.mWindowLock)
+ , mWindow(aPtr.mWindow)
+ {}
+
+ operator nsWindow*() const { return mWindow; }
+ nsWindow* operator->() const { return mWindow; }
+ };
+
+ WindowPtr(NativePtr<Impl>* aPtr, nsWindow* aWindow)
+ : mPtr(aPtr)
+ , mWindow(aWindow)
+ , mWindowLock(NativePtr<Impl>::sName)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mPtr->mPtr = this;
+ }
+
+ ~WindowPtr()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mPtr) {
+ return;
+ }
+ mPtr->mPtr = nullptr;
+ mPtr->mImpl = nullptr;
+ }
+
+ operator nsWindow*() const
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mWindow;
+ }
+
+ nsWindow* operator->() const { return operator nsWindow*(); }
+};
+
+
+class nsWindow::GeckoViewSupport final
+ : public GeckoView::Window::Natives<GeckoViewSupport>
+ , public GeckoEditable::Natives<GeckoViewSupport>
+ , public SupportsWeakPtr<GeckoViewSupport>
+{
+ nsWindow& window;
+
+public:
+ typedef GeckoView::Window::Natives<GeckoViewSupport> Base;
+ typedef GeckoEditable::Natives<GeckoViewSupport> EditableBase;
+
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(GeckoViewSupport);
+
+ template<typename Functor>
+ static void OnNativeCall(Functor&& aCall)
+ {
+ if (aCall.IsTarget(&Open) && NS_IsMainThread()) {
+ // Gecko state probably just switched to PROFILE_READY, and the
+ // event loop is not running yet. Skip the event loop here so we
+ // can get a head start on opening our window.
+ return aCall();
+ }
+
+ const nsAppShell::Event::Type eventType =
+ aCall.IsTarget(&GeckoViewSupport::OnKeyEvent) ||
+ aCall.IsTarget(&GeckoViewSupport::OnImeReplaceText) ||
+ aCall.IsTarget(&GeckoViewSupport::OnImeUpdateComposition) ?
+ nsAppShell::Event::Type::kUIActivity :
+ nsAppShell::Event::Type::kGeneralActivity;
+
+ nsAppShell::PostEvent(mozilla::MakeUnique<WindowEvent<Functor>>(
+ mozilla::Move(aCall), eventType));
+ }
+
+ GeckoViewSupport(nsWindow* aWindow,
+ const GeckoView::Window::LocalRef& aInstance,
+ GeckoView::Param aView)
+ : window(*aWindow)
+ , mEditable(GeckoEditable::New(aView))
+ , mIMERanges(new TextRangeArray())
+ , mIMEMaskEventsCount(1) // Mask IME events since there's no focus yet
+ , mIMEUpdatingContext(false)
+ , mIMESelectionChanged(false)
+ , mIMETextChangedDuringFlush(false)
+ , mIMEMonitorCursor(false)
+ {
+ Base::AttachNative(aInstance, this);
+ EditableBase::AttachNative(mEditable, this);
+ }
+
+ ~GeckoViewSupport();
+
+ using Base::DisposeNative;
+ using EditableBase::DisposeNative;
+
+ /**
+ * GeckoView methods
+ */
+private:
+ nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow;
+
+public:
+ // Create and attach a window.
+ static void Open(const jni::Class::LocalRef& aCls,
+ GeckoView::Window::Param aWindow,
+ GeckoView::Param aView, jni::Object::Param aCompositor,
+ jni::String::Param aChromeURI,
+ int32_t screenId);
+
+ // Close and destroy the nsWindow.
+ void Close();
+
+ // Reattach this nsWindow to a new GeckoView.
+ void Reattach(const GeckoView::Window::LocalRef& inst,
+ GeckoView::Param aView, jni::Object::Param aCompositor);
+
+ void LoadUri(jni::String::Param aUri, int32_t aFlags);
+
+ /**
+ * GeckoEditable methods
+ */
+private:
+ /*
+ 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
+ */
+
+ struct IMETextChange final {
+ int32_t mStart, mOldEnd, mNewEnd;
+
+ IMETextChange() :
+ mStart(-1), mOldEnd(-1), mNewEnd(-1) {}
+
+ 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; }
+ };
+
+ // GeckoEditable instance used by this nsWindow;
+ java::GeckoEditable::GlobalRef mEditable;
+ AutoTArray<mozilla::UniquePtr<mozilla::WidgetEvent>, 8> mIMEKeyEvents;
+ AutoTArray<IMETextChange, 4> mIMETextChanges;
+ InputContext mInputContext;
+ RefPtr<mozilla::TextRangeArray> mIMERanges;
+ int32_t mIMEMaskEventsCount; // Mask events when > 0.
+ bool mIMEUpdatingContext;
+ bool mIMESelectionChanged;
+ bool mIMETextChangedDuringFlush;
+ bool mIMEMonitorCursor;
+
+ void SendIMEDummyKeyEvents();
+ void AddIMETextChange(const IMETextChange& aChange);
+
+ 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
+ };
+ void PostFlushIMEChanges();
+ void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
+ void FlushIMEText(FlushChangesFlag aFlags = FLUSH_FLAG_NONE);
+ void AsyncNotifyIME(int32_t aNotification);
+ void UpdateCompositionRects();
+
+public:
+ bool NotifyIME(const IMENotification& aIMENotification);
+ void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction);
+ InputContext GetInputContext();
+
+ // RAII helper class that automatically sends an event reply through
+ // OnImeSynchronize, as required by events like OnImeReplaceText.
+ class AutoIMESynchronize {
+ GeckoViewSupport* const mGVS;
+ public:
+ AutoIMESynchronize(GeckoViewSupport* gvs) : mGVS(gvs) {}
+ ~AutoIMESynchronize() { mGVS->OnImeSynchronize(); }
+ };
+
+ // Handle an Android KeyEvent.
+ void OnKeyEvent(int32_t aAction, int32_t aKeyCode, int32_t aScanCode,
+ int32_t aMetaState, int64_t aTime, int32_t aUnicodeChar,
+ int32_t aBaseUnicodeChar, 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);
+
+ // Set cursor mode whether IME requests
+ void OnImeRequestCursorUpdates(int aRequestMode);
+};
+
+/**
+ * NativePanZoomController handles its native calls on the UI thread, so make
+ * it separate from GeckoViewSupport.
+ */
+class nsWindow::NPZCSupport final
+ : public NativePanZoomController::Natives<NPZCSupport>
+{
+ using LockedWindowPtr = WindowPtr<NPZCSupport>::Locked;
+
+ WindowPtr<NPZCSupport> mWindow;
+ NativePanZoomController::GlobalRef mNPZC;
+ int mPreviousButtons;
+
+public:
+ typedef NativePanZoomController::Natives<NPZCSupport> Base;
+
+ NPZCSupport(NativePtr<NPZCSupport>* aPtr, nsWindow* aWindow,
+ const NativePanZoomController::LocalRef& aNPZC)
+ : mWindow(aPtr, aWindow)
+ , mNPZC(aNPZC)
+ , mPreviousButtons(0)
+ {}
+
+ ~NPZCSupport()
+ {}
+
+ using Base::AttachNative;
+ using Base::DisposeNative;
+
+ void OnDetach()
+ {
+ // 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 OnDetach, 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. OnDetach is not
+ // called.
+ //
+ // 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.
+
+ typedef NativePanZoomController::GlobalRef NPZCRef;
+ auto callDestroy = [] (const NPZCRef& npzc) {
+ npzc->Destroy();
+ };
+
+ NativePanZoomController::GlobalRef npzc = mNPZC;
+ AndroidBridge::Bridge()->PostTaskToUiThread(NewRunnableFunction(
+ static_cast<void(*)(const NPZCRef&)>(callDestroy),
+ mozilla::Move(npzc)), 0);
+ }
+
+public:
+ void AdjustScrollForSurfaceShift(float aX, float aY)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (controller) {
+ controller->AdjustScrollForSurfaceShift(
+ ScreenPoint(aX, aY));
+ }
+ }
+
+ void SetIsLongpressEnabled(bool aIsLongpressEnabled)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (controller) {
+ controller->SetLongTapEnabled(aIsLongpressEnabled);
+ }
+ }
+
+ bool HandleScrollEvent(int64_t aTime, int32_t aMetaState,
+ float aX, float aY,
+ float aHScroll, float aVScroll)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (!controller) {
+ return false;
+ }
+
+ ScreenPoint origin = ScreenPoint(aX, aY);
+
+ ScrollWheelInput input(aTime, TimeStamp::Now(), GetModifiers(aMetaState),
+ ScrollWheelInput::SCROLLMODE_SMOOTH,
+ ScrollWheelInput::SCROLLDELTA_PIXEL,
+ origin,
+ aHScroll, aVScroll,
+ false);
+
+ ScrollableLayerGuid guid;
+ uint64_t blockId;
+ nsEventStatus status = controller->ReceiveInputEvent(input, &guid, &blockId);
+
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return true;
+ }
+
+ NativePanZoomController::GlobalRef npzc = mNPZC;
+ nsAppShell::PostEvent([npzc, input, guid, blockId, status] {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ NPZCSupport* npzcSupport = GetNative(
+ NativePanZoomController::LocalRef(env, npzc));
+
+ if (!npzcSupport || !npzcSupport->mWindow) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ nsWindow* const window = npzcSupport->mWindow;
+ window->UserActivity();
+ WidgetWheelEvent wheelEvent = input.ToWidgetWheelEvent(window);
+ window->ProcessUntransformedAPZEvent(&wheelEvent, guid,
+ blockId, status);
+ });
+
+ return true;
+ }
+
+private:
+ static MouseInput::ButtonType GetButtonType(int button)
+ {
+ MouseInput::ButtonType result = MouseInput::NONE;
+
+ switch (button) {
+ case java::sdk::MotionEvent::BUTTON_PRIMARY:
+ result = MouseInput::LEFT_BUTTON;
+ break;
+ case java::sdk::MotionEvent::BUTTON_SECONDARY:
+ result = MouseInput::RIGHT_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 |= WidgetMouseEventBase::eLeftButtonFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_SECONDARY) {
+ result |= WidgetMouseEventBase::eRightButtonFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_TERTIARY) {
+ result |= WidgetMouseEventBase::eMiddleButtonFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_BACK) {
+ result |= WidgetMouseEventBase::e4thButtonFlag;
+ }
+ if (buttons & java::sdk::MotionEvent::BUTTON_FORWARD) {
+ result |= WidgetMouseEventBase::e5thButtonFlag;
+ }
+
+ return result;
+ }
+
+public:
+ bool HandleMouseEvent(int32_t aAction, int64_t aTime, int32_t aMetaState,
+ float aX, float aY, int buttons)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (!controller) {
+ return false;
+ }
+
+ MouseInput::MouseType mouseType = MouseInput::MOUSE_NONE;
+ MouseInput::ButtonType buttonType = MouseInput::NONE;
+ switch (aAction) {
+ case AndroidMotionEvent::ACTION_DOWN:
+ mouseType = MouseInput::MOUSE_DOWN;
+ buttonType = GetButtonType(buttons ^ mPreviousButtons);
+ mPreviousButtons = buttons;
+ break;
+ case AndroidMotionEvent::ACTION_UP:
+ mouseType = MouseInput::MOUSE_UP;
+ buttonType = GetButtonType(buttons ^ mPreviousButtons);
+ mPreviousButtons = buttons;
+ break;
+ case AndroidMotionEvent::ACTION_MOVE:
+ mouseType = MouseInput::MOUSE_MOVE;
+ break;
+ case AndroidMotionEvent::ACTION_HOVER_MOVE:
+ mouseType = MouseInput::MOUSE_MOVE;
+ break;
+ case AndroidMotionEvent::ACTION_HOVER_ENTER:
+ mouseType = MouseInput::MOUSE_WIDGET_ENTER;
+ break;
+ case AndroidMotionEvent::ACTION_HOVER_EXIT:
+ mouseType = MouseInput::MOUSE_WIDGET_EXIT;
+ break;
+ default:
+ break;
+ }
+
+ if (mouseType == MouseInput::MOUSE_NONE) {
+ return false;
+ }
+
+ ScreenPoint origin = ScreenPoint(aX, aY);
+
+ MouseInput input(mouseType, buttonType, nsIDOMMouseEvent::MOZ_SOURCE_MOUSE, ConvertButtons(buttons), origin, aTime, TimeStamp(), GetModifiers(aMetaState));
+
+ ScrollableLayerGuid guid;
+ uint64_t blockId;
+ nsEventStatus status = controller->ReceiveInputEvent(input, &guid, &blockId);
+
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return true;
+ }
+
+ NativePanZoomController::GlobalRef npzc = mNPZC;
+ nsAppShell::PostEvent([npzc, input, guid, blockId, status] {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ NPZCSupport* npzcSupport = GetNative(
+ NativePanZoomController::LocalRef(env, npzc));
+
+ if (!npzcSupport || !npzcSupport->mWindow) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ nsWindow* const window = npzcSupport->mWindow;
+ window->UserActivity();
+ WidgetMouseEvent mouseEvent = input.ToWidgetMouseEvent(window);
+ window->ProcessUntransformedAPZEvent(&mouseEvent, guid,
+ blockId, status);
+ });
+
+ return true;
+ }
+
+ bool HandleMotionEvent(const NativePanZoomController::LocalRef& aInstance,
+ int32_t aAction, int32_t aActionIndex,
+ int64_t aTime, int32_t aMetaState,
+ jni::IntArray::Param aPointerId,
+ jni::FloatArray::Param aX,
+ jni::FloatArray::Param aY,
+ jni::FloatArray::Param aOrientation,
+ jni::FloatArray::Param aPressure,
+ jni::FloatArray::Param aToolMajor,
+ jni::FloatArray::Param aToolMinor)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (!controller) {
+ return false;
+ }
+
+ nsTArray<int32_t> pointerId(aPointerId->GetElements());
+ MultiTouchInput::MultiTouchType type;
+ size_t startIndex = 0;
+ size_t endIndex = pointerId.Length();
+
+ switch (aAction) {
+ case sdk::MotionEvent::ACTION_DOWN:
+ case sdk::MotionEvent::ACTION_POINTER_DOWN:
+ type = MultiTouchInput::MULTITOUCH_START;
+ break;
+ case sdk::MotionEvent::ACTION_MOVE:
+ type = MultiTouchInput::MULTITOUCH_MOVE;
+ break;
+ case sdk::MotionEvent::ACTION_UP:
+ case 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 = aActionIndex;
+ endIndex = aActionIndex + 1;
+ break;
+ case sdk::MotionEvent::ACTION_OUTSIDE:
+ case sdk::MotionEvent::ACTION_CANCEL:
+ type = MultiTouchInput::MULTITOUCH_CANCEL;
+ break;
+ default:
+ return false;
+ }
+
+ MultiTouchInput input(type, aTime, TimeStamp(), 0);
+ input.modifiers = GetModifiers(aMetaState);
+ input.mTouches.SetCapacity(endIndex - startIndex);
+
+ nsTArray<float> x(aX->GetElements());
+ nsTArray<float> y(aY->GetElements());
+ nsTArray<float> orientation(aOrientation->GetElements());
+ nsTArray<float> pressure(aPressure->GetElements());
+ nsTArray<float> toolMajor(aToolMajor->GetElements());
+ nsTArray<float> toolMinor(aToolMinor->GetElements());
+
+ MOZ_ASSERT(pointerId.Length() == x.Length());
+ MOZ_ASSERT(pointerId.Length() == y.Length());
+ MOZ_ASSERT(pointerId.Length() == orientation.Length());
+ MOZ_ASSERT(pointerId.Length() == pressure.Length());
+ MOZ_ASSERT(pointerId.Length() == toolMajor.Length());
+ MOZ_ASSERT(pointerId.Length() == toolMinor.Length());
+
+ for (size_t i = startIndex; i < endIndex; i++) {
+
+ float orien = orientation[i] * 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 (orien >= 90.0) {
+ orien -= 180.0f;
+ }
+
+ nsIntPoint point = nsIntPoint(int32_t(floorf(x[i])),
+ int32_t(floorf(y[i])));
+
+ // 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.
+ gfx::Size radius;
+ if (orien < 0.0f) {
+ orien += 90.0f;
+ radius = gfx::Size(int32_t(toolMajor[i] / 2.0f),
+ int32_t(toolMinor[i] / 2.0f));
+ } else {
+ radius = gfx::Size(int32_t(toolMinor[i] / 2.0f),
+ int32_t(toolMajor[i] / 2.0f));
+ }
+
+ input.mTouches.AppendElement(SingleTouchData(
+ pointerId[i], ScreenIntPoint::FromUnknownPoint(point),
+ ScreenSize::FromUnknownSize(radius), orien, pressure[i]));
+ }
+
+ ScrollableLayerGuid guid;
+ uint64_t blockId;
+ nsEventStatus status =
+ controller->ReceiveInputEvent(input, &guid, &blockId);
+
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return true;
+ }
+
+ // Dispatch APZ input event on Gecko thread.
+ NativePanZoomController::GlobalRef npzc = mNPZC;
+ nsAppShell::PostEvent([npzc, input, guid, blockId, status] {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ NPZCSupport* npzcSupport = GetNative(
+ NativePanZoomController::LocalRef(env, npzc));
+
+ if (!npzcSupport || !npzcSupport->mWindow) {
+ // We already shut down.
+ env->ExceptionClear();
+ return;
+ }
+
+ nsWindow* const window = npzcSupport->mWindow;
+ window->UserActivity();
+ WidgetTouchEvent touchEvent = input.ToWidgetTouchEvent(window);
+ window->ProcessUntransformedAPZEvent(&touchEvent, guid,
+ blockId, status);
+ window->DispatchHitTest(touchEvent);
+ });
+ return true;
+ }
+
+ void HandleMotionEventVelocity(int64_t aTime, float aSpeedY)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<IAPZCTreeManager> controller;
+
+ if (LockedWindowPtr window{mWindow}) {
+ controller = window->mAPZC;
+ }
+
+ if (controller) {
+ controller->ProcessTouchVelocity((uint32_t)aTime, aSpeedY);
+ }
+ }
+
+ void UpdateOverscrollVelocity(const float x, const float y)
+ {
+ mNPZC->UpdateOverscrollVelocity(x, y);
+ }
+
+ void UpdateOverscrollOffset(const float x, const float y)
+ {
+ mNPZC->UpdateOverscrollOffset(x, y);
+ }
+
+ void SetScrollingRootContent(const bool isRootContent)
+ {
+ mNPZC->SetScrollingRootContent(isRootContent);
+ }
+
+ void SetSelectionDragState(const bool aState)
+ {
+ mNPZC->OnSelectionDragState(aState);
+ }
+};
+
+template<> const char
+nsWindow::NativePtr<nsWindow::NPZCSupport>::sName[] = "NPZCSupport";
+
+/**
+ * Compositor has some unique requirements for its native calls, so make it
+ * separate from GeckoViewSupport.
+ */
+class nsWindow::LayerViewSupport final
+ : public LayerView::Compositor::Natives<LayerViewSupport>
+{
+ using LockedWindowPtr = WindowPtr<LayerViewSupport>::Locked;
+
+ WindowPtr<LayerViewSupport> mWindow;
+ LayerView::Compositor::GlobalRef mCompositor;
+ GeckoLayerClient::GlobalRef mLayerClient;
+ Atomic<bool, ReleaseAcquire> mCompositorPaused;
+ jni::Object::GlobalRef mSurface;
+
+ // 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>(mozilla::Move(event));
+ }
+
+ LayerViewEvent(UniquePtr<Event>&& event)
+ : nsAppShell::ProxyEvent(mozilla::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 LayerView::Compositor::Natives<LayerViewSupport> Base;
+
+ template<class Functor>
+ static void OnNativeCall(Functor&& aCall)
+ {
+ if (aCall.IsTarget(&LayerViewSupport::CreateCompositor)) {
+ // This call is blocking.
+ nsAppShell::SyncRunEvent(nsAppShell::LambdaEvent<Functor>(
+ mozilla::Move(aCall)), &LayerViewEvent::MakeEvent);
+ return;
+ }
+ }
+
+ static LayerViewSupport*
+ FromNative(const LayerView::Compositor::LocalRef& instance)
+ {
+ return GetNative(instance);
+ }
+
+ LayerViewSupport(NativePtr<LayerViewSupport>* aPtr, nsWindow* aWindow,
+ const LayerView::Compositor::LocalRef& aInstance)
+ : mWindow(aPtr, aWindow)
+ , mCompositor(aInstance)
+ , mCompositorPaused(true)
+ {}
+
+ ~LayerViewSupport()
+ {}
+
+ using Base::AttachNative;
+ using Base::DisposeNative;
+
+ void OnDetach()
+ {
+ mCompositor->Destroy();
+ }
+
+ const GeckoLayerClient::Ref& GetLayerClient() const
+ {
+ return mLayerClient;
+ }
+
+ bool CompositorPaused() const
+ {
+ return mCompositorPaused;
+ }
+
+ jni::Object::Param GetSurface()
+ {
+ return mSurface;
+ }
+
+private:
+ void OnResumedCompositor()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // When we receive this, the compositor has already been told to
+ // resume. (It turns out that waiting till we reach here to tell
+ // the compositor to resume takes too long, resulting in a black
+ // flash.) 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 (!mCompositorPaused) {
+ mWindow->RedrawAll();
+ }
+ }
+
+ /**
+ * Compositor methods
+ */
+public:
+ void AttachToJava(jni::Object::Param aClient, jni::Object::Param aNPZC)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mWindow) {
+ return; // Already shut down.
+ }
+
+ const auto& layerClient = GeckoLayerClient::Ref::From(aClient);
+
+ // If resetting is true, Android destroyed our GeckoApp activity and we
+ // had to recreate it, but all the Gecko-side things were not
+ // destroyed. We therefore need to link up the new java objects to
+ // Gecko, and that's what we do here.
+ const bool resetting = !!mLayerClient;
+ mLayerClient = layerClient;
+
+ MOZ_ASSERT(aNPZC);
+ auto npzc = NativePanZoomController::LocalRef(
+ jni::GetGeckoThreadEnv(),
+ NativePanZoomController::Ref::From(aNPZC));
+ mWindow->mNPZCSupport.Attach(npzc, mWindow, npzc);
+
+ layerClient->OnGeckoReady();
+
+ if (resetting) {
+ // Since we are re-linking the new java objects to Gecko, we need
+ // to get the viewport from the compositor (since the Java copy was
+ // thrown away) and we do that by setting the first-paint flag.
+ if (RefPtr<CompositorBridgeParent> bridge = mWindow->GetCompositorBridgeParent()) {
+ bridge->ForceIsFirstPaint();
+ }
+ }
+ }
+
+ void OnSizeChanged(int32_t aWindowWidth, int32_t aWindowHeight,
+ int32_t aScreenWidth, int32_t aScreenHeight)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mWindow) {
+ return; // Already shut down.
+ }
+
+ if (aWindowWidth != mWindow->mBounds.width ||
+ aWindowHeight != mWindow->mBounds.height) {
+
+ mWindow->Resize(aWindowWidth, aWindowHeight, /* repaint */ false);
+ }
+ }
+
+ void CreateCompositor(int32_t aWidth, int32_t aHeight,
+ jni::Object::Param aSurface)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mWindow);
+
+ mSurface = aSurface;
+ mWindow->CreateLayerManager(aWidth, aHeight);
+
+ mCompositorPaused = false;
+ OnResumedCompositor();
+ }
+
+ void SyncPauseCompositor()
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<CompositorBridgeParent> bridge;
+
+ if (LockedWindowPtr window{mWindow}) {
+ bridge = window->GetCompositorBridgeParent();
+ }
+
+ if (bridge) {
+ mCompositorPaused = true;
+ bridge->SchedulePauseOnCompositorThread();
+ }
+ }
+
+ void SyncResumeCompositor()
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<CompositorBridgeParent> bridge;
+
+ if (LockedWindowPtr window{mWindow}) {
+ bridge = window->GetCompositorBridgeParent();
+ }
+
+ if (bridge && bridge->ScheduleResumeOnCompositorThread()) {
+ mCompositorPaused = false;
+ }
+ }
+
+ void SyncResumeResizeCompositor(const LayerView::Compositor::LocalRef& aObj,
+ int32_t aWidth, int32_t aHeight,
+ jni::Object::Param aSurface)
+ {
+ MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+ RefPtr<CompositorBridgeParent> bridge;
+
+ if (LockedWindowPtr window{mWindow}) {
+ bridge = window->GetCompositorBridgeParent();
+ }
+
+ mSurface = aSurface;
+
+ if (!bridge || !bridge->ScheduleResumeOnCompositorThread(aWidth,
+ aHeight)) {
+ return;
+ }
+
+ mCompositorPaused = false;
+
+ class OnResumedEvent : public nsAppShell::Event
+ {
+ LayerView::Compositor::GlobalRef mCompositor;
+
+ public:
+ OnResumedEvent(LayerView::Compositor::GlobalRef&& aCompositor)
+ : mCompositor(mozilla::Move(aCompositor))
+ {}
+
+ void Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ LayerViewSupport* const lvs = GetNative(
+ LayerView::Compositor::LocalRef(env, mCompositor));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+
+ lvs->OnResumedCompositor();
+ }
+ };
+
+ nsAppShell::PostEvent(MakeUnique<LayerViewEvent>(
+ MakeUnique<OnResumedEvent>(aObj)));
+ }
+
+ void SyncInvalidateAndScheduleComposite()
+ {
+ RefPtr<CompositorBridgeParent> bridge;
+
+ if (LockedWindowPtr window{mWindow}) {
+ bridge = window->GetCompositorBridgeParent();
+ }
+
+ if (bridge) {
+ bridge->InvalidateOnCompositorThread();
+ bridge->ScheduleRenderOnCompositorThread();
+ }
+ }
+};
+
+template<> const char
+nsWindow::NativePtr<nsWindow::LayerViewSupport>::sName[] = "LayerViewSupport";
+
+/* PresentationMediaPlayerManager native calls access inner nsWindow functionality so PMPMSupport is a child class of nsWindow */
+class nsWindow::PMPMSupport final
+ : public PresentationMediaPlayerManager::Natives<PMPMSupport>
+{
+ PMPMSupport() = delete;
+
+ static LayerViewSupport* GetLayerViewSupport(jni::Object::Param aView)
+ {
+ const auto& layerView = LayerView::Ref::From(aView);
+
+ LayerView::Compositor::LocalRef compositor = layerView->GetCompositor();
+ if (!layerView->CompositorCreated() || !compositor) {
+ return nullptr;
+ }
+
+ LayerViewSupport* const lvs = LayerViewSupport::FromNative(compositor);
+ if (!lvs) {
+ // There is a pending exception whenever FromNative returns nullptr.
+ compositor.Env()->ExceptionClear();
+ }
+ return lvs;
+ }
+
+public:
+ static ANativeWindow* sWindow;
+ static EGLSurface sSurface;
+
+ static void InvalidateAndScheduleComposite(jni::Object::Param aView)
+ {
+ LayerViewSupport* const lvs = GetLayerViewSupport(aView);
+ if (lvs) {
+ lvs->SyncInvalidateAndScheduleComposite();
+ }
+ }
+
+ static void AddPresentationSurface(const jni::Class::LocalRef& aCls,
+ jni::Object::Param aView,
+ jni::Object::Param aSurface)
+ {
+ RemovePresentationSurface();
+
+ LayerViewSupport* const lvs = GetLayerViewSupport(aView);
+ if (!lvs) {
+ return;
+ }
+
+ ANativeWindow* const window = ANativeWindow_fromSurface(
+ aCls.Env(), aSurface.Get());
+ if (!window) {
+ return;
+ }
+
+ sWindow = window;
+
+ const bool wasAlreadyPaused = lvs->CompositorPaused();
+ if (!wasAlreadyPaused) {
+ lvs->SyncPauseCompositor();
+ }
+
+ if (sSurface) {
+ // Destroy the EGL surface! The compositor is paused so it should
+ // be okay to destroy the surface here.
+ mozilla::gl::GLContextProvider::DestroyEGLSurface(sSurface);
+ sSurface = nullptr;
+ }
+
+ if (!wasAlreadyPaused) {
+ lvs->SyncResumeCompositor();
+ }
+
+ lvs->SyncInvalidateAndScheduleComposite();
+ }
+
+ static void RemovePresentationSurface()
+ {
+ if (sWindow) {
+ ANativeWindow_release(sWindow);
+ sWindow = nullptr;
+ }
+ }
+};
+
+ANativeWindow* nsWindow::PMPMSupport::sWindow;
+EGLSurface nsWindow::PMPMSupport::sSurface;
+
+
+nsWindow::GeckoViewSupport::~GeckoViewSupport()
+{
+ // Disassociate our GeckoEditable instance with our native object.
+ // OnDestroy will call disposeNative after any pending native calls have
+ // been made.
+ MOZ_ASSERT(mEditable);
+ mEditable->OnViewChange(nullptr);
+
+ if (window.mNPZCSupport) {
+ window.mNPZCSupport.Detach();
+ }
+
+ if (window.mLayerViewSupport) {
+ window.mLayerViewSupport.Detach();
+ }
+}
+
+/* static */ void
+nsWindow::GeckoViewSupport::Open(const jni::Class::LocalRef& aCls,
+ GeckoView::Window::Param aWindow,
+ GeckoView::Param aView,
+ jni::Object::Param aCompositor,
+ jni::String::Param aChromeURI,
+ int32_t aScreenId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PROFILER_LABEL("nsWindow", "GeckoViewSupport::Open",
+ js::ProfileEntry::Category::OTHER);
+
+ nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+ MOZ_RELEASE_ASSERT(ww);
+
+ nsAdoptingCString url;
+ if (aChromeURI) {
+ url = aChromeURI->ToCString();
+ } else {
+ url = Preferences::GetCString("toolkit.defaultChromeURI");
+ if (!url) {
+ url = NS_LITERAL_CSTRING("chrome://browser/content/browser.xul");
+ }
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ ww->OpenWindow(nullptr, url, nullptr, "chrome,dialog=0,resizable,scrollbars=yes",
+ nullptr, getter_AddRefs(domWindow));
+ MOZ_RELEASE_ASSERT(domWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> pdomWindow =
+ nsPIDOMWindowOuter::From(domWindow);
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(pdomWindow);
+ MOZ_ASSERT(widget);
+
+ const auto window = static_cast<nsWindow*>(widget.get());
+ window->SetScreenId(aScreenId);
+
+ // Attach a new GeckoView support object to the new window.
+ window->mGeckoViewSupport = mozilla::MakeUnique<GeckoViewSupport>(
+ window, GeckoView::Window::LocalRef(aCls.Env(), aWindow), aView);
+
+ window->mGeckoViewSupport->mDOMWindow = pdomWindow;
+
+ // Attach the Compositor to the new window.
+ auto compositor = LayerView::Compositor::LocalRef(
+ aCls.Env(), LayerView::Compositor::Ref::From(aCompositor));
+ window->mLayerViewSupport.Attach(compositor, window, compositor);
+
+ if (window->mWidgetListener) {
+ nsCOMPtr<nsIXULWindow> xulWindow(
+ window->mWidgetListener->GetXULWindow());
+ if (xulWindow) {
+ // Our window is not intrinsically sized, so tell nsXULWindow to
+ // not set a size for us.
+ xulWindow->SetIntrinsicallySized(false);
+ }
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::Close()
+{
+ if (!mDOMWindow) {
+ return;
+ }
+
+ mDOMWindow->ForceClose();
+ mDOMWindow = nullptr;
+}
+
+void
+nsWindow::GeckoViewSupport::Reattach(const GeckoView::Window::LocalRef& inst,
+ GeckoView::Param aView,
+ jni::Object::Param aCompositor)
+{
+ // Associate our previous GeckoEditable with the new GeckoView.
+ mEditable->OnViewChange(aView);
+
+ // mNPZCSupport might have already been detached through the Java side calling
+ // NativePanZoomController.destroy().
+ if (window.mNPZCSupport) {
+ window.mNPZCSupport.Detach();
+ }
+
+ MOZ_ASSERT(window.mLayerViewSupport);
+ window.mLayerViewSupport.Detach();
+
+ auto compositor = LayerView::Compositor::LocalRef(
+ inst.Env(), LayerView::Compositor::Ref::From(aCompositor));
+ window.mLayerViewSupport.Attach(compositor, &window, compositor);
+ compositor->Reattach();
+}
+
+void
+nsWindow::GeckoViewSupport::LoadUri(jni::String::Param aUri, int32_t aFlags)
+{
+ if (!mDOMWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = nsAppShell::ResolveURI(aUri->ToCString());
+ if (NS_WARN_IF(!uri)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(mDOMWindow);
+ nsCOMPtr<nsIBrowserDOMWindow> browserWin;
+
+ if (NS_WARN_IF(!chromeWin) || NS_WARN_IF(NS_FAILED(
+ chromeWin->GetBrowserDOMWindow(getter_AddRefs(browserWin))))) {
+ return;
+ }
+
+ const int flags = aFlags == GeckoView::LOAD_NEW_TAB ?
+ nsIBrowserDOMWindow::OPEN_NEWTAB :
+ aFlags == GeckoView::LOAD_SWITCH_TAB ?
+ nsIBrowserDOMWindow::OPEN_SWITCHTAB :
+ nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
+ nsCOMPtr<mozIDOMWindowProxy> newWin;
+
+ if (NS_FAILED(browserWin->OpenURI(
+ uri, nullptr, flags, nsIBrowserDOMWindow::OPEN_EXTERNAL,
+ getter_AddRefs(newWin)))) {
+ NS_WARNING("Failed to open URI");
+ }
+}
+
+void
+nsWindow::InitNatives()
+{
+ nsWindow::GeckoViewSupport::Base::Init();
+ nsWindow::GeckoViewSupport::EditableBase::Init();
+ nsWindow::LayerViewSupport::Init();
+ nsWindow::NPZCSupport::Init();
+ if (jni::IsFennec()) {
+ nsWindow::PMPMSupport::Init();
+ }
+}
+
+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%08x [parent 0x%08x] [% 3d,% 3dx% 3d,% 3d] vis %d type %d",
+ spaces, index, (intptr_t)win, (intptr_t)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),
+ mAwaitingFullScreen(false),
+ mIsFullScreen(false)
+{
+}
+
+nsWindow::~nsWindow()
+{
+ gTopLevelWindows.RemoveElement(this);
+ ALOG("nsWindow %p destructor", (void*)this);
+}
+
+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;
+ }
+ }
+
+ mBounds = aRect;
+
+ 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;
+ }
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+
+ return NS_OK;
+}
+
+void
+nsWindow::Destroy()
+{
+ nsBaseWidget::mOnDestroyCalled = true;
+
+ if (mGeckoViewSupport) {
+ // Disassociate our native object with GeckoView.
+ mGeckoViewSupport = nullptr;
+ }
+
+ // 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);
+ }
+
+ nsBaseWidget::Destroy();
+
+ if (IsTopLevel())
+ gTopLevelWindows.RemoveElement(this);
+
+ SetParent(nullptr);
+
+ nsBaseWidget::OnDestroy();
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+}
+
+NS_IMETHODIMP
+nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>& config)
+{
+ for (uint32_t i = 0; i < config.Length(); ++i) {
+ nsWindow *childWin = (nsWindow*) config[i].mChild.get();
+ childWin->Resize(config[i].mBounds.x,
+ config[i].mBounds.y,
+ config[i].mBounds.width,
+ config[i].mBounds.height,
+ false);
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindow::RedrawAll()
+{
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->RequestRepaint();
+ } else if (mWidgetListener) {
+ mWidgetListener->RequestRepaint();
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::SetParent(nsIWidget *aNewParent)
+{
+ if ((nsIWidget*)mParent == aNewParent)
+ return NS_OK;
+
+ // 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();
+
+ return NS_OK;
+}
+
+nsIWidget*
+nsWindow::GetParent()
+{
+ return mParent;
+}
+
+float
+nsWindow::GetDPI()
+{
+ if (AndroidBridge::Bridge())
+ return AndroidBridge::Bridge()->GetDPI();
+ return 160.0f;
+}
+
+double
+nsWindow::GetDefaultScaleInternal()
+{
+
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ MOZ_ASSERT(screen);
+ RefPtr<nsScreenAndroid> screenAndroid = (nsScreenAndroid*) screen.get();
+ return screenAndroid->GetDensity();
+}
+
+NS_IMETHODIMP
+nsWindow::Show(bool aState)
+{
+ ALOG("nsWindow[%p]::Show %d", (void*)this, aState);
+
+ if (mWindowType == eWindowType_invisible) {
+ ALOG("trying to show invisible window! ignoring..");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aState == mIsVisible)
+ return NS_OK;
+
+ 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
+
+ return NS_OK;
+}
+
+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;
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::Move(double aX,
+ double aY)
+{
+ if (IsTopLevel())
+ return NS_OK;
+
+ return Resize(aX,
+ aY,
+ mBounds.width,
+ mBounds.height,
+ true);
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aWidth,
+ double aHeight,
+ bool aRepaint)
+{
+ return Resize(mBounds.x,
+ mBounds.y,
+ aWidth,
+ aHeight,
+ aRepaint);
+}
+
+NS_IMETHODIMP
+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 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));
+ }
+
+ // Should we skip honoring aRepaint here?
+ if (aRepaint && FindTopLevel() == nsWindow::TopWindow())
+ RedrawAll();
+
+ nsIWidgetListener* listener = GetWidgetListener();
+ if (mAwaitingFullScreen && listener) {
+ listener->FullscreenChanged(mIsFullScreen);
+ mAwaitingFullScreen = false;
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindow::SetZIndex(int32_t aZIndex)
+{
+ ALOG("nsWindow[%p]::SetZIndex %d ignored", (void*)this, aZIndex);
+}
+
+void
+nsWindow::SetSizeMode(nsSizeMode aMode)
+{
+ switch (aMode) {
+ case nsSizeMode_Minimized:
+ GeckoAppShell::MoveTaskToBack();
+ break;
+ case nsSizeMode_Fullscreen:
+ MakeFullScreen(true);
+ break;
+ default:
+ break;
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::Enable(bool aState)
+{
+ ALOG("nsWindow[%p]::Enable %d ignored", (void*)this, aState);
+ return NS_OK;
+}
+
+bool
+nsWindow::IsEnabled() const
+{
+ return true;
+}
+
+NS_IMETHODIMP
+nsWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ return NS_OK;
+}
+
+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;
+}
+
+NS_IMETHODIMP
+nsWindow::SetFocus(bool aRaise)
+{
+ nsWindow *top = FindTopLevel();
+ top->BringToFront();
+
+ return NS_OK;
+}
+
+void
+nsWindow::BringToFront()
+{
+ // 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.
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ nsCOMPtr<mozIDOMWindowProxy> existingTopWindow;
+ fm->GetActiveWindow(getter_AddRefs(existingTopWindow));
+ if (existingTopWindow && 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);
+ nsWindow *w = this;
+
+ while (w && !w->IsTopLevel()) {
+ p.x += w->mBounds.x;
+ p.y += w->mBounds.y;
+
+ w = w->mParent;
+ }
+
+ return p;
+}
+
+NS_IMETHODIMP
+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*)
+{
+ mIsFullScreen = aFullScreen;
+ mAwaitingFullScreen = true;
+ GeckoAppShell::SetFullScreen(aFullScreen);
+ return NS_OK;
+}
+
+mozilla::layers::LayerManager*
+nsWindow::GetLayerManager(PLayerTransactionChild*, LayersBackend, LayerManagerPersistence)
+{
+ if (mLayerManager) {
+ return mLayerManager;
+ }
+ return nullptr;
+}
+
+void
+nsWindow::CreateLayerManager(int aCompositorWidth, int aCompositorHeight)
+{
+ 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()) {
+ CreateCompositor(aCompositorWidth, aCompositorHeight);
+ 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::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 (NativePtr<NPZCSupport>::Locked npzcs{mNPZCSupport}) {
+ npzcs->UpdateOverscrollVelocity(aX, aY);
+ }
+}
+
+void
+nsWindow::UpdateOverscrollOffset(const float aX, const float aY)
+{
+ if (NativePtr<NPZCSupport>::Locked npzcs{mNPZCSupport}) {
+ npzcs->UpdateOverscrollOffset(aX, aY);
+ }
+}
+
+void
+nsWindow::SetScrollingRootContent(const bool isRootContent)
+{
+ // On Android, the Controller thread and UI thread are the same.
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread(), "nsWindow::SetScrollingRootContent must be called from the controller thread");
+
+ if (NativePtr<NPZCSupport>::Locked npzcs{mNPZCSupport}) {
+ npzcs->SetScrollingRootContent(isRootContent);
+ }
+}
+
+void
+nsWindow::SetSelectionDragState(bool aState)
+{
+ if (NativePtr<NPZCSupport>::Locked npzcs{mNPZCSupport}) {
+ npzcs->SetSelectionDragState(aState);
+ }
+}
+
+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 (NativePtr<LayerViewSupport>::Locked lvs{mLayerViewSupport}) {
+ return lvs->GetSurface().Get();
+ }
+ return nullptr;
+
+ case NS_PRESENTATION_WINDOW:
+ return PMPMSupport::sWindow;
+
+ case NS_PRESENTATION_SURFACE:
+ return PMPMSupport::sSurface;
+ }
+
+ return nullptr;
+}
+
+void
+nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal)
+{
+ switch (aDataType) {
+ case NS_PRESENTATION_SURFACE:
+ PMPMSupport::sSurface = reinterpret_cast<EGLSurface>(aVal);
+ break;
+ }
+}
+
+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 on Fennec, we dispatch a dummy mouse event that *does* get
+ // retargeted. The Fennec browser.js code can use this to activate the
+ // highlight element in case the this touchstart is the start of a tap.
+ WidgetMouseEvent hittest(true, eMouseHitTest, this,
+ WidgetMouseEvent::eReal);
+ hittest.mRefPoint = aEvent.mTouches[0]->mRefPoint;
+ hittest.mIgnoreRootScrollFrame = true;
+ hittest.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+ nsEventStatus status;
+ DispatchEvent(&hittest, status);
+
+ if (mAPZEventState && hittest.hitCluster) {
+ mAPZEventState->ProcessClusterHit();
+ }
+ }
+}
+
+static unsigned int ConvertAndroidKeyCodeToDOMKeyCode(int 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", androidKeyCode);
+ return 0;
+ }
+}
+
+static KeyNameIndex
+ConvertAndroidKeyCodeToKeyNameIndex(int keyCode, int action,
+ int 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_ENDCALL:
+ case AKEYCODE_NUM: // XXX Not sure
+ case AKEYCODE_HEADSETHOOK:
+ case AKEYCODE_NOTIFICATION: // 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_MUTE: // mutes the microphone
+ case AKEYCODE_MEDIA_CLOSE:
+
+ case AKEYCODE_DVR:
+
+ 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:
+
+ case AKEYCODE_MANNER_MODE:
+ case AKEYCODE_3D_MODE:
+ case AKEYCODE_CONTACTS:
+ 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(int 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 bool
+IsModifierKey(int32_t keyCode)
+{
+ using mozilla::java::sdk::KeyEvent;
+ return keyCode == KeyEvent::KEYCODE_ALT_LEFT ||
+ keyCode == KeyEvent::KEYCODE_ALT_RIGHT ||
+ keyCode == KeyEvent::KEYCODE_SHIFT_LEFT ||
+ keyCode == KeyEvent::KEYCODE_SHIFT_RIGHT ||
+ keyCode == KeyEvent::KEYCODE_CTRL_LEFT ||
+ keyCode == KeyEvent::KEYCODE_CTRL_RIGHT ||
+ keyCode == KeyEvent::KEYCODE_META_LEFT ||
+ keyCode == KeyEvent::KEYCODE_META_RIGHT;
+}
+
+static Modifiers
+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);
+}
+
+static void
+InitKeyEvent(WidgetKeyboardEvent& event,
+ int32_t action, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, int64_t time, int32_t unicodeChar,
+ int32_t baseUnicodeChar, int32_t domPrintableKeyValue,
+ int32_t repeatCount, int32_t flags)
+{
+ const uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(keyCode);
+ const int32_t charCode = unicodeChar ? unicodeChar : baseUnicodeChar;
+
+ event.mModifiers = GetModifiers(metaState);
+
+ if (event.mMessage == eKeyPress) {
+ // Android gives us \n, so filter out some control characters.
+ event.mIsChar = (charCode >= ' ');
+ event.mCharCode = event.mIsChar ? charCode : 0;
+ event.mKeyCode = event.mIsChar ? 0 : domKeyCode;
+ event.mPluginEvent.Clear();
+
+ // For keypress, if the unicode char already has modifiers applied, we
+ // don't specify extra modifiers. If UnicodeChar() != BaseUnicodeChar()
+ // it means UnicodeChar() already has modifiers applied.
+ // Note that on Android 4.x, Alt modifier isn't set when the key input
+ // causes text input even while right Alt key is pressed. However,
+ // this is necessary for Android 2.3 compatibility.
+ if (unicodeChar && unicodeChar != baseUnicodeChar) {
+ event.mModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL
+ | MODIFIER_META);
+ }
+
+ } else {
+ event.mIsChar = false;
+ event.mCharCode = 0;
+ event.mKeyCode = domKeyCode;
+
+ ANPEvent pluginEvent;
+ pluginEvent.inSize = sizeof(pluginEvent);
+ pluginEvent.eventType = kKey_ANPEventType;
+ pluginEvent.data.key.action = event.mMessage == eKeyDown
+ ? kDown_ANPKeyAction : kUp_ANPKeyAction;
+ pluginEvent.data.key.nativeCode = keyCode;
+ pluginEvent.data.key.virtualCode = domKeyCode;
+ pluginEvent.data.key.unichar = charCode;
+ pluginEvent.data.key.modifiers =
+ (metaState & sdk::KeyEvent::META_SHIFT_MASK
+ ? kShift_ANPKeyModifier : 0) |
+ (metaState & sdk::KeyEvent::META_ALT_MASK
+ ? kAlt_ANPKeyModifier : 0);
+ pluginEvent.data.key.repeatCount = repeatCount;
+ event.mPluginEvent.Copy(pluginEvent);
+ }
+
+ event.mIsRepeat =
+ (event.mMessage == eKeyDown || event.mMessage == eKeyPress) &&
+ ((flags & sdk::KeyEvent::FLAG_LONG_PRESS) || repeatCount);
+
+ event.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex(
+ keyCode, action, domPrintableKeyValue);
+ event.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(scanCode);
+
+ if (event.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+ domPrintableKeyValue) {
+ event.mKeyValue = char16_t(domPrintableKeyValue);
+ }
+
+ event.mLocation =
+ WidgetKeyboardEvent::ComputeLocationFromCodeValue(event.mCodeNameIndex);
+ event.mTime = time;
+}
+
+void
+nsWindow::GeckoViewSupport::OnKeyEvent(int32_t aAction, int32_t aKeyCode,
+ int32_t aScanCode, int32_t aMetaState, int64_t aTime,
+ int32_t aUnicodeChar, int32_t aBaseUnicodeChar,
+ int32_t aDomPrintableKeyValue, int32_t aRepeatCount, int32_t aFlags,
+ bool aIsSynthesizedImeKey, jni::Object::Param originalEvent)
+{
+ RefPtr<nsWindow> kungFuDeathGrip(&window);
+ if (!aIsSynthesizedImeKey) {
+ window.UserActivity();
+ window.RemoveIMEComposition();
+ }
+
+ EventMessage msg;
+ if (aAction == sdk::KeyEvent::ACTION_DOWN) {
+ msg = eKeyDown;
+ } else if (aAction == sdk::KeyEvent::ACTION_UP) {
+ msg = eKeyUp;
+ } else if (aAction == 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 {
+ ALOG("Unknown key action event!");
+ return;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetKeyboardEvent event(true, msg, &window);
+ window.InitEvent(event, nullptr);
+ InitKeyEvent(event, aAction, aKeyCode, aScanCode, aMetaState, aTime,
+ aUnicodeChar, aBaseUnicodeChar, aDomPrintableKeyValue,
+ aRepeatCount, aFlags);
+
+ 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(
+ mozilla::UniquePtr<WidgetEvent>(event.Duplicate()));
+
+ } else {
+ window.DispatchEvent(&event, status);
+
+ if (window.Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
+ // Skip default processing.
+ return;
+ }
+
+ mEditable->OnDefaultKeyEvent(originalEvent);
+ }
+
+ if (msg != eKeyDown || IsModifierKey(aKeyCode)) {
+ // Skip sending key press event.
+ return;
+ }
+
+ WidgetKeyboardEvent pressEvent(true, eKeyPress, &window);
+ window.InitEvent(pressEvent, nullptr);
+ InitKeyEvent(pressEvent, aAction, aKeyCode, aScanCode, aMetaState, aTime,
+ aUnicodeChar, aBaseUnicodeChar, aDomPrintableKeyValue,
+ aRepeatCount, aFlags);
+
+ if (aIsSynthesizedImeKey) {
+ mIMEKeyEvents.AppendElement(
+ mozilla::UniquePtr<WidgetEvent>(pressEvent.Duplicate()));
+ } else {
+ window.DispatchEvent(&pressEvent, status);
+ }
+}
+
+#ifdef DEBUG_ANDROID_IME
+#define ALOGIME(args...) ALOG(args)
+#else
+#define ALOGIME(args...) ((void)0)
+#endif
+
+static nscolor
+ConvertAndroidColor(uint32_t aArgb)
+{
+ return NS_RGBA((aArgb & 0x00ff0000) >> 16,
+ (aArgb & 0x0000ff00) >> 8,
+ (aArgb & 0x000000ff),
+ (aArgb & 0xff000000) >> 24);
+}
+
+/*
+ * Get the current composition object, if any.
+ */
+RefPtr<mozilla::TextComposition>
+nsWindow::GetIMEComposition()
+{
+ MOZ_ASSERT(this == FindTopLevel());
+ return mozilla::IMEStateManager::GetTextCompositionFor(this);
+}
+
+/*
+ Remove the composition but leave the text content as-is
+*/
+void
+nsWindow::RemoveIMEComposition(RemoveIMECompositionFlag aFlag)
+{
+ // Remove composition on Gecko side
+ const RefPtr<mozilla::TextComposition> composition(GetIMEComposition());
+ if (!composition) {
+ return;
+ }
+
+ RefPtr<nsWindow> kungFuDeathGrip(this);
+
+ WidgetCompositionEvent compositionCommitEvent(
+ true, eCompositionCommit, this);
+ if (aFlag == COMMIT_IME_COMPOSITION) {
+ compositionCommitEvent.mMessage = eCompositionCommitAsIs;
+ }
+ InitEvent(compositionCommitEvent, nullptr);
+ DispatchEvent(&compositionCommitEvent);
+}
+
+/*
+ * Send dummy key events for pages that are unaware of input events,
+ * to provide web compatibility for pages that depend on key events.
+ * Our dummy key events have 0 as the keycode.
+ */
+void
+nsWindow::GeckoViewSupport::SendIMEDummyKeyEvents()
+{
+ WidgetKeyboardEvent downEvent(true, eKeyDown, &window);
+ window.InitEvent(downEvent, nullptr);
+ MOZ_ASSERT(downEvent.mKeyCode == 0);
+ window.DispatchEvent(&downEvent);
+
+ WidgetKeyboardEvent upEvent(true, eKeyUp, &window);
+ window.InitEvent(upEvent, nullptr);
+ MOZ_ASSERT(upEvent.mKeyCode == 0);
+ window.DispatchEvent(&upEvent);
+}
+
+void
+nsWindow::GeckoViewSupport::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;
+ }
+ // 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
+nsWindow::GeckoViewSupport::PostFlushIMEChanges()
+{
+ if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) {
+ // Already posted
+ return;
+ }
+
+ // Keep a strong reference to the window to keep 'this' alive.
+ RefPtr<nsWindow> window(&this->window);
+
+ nsAppShell::PostEvent([this, window] {
+ if (!window->Destroyed()) {
+ FlushIMEChanges();
+ }
+ });
+}
+
+void
+nsWindow::GeckoViewSupport::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);
+
+ nsCOMPtr<nsISelection> imeSelection;
+ nsCOMPtr<nsIContent> imeRoot;
+
+ // If we are receiving notifications, we must have selection/root content.
+ MOZ_ALWAYS_SUCCEEDS(IMEStateManager::GetFocusSelectionAndRoot(
+ getter_AddRefs(imeSelection), getter_AddRefs(imeRoot)));
+
+ // Make sure we still have a valid selection/root. We can potentially get
+ // a stale selection/root if the editor becomes hidden, for example.
+ NS_ENSURE_TRUE_VOID(imeRoot->IsInComposedDoc());
+
+ RefPtr<nsWindow> kungFuDeathGrip(&window);
+ window.UserActivity();
+
+ struct TextRecord {
+ nsString text;
+ int32_t start;
+ int32_t oldEnd;
+ int32_t newEnd;
+ };
+ AutoTArray<TextRecord, 4> textTransaction;
+ if (mIMETextChanges.Length() > textTransaction.Capacity()) {
+ textTransaction.SetCapacity(mIMETextChanges.Length());
+ }
+
+ mIMETextChangedDuringFlush = false;
+
+ auto shouldAbort = [=] () -> bool {
+ if (!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, "GeckoViewSupport",
+ "Already retrying IME flush");
+ }
+ return true;
+ };
+
+ for (const IMETextChange &change : mIMETextChanges) {
+ if (change.mStart == change.mOldEnd &&
+ change.mStart == change.mNewEnd) {
+ continue;
+ }
+
+ WidgetQueryContentEvent event(true, eQueryTextContent, &window);
+
+ if (change.mNewEnd != change.mStart) {
+ window.InitEvent(event, nullptr);
+ event.InitForQueryTextContent(change.mStart,
+ change.mNewEnd - change.mStart);
+ window.DispatchEvent(&event);
+ NS_ENSURE_TRUE_VOID(event.mSucceeded);
+ NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
+ }
+
+ if (shouldAbort()) {
+ return;
+ }
+
+ textTransaction.AppendElement(
+ TextRecord{event.mReply.mString, change.mStart,
+ change.mOldEnd, change.mNewEnd});
+ }
+
+ int32_t selStart = -1;
+ int32_t selEnd = -1;
+
+ if (mIMESelectionChanged) {
+ WidgetQueryContentEvent event(true, eQuerySelectedText, &window);
+ window.InitEvent(event, nullptr);
+ window.DispatchEvent(&event);
+
+ NS_ENSURE_TRUE_VOID(event.mSucceeded);
+ NS_ENSURE_TRUE_VOID(event.mReply.mContentsRoot == imeRoot.get());
+
+ if (shouldAbort()) {
+ return;
+ }
+
+ selStart = int32_t(event.GetSelectionStart());
+ selEnd = int32_t(event.GetSelectionEnd());
+ }
+
+ 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, "GeckoViewSupport",
+ "Recovering from IME exception");
+ FlushIMEText(FLUSH_FLAG_RECOVER);
+ } else {
+ // Give up because we've already tried.
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ }
+ 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;
+ }
+ }
+
+ if (mIMESelectionChanged) {
+ mIMESelectionChanged = false;
+ mEditable->OnSelectionChange(selStart, selEnd);
+ flushOnException();
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::FlushIMEText(FlushChangesFlag aFlags)
+{
+ // 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(notification);
+
+ FlushIMEChanges(aFlags);
+}
+
+static jni::ObjectArray::LocalRef
+ConvertRectArrayToJavaRectFArray(JNIEnv* aJNIEnv, const nsTArray<LayoutDeviceIntRect>& aRects, const LayoutDeviceIntPoint& aOffset, const CSSToLayoutDeviceScale aScale)
+{
+ size_t length = aRects.Length();
+ jobjectArray rects = aJNIEnv->NewObjectArray(length, sdk::RectF::Context().ClassRef(), nullptr);
+ auto rectsRef = jni::ObjectArray::LocalRef::Adopt(aJNIEnv, rects);
+ for (size_t i = 0; i < length; i++) {
+ sdk::RectF::LocalRef rect(aJNIEnv);
+ LayoutDeviceIntRect tmp = aRects[i] + aOffset;
+ sdk::RectF::New(tmp.x / aScale.scale, tmp.y / aScale.scale,
+ (tmp.x + tmp.width) / aScale.scale,
+ (tmp.y + tmp.height) / aScale.scale,
+ &rect);
+ rectsRef->SetElement(i, rect);
+ }
+ return rectsRef;
+}
+
+void
+nsWindow::GeckoViewSupport::UpdateCompositionRects()
+{
+ const auto composition(window.GetIMEComposition());
+ if (NS_WARN_IF(!composition)) {
+ return;
+ }
+
+ uint32_t offset = composition->NativeOffsetOfStartComposition();
+ WidgetQueryContentEvent textRects(true, eQueryTextRectArray, &window);
+ textRects.InitForQueryTextRectArray(offset, composition->String().Length());
+ window.DispatchEvent(&textRects);
+
+ auto rects =
+ ConvertRectArrayToJavaRectFArray(jni::GetGeckoThreadEnv(),
+ textRects.mReply.mRectArray,
+ window.WidgetToScreenOffset(),
+ window.GetDefaultScale());
+
+ mEditable->UpdateCompositionRects(rects);
+}
+
+void
+nsWindow::GeckoViewSupport::AsyncNotifyIME(int32_t aNotification)
+{
+ // Keep a strong reference to the window to keep 'this' alive.
+ RefPtr<nsWindow> window(&this->window);
+
+ nsAppShell::PostEvent([this, window, aNotification] {
+ if (mIMEMaskEventsCount) {
+ return;
+ }
+
+ mEditable->NotifyIME(aNotification);
+ });
+}
+
+bool
+nsWindow::GeckoViewSupport::NotifyIME(const IMENotification& aIMENotification)
+{
+ MOZ_ASSERT(mEditable);
+
+ switch (aIMENotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION: {
+ ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION");
+
+ window.RemoveIMEComposition();
+
+ AsyncNotifyIME(GeckoEditableListener::
+ NOTIFY_IME_TO_COMMIT_COMPOSITION);
+ return true;
+ }
+
+ case REQUEST_TO_CANCEL_COMPOSITION: {
+ ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION");
+
+ window.RemoveIMEComposition(CANCEL_IME_COMPOSITION);
+
+ AsyncNotifyIME(GeckoEditableListener::
+ NOTIFY_IME_TO_CANCEL_COMPOSITION);
+ return true;
+ }
+
+ case NOTIFY_IME_OF_FOCUS: {
+ ALOGIME("IME: NOTIFY_IME_OF_FOCUS");
+ // Keep a strong reference to the window to keep 'this' alive.
+ RefPtr<nsWindow> window(&this->window);
+
+ // 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, window] {
+ --mIMEMaskEventsCount;
+ if (mIMEMaskEventsCount || window->Destroyed()) {
+ return;
+ }
+
+ FlushIMEText();
+
+ // IME will call requestCursorUpdates after getting context.
+ // So reset cursor update mode before getting context.
+ mIMEMonitorCursor = false;
+
+ MOZ_ASSERT(mEditable);
+ mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OF_FOCUS);
+ });
+ return true;
+ }
+
+ case NOTIFY_IME_OF_BLUR: {
+ ALOGIME("IME: NOTIFY_IME_OF_BLUR");
+
+ if (!mIMEMaskEventsCount) {
+ mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OF_BLUR);
+ }
+
+ // Mask events because we lost focus. Unmask on the next focus.
+ mIMEMaskEventsCount++;
+ return true;
+ }
+
+ case NOTIFY_IME_OF_SELECTION_CHANGE: {
+ ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE");
+
+ PostFlushIMEChanges();
+ mIMESelectionChanged = true;
+ return true;
+ }
+
+ case NOTIFY_IME_OF_TEXT_CHANGE: {
+ ALOGIME("IME: NotifyIMEOfTextChange: s=%d, oe=%d, ne=%d",
+ aIMENotification.mTextChangeData.mStartOffset,
+ aIMENotification.mTextChangeData.mRemovedEndOffset,
+ aIMENotification.mTextChangeData.mAddedEndOffset);
+
+ /* Make sure Java's selection is up-to-date */
+ PostFlushIMEChanges();
+ mIMESelectionChanged = true;
+ AddIMETextChange(IMETextChange(aIMENotification));
+ return true;
+ }
+
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: {
+ ALOGIME("IME: NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED");
+
+ // Hardware keyboard support requires each string rect.
+ if (AndroidBridge::Bridge() && AndroidBridge::Bridge()->GetAPIVersion() >= 21 && mIMEMonitorCursor) {
+ UpdateCompositionRects();
+ }
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ MOZ_ASSERT(mEditable);
+
+ ALOGIME("IME: SetInputContext: s=0x%X, 0x%X, action=0x%X, 0x%X",
+ aContext.mIMEState.mEnabled, aContext.mIMEState.mOpen,
+ aAction.mCause, aAction.mFocusChange);
+
+ // Ensure that opening the virtual keyboard is allowed for this specific
+ // InputContext depending on the content.ime.strict.policy pref
+ if (aContext.mIMEState.mEnabled != IMEState::DISABLED &&
+ aContext.mIMEState.mEnabled != IMEState::PLUGIN &&
+ Preferences::GetBool("content.ime.strict_policy", false) &&
+ !aAction.ContentGotFocusByTrustedCause() &&
+ !aAction.UserMightRequestOpenVKB()) {
+ return;
+ }
+
+ IMEState::Enabled enabled = aContext.mIMEState.mEnabled;
+
+ // Only show the virtual keyboard for plugins if mOpen is set appropriately.
+ // This avoids showing it whenever a plugin is focused. Bug 747492
+ if (aContext.mIMEState.mEnabled == IMEState::PLUGIN &&
+ aContext.mIMEState.mOpen != IMEState::OPEN) {
+ enabled = IMEState::DISABLED;
+ }
+
+ mInputContext = aContext;
+ mInputContext.mIMEState.mEnabled = enabled;
+
+ if (enabled == IMEState::ENABLED && aAction.UserMightRequestOpenVKB()) {
+ // Don't reset keyboard when we should simply open the vkb
+ mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_OPEN_VKB);
+ return;
+ }
+
+ if (mIMEUpdatingContext) {
+ return;
+ }
+
+ // Keep a strong reference to the window to keep 'this' alive.
+ RefPtr<nsWindow> window(&this->window);
+ mIMEUpdatingContext = true;
+
+ nsAppShell::PostEvent([this, window] {
+ mIMEUpdatingContext = false;
+ if (window->Destroyed()) {
+ return;
+ }
+ MOZ_ASSERT(mEditable);
+ mEditable->NotifyIMEContext(mInputContext.mIMEState.mEnabled,
+ mInputContext.mHTMLInputType,
+ mInputContext.mHTMLInputInputmode,
+ mInputContext.mActionHint);
+ });
+}
+
+InputContext
+nsWindow::GeckoViewSupport::GetInputContext()
+{
+ InputContext context = mInputContext;
+ context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ return context;
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeSynchronize()
+{
+ if (!mIMEMaskEventsCount) {
+ FlushIMEChanges();
+ }
+ mEditable->NotifyIME(GeckoEditableListener::NOTIFY_IME_REPLY_EVENT);
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeReplaceText(int32_t aStart, int32_t aEnd,
+ jni::String::Param aText)
+{
+ AutoIMESynchronize as(this);
+
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused; still reply to events, but don't do anything else.
+ return;
+ }
+
+ /*
+ Replace text in Gecko thread from aStart to aEnd with the string text.
+ */
+ RefPtr<nsWindow> kungFuDeathGrip(&window);
+ nsString string(aText->ToString());
+
+ const auto composition(window.GetIMEComposition());
+ MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+ const bool composing = !mIMERanges->IsEmpty();
+
+ if (!mIMEKeyEvents.IsEmpty() || !composition ||
+ 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.
+ window.RemoveIMEComposition();
+
+ {
+ // Use text selection to set target postion(s) for
+ // insert, or replace, of text.
+ WidgetSelectionEvent event(true, eSetSelection, &window);
+ window.InitEvent(event, nullptr);
+ event.mOffset = uint32_t(aStart);
+ event.mLength = uint32_t(aEnd - aStart);
+ event.mExpandToClusterBoundary = false;
+ event.mReason = nsISelectionListener::IME_REASON;
+ window.DispatchEvent(&event);
+ }
+
+ if (!mIMEKeyEvents.IsEmpty()) {
+ nsEventStatus status;
+ for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) {
+ const auto event = static_cast<WidgetGUIEvent*>(
+ mIMEKeyEvents[i].get());
+ if (event->mMessage == eKeyPress &&
+ status == nsEventStatus_eConsumeNoDefault) {
+ MOZ_ASSERT(i > 0 &&
+ mIMEKeyEvents[i - 1]->mMessage == eKeyDown);
+ // The previous key down event resulted in eConsumeNoDefault
+ // so we should not dispatch the current key press event.
+ continue;
+ }
+ // widget for duplicated events is initially nullptr.
+ event->mWidget = &window;
+ window.DispatchEvent(event, status);
+ }
+ mIMEKeyEvents.Clear();
+ return;
+ }
+
+ if (aStart != aEnd) {
+ // Perform a deletion first.
+ WidgetContentCommandEvent event(
+ true, eContentCommandDelete, &window);
+ window.InitEvent(event, nullptr);
+ window.DispatchEvent(&event);
+ }
+
+ // Start a composition if we're not just performing a deletion.
+ if (composing || !string.IsEmpty()) {
+ WidgetCompositionEvent event(true, eCompositionStart, &window);
+ window.InitEvent(event, nullptr);
+ window.DispatchEvent(&event);
+ }
+
+ } 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;
+ AddIMETextChange(dummyChange);
+ }
+
+ // Check composition again because previous events may have destroyed our
+ // composition; in which case we should just skip the next event.
+ if (window.GetIMEComposition()) {
+ WidgetCompositionEvent event(true, eCompositionChange, &window);
+ window.InitEvent(event, nullptr);
+ event.mData = string;
+
+ if (composing) {
+ event.mRanges = new TextRangeArray();
+ mIMERanges.swap(event.mRanges);
+ } else {
+ event.mMessage = eCompositionCommit;
+ }
+
+ window.DispatchEvent(&event);
+
+ } else if (composing) {
+ // Ensure IME ranges are empty.
+ mIMERanges->Clear();
+ }
+
+ if (mInputContext.mMayBeIMEUnaware) {
+ SendIMEDummyKeyEvents();
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::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)
+{
+ 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 = 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
+nsWindow::GeckoViewSupport::OnImeUpdateComposition(int32_t aStart, int32_t aEnd)
+{
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused.
+ return;
+ }
+
+ RefPtr<nsWindow> kungFuDeathGrip(&window);
+
+ // A composition with no ranges means we want to set the selection.
+ if (mIMERanges->IsEmpty()) {
+ MOZ_ASSERT(aStart >= 0 && aEnd >= 0);
+ window.RemoveIMEComposition();
+
+ WidgetSelectionEvent selEvent(true, eSetSelection, &window);
+ window.InitEvent(selEvent, nullptr);
+
+ selEvent.mOffset = std::min(aStart, aEnd);
+ selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset;
+ selEvent.mReversed = aStart > aEnd;
+ selEvent.mExpandToClusterBoundary = false;
+
+ window.DispatchEvent(&selEvent);
+ return;
+ }
+
+ /*
+ 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.
+ */
+ const auto composition(window.GetIMEComposition());
+ MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent());
+
+ WidgetCompositionEvent event(true, eCompositionChange, &window);
+ window.InitEvent(event, nullptr);
+
+ event.mRanges = new TextRangeArray();
+ mIMERanges.swap(event.mRanges);
+
+ if (!composition ||
+ uint32_t(aStart) != composition->NativeOffsetOfStartComposition() ||
+ uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() +
+ composition->String().Length())
+ {
+ // Only start new composition if we don't have an existing one,
+ // or if the existing composition doesn't match the new one.
+ window.RemoveIMEComposition();
+
+ {
+ WidgetSelectionEvent event(true, eSetSelection, &window);
+ window.InitEvent(event, nullptr);
+ event.mOffset = uint32_t(aStart);
+ event.mLength = uint32_t(aEnd - aStart);
+ event.mExpandToClusterBoundary = false;
+ event.mReason = nsISelectionListener::IME_REASON;
+ window.DispatchEvent(&event);
+ }
+
+ {
+ WidgetQueryContentEvent queryEvent(true, eQuerySelectedText,
+ &window);
+ window.InitEvent(queryEvent, nullptr);
+ window.DispatchEvent(&queryEvent);
+ MOZ_ASSERT(queryEvent.mSucceeded);
+ event.mData = queryEvent.mReply.mString;
+ }
+
+ {
+ WidgetCompositionEvent event(true, eCompositionStart, &window);
+ window.InitEvent(event, nullptr);
+ window.DispatchEvent(&event);
+ }
+
+ } else {
+ // If the new composition matches the existing composition,
+ // reuse the old composition.
+ event.mData = composition->String();
+ }
+
+#ifdef DEBUG_ANDROID_IME
+ const NS_ConvertUTF16toUTF8 data(event.mData);
+ const char* text = data.get();
+ ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%u",
+ text, event.mData.Length(), event.mRanges->Length());
+#endif // DEBUG_ANDROID_IME
+
+ // Previous events may have destroyed our composition; bail in that case.
+ if (window.GetIMEComposition()) {
+ window.DispatchEvent(&event);
+ }
+}
+
+void
+nsWindow::GeckoViewSupport::OnImeRequestCursorUpdates(int aRequestMode)
+{
+ if (aRequestMode == IME_MONITOR_CURSOR_ONE_SHOT) {
+ UpdateCompositionRects();
+ return;
+ }
+
+ mIMEMonitorCursor = (aRequestMode == IME_MONITOR_CURSOR_START_MONITOR);
+}
+
+void
+nsWindow::UserActivity()
+{
+ if (!mIdleService) {
+ mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
+ }
+
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+}
+
+nsresult
+nsWindow::NotifyIMEInternal(const IMENotification& aIMENotification)
+{
+ MOZ_ASSERT(this == FindTopLevel());
+
+ if (!mGeckoViewSupport) {
+ // Non-GeckoView windows don't support IME operations.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mGeckoViewSupport->NotifyIME(aIMENotification)) {
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ if (!top->mGeckoViewSupport) {
+ // 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
+ top->mGeckoViewSupport->SetInputContext(aContext, aAction);
+}
+
+NS_IMETHODIMP_(InputContext)
+nsWindow::GetInputContext()
+{
+ nsWindow* top = FindTopLevel();
+ MOZ_ASSERT(top);
+
+ if (!top->mGeckoViewSupport) {
+ // 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 top->mGeckoViewSupport->GetInputContext();
+}
+
+nsIMEUpdatePreference
+nsWindow::GetIMEUpdatePreference()
+{
+ // While a plugin has focus, nsWindow for Android doesn't need any
+ // notifications.
+ if (GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
+ return nsIMEUpdatePreference();
+ }
+ return nsIMEUpdatePreference(nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
+}
+
+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 = sdk::MotionEvent::ACTION_POINTER_DOWN;
+ break;
+ case TOUCH_REMOVE:
+ // This could be turned into a ACTION_UP in Java
+ eventType = sdk::MotionEvent::ACTION_POINTER_UP;
+ break;
+ case TOUCH_CANCEL:
+ eventType = sdk::MotionEvent::ACTION_CANCEL;
+ break;
+ case TOUCH_HOVER: // not supported for now
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(mLayerViewSupport);
+ GeckoLayerClient::LocalRef client = mLayerViewSupport->GetLayerClient();
+ client->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(mLayerViewSupport);
+ GeckoLayerClient::LocalRef client = mLayerViewSupport->GetLayerClient();
+ client->SynthesizeNativeMouseEvent(aNativeMessage, aPoint.x, aPoint.y);
+
+ return NS_OK;
+}
+
+nsresult
+nsWindow::SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver)
+{
+ mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ MOZ_ASSERT(mLayerViewSupport);
+ GeckoLayerClient::LocalRef client = mLayerViewSupport->GetLayerClient();
+ client->SynthesizeNativeMouseEvent(sdk::MotionEvent::ACTION_HOVER_MOVE, aPoint.x, aPoint.y);
+
+ return NS_OK;
+}
+
+bool
+nsWindow::PreRender(WidgetRenderingContext* aContext)
+{
+ if (Destroyed()) {
+ return true;
+ }
+
+ layers::Compositor* compositor = aContext->mCompositor;
+
+ GeckoLayerClient::LocalRef client;
+
+ if (NativePtr<LayerViewSupport>::Locked lvs{mLayerViewSupport}) {
+ client = lvs->GetLayerClient();
+ }
+
+ if (compositor && client) {
+ // Android Color is ARGB which is apparently unusual.
+ compositor->SetDefaultClearColor(gfx::Color::UnusualFromARGB((uint32_t)client->ClearColor()));
+ }
+
+ return true;
+}
+void
+nsWindow::DrawWindowUnderlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+{
+ if (Destroyed()) {
+ return;
+ }
+
+ GeckoLayerClient::LocalRef client;
+
+ if (NativePtr<LayerViewSupport>::Locked lvs{mLayerViewSupport}) {
+ client = lvs->GetLayerClient();
+ }
+
+ if (!client) {
+ return;
+ }
+
+ LayerRenderer::Frame::LocalRef frame = client->CreateFrame();
+ mLayerRendererFrame = frame;
+ if (NS_WARN_IF(!mLayerRendererFrame)) {
+ return;
+ }
+
+ if (!WidgetPaintsBackground()) {
+ return;
+ }
+
+ frame->BeginDrawing();
+}
+
+void
+nsWindow::DrawWindowOverlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+{
+ PROFILER_LABEL("nsWindow", "DrawWindowOverlay",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ if (Destroyed() || NS_WARN_IF(!mLayerRendererFrame)) {
+ return;
+ }
+
+ mLayerRendererFrame->EndDrawing();
+ mLayerRendererFrame = nullptr;
+}
+
+bool
+nsWindow::WidgetPaintsBackground()
+{
+ static bool sWidgetPaintsBackground = true;
+ static bool sWidgetPaintsBackgroundPrefCached = false;
+
+ if (!sWidgetPaintsBackgroundPrefCached) {
+ sWidgetPaintsBackgroundPrefCached = true;
+ mozilla::Preferences::AddBoolVarCache(&sWidgetPaintsBackground,
+ "android.widget_paints_background",
+ true);
+ }
+
+ return sWidgetPaintsBackground;
+}
+
+bool
+nsWindow::NeedsPaint()
+{
+ if (!mLayerViewSupport || mLayerViewSupport->CompositorPaused() ||
+ // FindTopLevel() != nsWindow::TopWindow() ||
+ !GetLayerManager(nullptr)) {
+ return false;
+ }
+ return nsIWidget::NeedsPaint();
+}
+
+void
+nsWindow::ConfigureAPZControllerThread()
+{
+ APZThreadUtils::SetControllerThread(nullptr);
+}
+
+already_AddRefed<GeckoContentController>
+nsWindow::CreateRootContentController()
+{
+ RefPtr<GeckoContentController> controller = new AndroidContentController(this, mAPZEventState, mAPZC);
+ return controller.forget();
+}
+
+uint32_t
+nsWindow::GetMaxTouchPoints() const
+{
+ return GeckoAppShell::GetMaxTouchPoints();
+}
+
+void
+nsWindow::UpdateZoomConstraints(const uint32_t& aPresShellId,
+ const FrameMetrics::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints)
+{
+ nsBaseWidget::UpdateZoomConstraints(aPresShellId, aViewId, aConstraints);
+}
+
+CompositorBridgeParent*
+nsWindow::GetCompositorBridgeParent() const
+{
+ return mCompositorSession ? mCompositorSession->GetInProcessBridge() : nullptr;
+}
+
+already_AddRefed<nsIScreen>
+nsWindow::GetWidgetScreen()
+{
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ MOZ_ASSERT(screenMgr, "Failed to get nsIScreenManager");
+
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->ScreenForId(mScreenId, getter_AddRefs(screen));
+
+ return screen.forget();
+}
+
+jni::DependentRef<java::GeckoLayerClient>
+nsWindow::GetLayerClient()
+{
+ if (NativePtr<LayerViewSupport>::Locked lvs{mLayerViewSupport}) {
+ return lvs->GetLayerClient().Get();
+ }
+ return nullptr;
+}
diff --git a/widget/android/nsWindow.h b/widget/android/nsWindow.h
new file mode 100644
index 000000000..f3d7566f7
--- /dev/null
+++ b/widget/android/nsWindow.h
@@ -0,0 +1,289 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * vim: set sw=4 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 "nsIIdleServiceInternal.h"
+#include "nsTArray.h"
+#include "AndroidJavaWrappers.h"
+#include "GeneratedJNIWrappers.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/UniquePtr.h"
+
+struct ANPEvent;
+
+namespace mozilla {
+ class TextComposition;
+ class WidgetTouchEvent;
+
+ namespace layers {
+ class CompositorBridgeParent;
+ class CompositorBridgeChild;
+ class LayerManager;
+ class APZCTreeManager;
+ }
+}
+
+class nsWindow : public nsBaseWidget
+{
+private:
+ virtual ~nsWindow();
+
+public:
+ using nsBaseWidget::GetLayerManager;
+
+ nsWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ static void InitNatives();
+ void SetScreenId(uint32_t aScreenId) { mScreenId = aScreenId; }
+
+private:
+ uint32_t mScreenId;
+
+ // An Event subclass that guards against stale events.
+ template<typename Lambda,
+ bool IsStatic = Lambda::isStatic,
+ typename InstanceType = typename Lambda::ThisArgType,
+ class Impl = typename Lambda::TargetClass>
+ class WindowEvent;
+
+ // Smart pointer for holding a pointer back to the nsWindow inside a native
+ // object class. The nsWindow pointer is automatically cleared when the
+ // nsWindow is destroyed, and a WindowPtr<Impl>::Locked class is provided
+ // for thread-safe access to the nsWindow pointer off of the Gecko thread.
+ template<class Impl> class WindowPtr;
+
+ // Smart pointer for holding a pointer to a native object class. The
+ // pointer is automatically cleared when the object is destroyed.
+ template<class Impl>
+ class NativePtr final
+ {
+ friend WindowPtr<Impl>;
+
+ static const char sName[];
+
+ WindowPtr<Impl>* mPtr;
+ Impl* mImpl;
+ mozilla::Mutex mImplLock;
+
+ public:
+ class Locked;
+
+ NativePtr() : mPtr(nullptr), mImpl(nullptr), mImplLock(sName) {}
+ ~NativePtr() { MOZ_ASSERT(!mPtr); }
+
+ operator Impl*() const
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mImpl;
+ }
+
+ Impl* operator->() const { return operator Impl*(); }
+
+ template<class Instance, typename... Args>
+ void Attach(Instance aInstance, nsWindow* aWindow, Args&&... aArgs);
+ void Detach();
+ };
+
+ class LayerViewSupport;
+ // Object that implements native LayerView calls.
+ // Owned by the Java LayerView instance.
+ NativePtr<LayerViewSupport> mLayerViewSupport;
+
+ class NPZCSupport;
+ // Object that implements native NativePanZoomController calls.
+ // Owned by the Java NativePanZoomController instance.
+ NativePtr<NPZCSupport> mNPZCSupport;
+
+ class GeckoViewSupport;
+ // Object that implements native GeckoView calls and associated states.
+ // nullptr for nsWindows that were not opened from GeckoView.
+ // Because other objects get destroyed in the mGeckOViewSupport destructor,
+ // keep it last in the list, so its destructor is called first.
+ mozilla::UniquePtr<GeckoViewSupport> mGeckoViewSupport;
+
+ // Class that implements native PresentationMediaPlayerManager calls.
+ class PMPMSupport;
+
+public:
+ static nsWindow* TopWindow();
+
+ 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);
+ void SetScrollingRootContent(const bool isRootContent);
+
+ //
+ // nsIWidget
+ //
+
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) override;
+ virtual void Destroy() override;
+ NS_IMETHOD ConfigureChildren(const nsTArray<nsIWidget::Configuration>&) override;
+ NS_IMETHOD SetParent(nsIWidget* aNewParent) override;
+ virtual nsIWidget *GetParent(void) override;
+ virtual float GetDPI() override;
+ virtual double GetDefaultScaleInternal() override;
+ NS_IMETHOD Show(bool aState) override;
+ virtual bool IsVisible() const override;
+ virtual void ConstrainPosition(bool aAllowSlop,
+ int32_t *aX,
+ int32_t *aY) override;
+ NS_IMETHOD Move(double aX,
+ double aY) override;
+ NS_IMETHOD Resize(double aWidth,
+ double aHeight,
+ bool aRepaint) override;
+ NS_IMETHOD Resize(double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint) override;
+ void SetZIndex(int32_t aZIndex) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ NS_IMETHOD Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect) override;
+ NS_IMETHOD SetFocus(bool aRaise = false) override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ NS_IMETHOD 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;
+
+ NS_IMETHOD SetCursor(nsCursor aCursor) override { return NS_ERROR_NOT_IMPLEMENTED; }
+ NS_IMETHOD SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX,
+ uint32_t aHotspotY) override { return NS_ERROR_NOT_IMPLEMENTED; }
+ NS_IMETHOD SetHasTransparentBackground(bool aTransparent) { return NS_OK; }
+ NS_IMETHOD GetHasTransparentBackground(bool& aTransparent) { aTransparent = false; return NS_OK; }
+ NS_IMETHOD HideWindowChrome(bool aShouldHide) override { return NS_ERROR_NOT_IMPLEMENTED; }
+ void* GetNativeData(uint32_t aDataType) override;
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override { return NS_OK; }
+ NS_IMETHOD SetIcon(const nsAString& aIconSpec) override { return NS_OK; }
+ NS_IMETHOD GetAttention(int32_t aCycleCount) override { return NS_ERROR_NOT_IMPLEMENTED; }
+ NS_IMETHOD BeginResizeDrag(mozilla::WidgetGUIEvent* aEvent,
+ int32_t aHorizontal,
+ int32_t aVertical) override
+ {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override;
+ virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
+
+ void SetSelectionDragState(bool aState);
+ LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+
+ virtual bool NeedsPaint() override;
+ virtual bool PreRender(mozilla::widget::WidgetRenderingContext* aContext) override;
+ virtual void DrawWindowUnderlay(mozilla::widget::WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect) override;
+ virtual void DrawWindowOverlay(mozilla::widget::WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect) override;
+
+ virtual bool WidgetPaintsBackground() override;
+
+ virtual uint32_t GetMaxTouchPoints() const override;
+
+ void UpdateZoomConstraints(const uint32_t& aPresShellId,
+ const FrameMetrics::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;
+
+ CompositorBridgeParent* GetCompositorBridgeParent() const;
+
+ mozilla::jni::DependentRef<mozilla::java::GeckoLayerClient> GetLayerClient();
+
+protected:
+ void BringToFront();
+ nsWindow *FindTopLevel();
+ bool IsTopLevel();
+
+ RefPtr<mozilla::TextComposition> GetIMEComposition();
+ enum RemoveIMECompositionFlag {
+ CANCEL_IME_COMPOSITION,
+ COMMIT_IME_COMPOSITION
+ };
+ void RemoveIMEComposition(RemoveIMECompositionFlag aFlag = COMMIT_IME_COMPOSITION);
+
+ void ConfigureAPZControllerThread() override;
+ void DispatchHitTest(const mozilla::WidgetTouchEvent& aEvent);
+
+ already_AddRefed<GeckoContentController> CreateRootContentController() override;
+
+ // Call this function when the users activity is the direct cause of an
+ // event (like a keypress or mouse click).
+ void UserActivity();
+
+ bool mIsVisible;
+ nsTArray<nsWindow*> mChildren;
+ nsWindow* mParent;
+
+ double mStartDist;
+ double mLastDist;
+
+ nsCOMPtr<nsIIdleServiceInternal> mIdleService;
+
+ bool mAwaitingFullScreen;
+ bool mIsFullScreen;
+
+ virtual nsresult NotifyIMEInternal(
+ const IMENotification& aIMENotification) override;
+
+ 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(int aCompositorWidth, int aCompositorHeight);
+ void RedrawAll();
+
+ mozilla::java::LayerRenderer::Frame::GlobalRef mLayerRendererFrame;
+};
+
+#endif /* NSWINDOW_H_ */
diff --git a/widget/cocoa/ComplexTextInputPanel.h b/widget/cocoa/ComplexTextInputPanel.h
new file mode 100644
index 000000000..648e6d911
--- /dev/null
+++ b/widget/cocoa/ComplexTextInputPanel.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Modified by Josh Aas of Mozilla Corporation.
+ */
+
+#ifndef ComplexTextInputPanel_h_
+#define ComplexTextInputPanel_h_
+
+#include "nsString.h"
+#include "npapi.h"
+
+class ComplexTextInputPanel
+{
+public:
+ static ComplexTextInputPanel* GetSharedComplexTextInputPanel();
+ virtual void PlacePanel(int32_t x, int32_t y) = 0; // Bottom left coordinate of plugin in screen coords
+ virtual void InterpretKeyEvent(void* aEvent, nsAString& aOutText) = 0;
+ virtual bool IsInComposition() = 0;
+ virtual void* GetInputContext() = 0;
+ virtual void CancelComposition() = 0;
+
+protected:
+ virtual ~ComplexTextInputPanel() {};
+};
+
+#endif // ComplexTextInputPanel_h_
diff --git a/widget/cocoa/ComplexTextInputPanel.mm b/widget/cocoa/ComplexTextInputPanel.mm
new file mode 100644
index 000000000..a4b58955e
--- /dev/null
+++ b/widget/cocoa/ComplexTextInputPanel.mm
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Modified by Josh Aas of Mozilla Corporation.
+ */
+
+#import "ComplexTextInputPanel.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include <algorithm>
+#include "mozilla/Preferences.h"
+#include "nsChildView.h"
+
+using namespace mozilla;
+
+extern "C" OSStatus TSMProcessRawKeyEvent(EventRef anEvent);
+
+#define kInputWindowHeight 20
+
+@interface ComplexTextInputPanelImpl : NSPanel {
+ NSTextView *mInputTextView;
+}
+
++ (ComplexTextInputPanelImpl*)sharedComplexTextInputPanelImpl;
+
+- (NSTextInputContext*)inputContext;
+- (void)interpretKeyEvent:(NSEvent*)event string:(NSString**)string;
+- (void)cancelComposition;
+- (BOOL)inComposition;
+
+// This places the text input panel fully onscreen and below the lower left
+// corner of the focused plugin.
+- (void)adjustTo:(NSPoint)point;
+
+@end
+
+@implementation ComplexTextInputPanelImpl
+
++ (ComplexTextInputPanelImpl*)sharedComplexTextInputPanelImpl
+{
+ static ComplexTextInputPanelImpl *sComplexTextInputPanelImpl;
+ if (!sComplexTextInputPanelImpl)
+ sComplexTextInputPanelImpl = [[ComplexTextInputPanelImpl alloc] init];
+ return sComplexTextInputPanelImpl;
+}
+
+- (id)init
+{
+ // In the original Apple code the style mask is given by a function which is not open source.
+ // What could possibly be worth hiding in that function, I do not know.
+ // Courtesy of gdb: stylemask: 011000011111, 0x61f
+ self = [super initWithContentRect:NSZeroRect styleMask:0x61f backing:NSBackingStoreBuffered defer:YES];
+ if (!self)
+ return nil;
+
+ // Set the frame size.
+ NSRect visibleFrame = [[NSScreen mainScreen] visibleFrame];
+ NSRect frame = NSMakeRect(visibleFrame.origin.x, visibleFrame.origin.y, visibleFrame.size.width, kInputWindowHeight);
+
+ [self setFrame:frame display:NO];
+
+ mInputTextView = [[NSTextView alloc] initWithFrame:[self.contentView frame]];
+ mInputTextView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable | NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin | NSViewMinYMargin;
+
+ NSScrollView* scrollView = [[NSScrollView alloc] initWithFrame:[self.contentView frame]];
+ scrollView.documentView = mInputTextView;
+ self.contentView = scrollView;
+ [scrollView release];
+
+ [self setFloatingPanel:YES];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(keyboardInputSourceChanged:)
+ name:NSTextInputContextKeyboardSelectionDidChangeNotification
+ object:nil];
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [mInputTextView release];
+
+ [super dealloc];
+}
+
+- (void)keyboardInputSourceChanged:(NSNotification *)notification
+{
+ static int8_t sDoCancel = -1;
+ if (!sDoCancel || ![self inComposition]) {
+ return;
+ }
+ if (sDoCancel < 0) {
+ bool cancelComposition = false;
+ static const char* kPrefName =
+ "ui.plugin.cancel_composition_at_input_source_changed";
+ nsresult rv = Preferences::GetBool(kPrefName, &cancelComposition);
+ NS_ENSURE_SUCCESS(rv, );
+ sDoCancel = cancelComposition ? 1 : 0;
+ }
+ if (sDoCancel) {
+ [self cancelComposition];
+ }
+}
+
+- (void)interpretKeyEvent:(NSEvent*)event string:(NSString**)string
+{
+ *string = nil;
+
+ if (![[mInputTextView inputContext] handleEvent:event]) {
+ return;
+ }
+
+ if ([mInputTextView hasMarkedText]) {
+ // Don't show the input method window for dead keys
+ if ([[event characters] length] > 0) {
+ [self orderFront:nil];
+ }
+ return;
+ } else {
+ [self orderOut:nil];
+
+ NSString *text = [[mInputTextView textStorage] string];
+ if ([text length] > 0) {
+ *string = [[text copy] autorelease];
+ }
+ }
+
+ [mInputTextView setString:@""];
+}
+
+- (NSTextInputContext*)inputContext
+{
+ return [mInputTextView inputContext];
+}
+
+- (void)cancelComposition
+{
+ [mInputTextView setString:@""];
+ [self orderOut:nil];
+}
+
+- (BOOL)inComposition
+{
+ return [mInputTextView hasMarkedText];
+}
+
+- (void)adjustTo:(NSPoint)point
+{
+ NSRect selfRect = [self frame];
+ NSRect rect = NSMakeRect(point.x,
+ point.y - selfRect.size.height,
+ 500,
+ selfRect.size.height);
+
+ // Adjust to screen.
+ NSRect screenRect = [[NSScreen mainScreen] visibleFrame];
+ if (rect.origin.x < screenRect.origin.x) {
+ rect.origin.x = screenRect.origin.x;
+ }
+ if (rect.origin.y < screenRect.origin.y) {
+ rect.origin.y = screenRect.origin.y;
+ }
+ CGFloat xMostOfScreen = screenRect.origin.x + screenRect.size.width;
+ CGFloat yMostOfScreen = screenRect.origin.y + screenRect.size.height;
+ CGFloat xMost = rect.origin.x + rect.size.width;
+ CGFloat yMost = rect.origin.y + rect.size.height;
+ if (xMostOfScreen < xMost) {
+ rect.origin.x -= xMost - xMostOfScreen;
+ }
+ if (yMostOfScreen < yMost) {
+ rect.origin.y -= yMost - yMostOfScreen;
+ }
+
+ [self setFrame:rect display:[self isVisible]];
+}
+
+@end
+
+class ComplexTextInputPanelPrivate : public ComplexTextInputPanel
+{
+public:
+ ComplexTextInputPanelPrivate();
+
+ virtual void InterpretKeyEvent(void* aEvent, nsAString& aOutText);
+ virtual bool IsInComposition();
+ virtual void PlacePanel(int32_t x, int32_t y);
+ virtual void* GetInputContext() { return [mPanel inputContext]; }
+ virtual void CancelComposition() { [mPanel cancelComposition]; }
+
+private:
+ ~ComplexTextInputPanelPrivate();
+ ComplexTextInputPanelImpl* mPanel;
+};
+
+ComplexTextInputPanelPrivate::ComplexTextInputPanelPrivate()
+{
+ mPanel = [[ComplexTextInputPanelImpl alloc] init];
+}
+
+ComplexTextInputPanelPrivate::~ComplexTextInputPanelPrivate()
+{
+ [mPanel release];
+}
+
+ComplexTextInputPanel*
+ComplexTextInputPanel::GetSharedComplexTextInputPanel()
+{
+ static ComplexTextInputPanelPrivate *sComplexTextInputPanelPrivate;
+ if (!sComplexTextInputPanelPrivate) {
+ sComplexTextInputPanelPrivate = new ComplexTextInputPanelPrivate();
+ }
+ return sComplexTextInputPanelPrivate;
+}
+
+void
+ComplexTextInputPanelPrivate::InterpretKeyEvent(void* aEvent, nsAString& aOutText)
+{
+ NSString* textString = nil;
+ [mPanel interpretKeyEvent:(NSEvent*)aEvent string:&textString];
+
+ if (textString) {
+ nsCocoaUtils::GetStringForNSString(textString, aOutText);
+ }
+}
+
+bool
+ComplexTextInputPanelPrivate::IsInComposition()
+{
+ return !![mPanel inComposition];
+}
+
+void
+ComplexTextInputPanelPrivate::PlacePanel(int32_t x, int32_t y)
+{
+ [mPanel adjustTo:NSMakePoint(x, y)];
+}
diff --git a/widget/cocoa/CustomCocoaEvents.h b/widget/cocoa/CustomCocoaEvents.h
new file mode 100644
index 000000000..0043f0d69
--- /dev/null
+++ b/widget/cocoa/CustomCocoaEvents.h
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file defines constants to be used in the "subtype" field of
+ * NSApplicationDefined type NSEvents.
+ */
+
+#ifndef WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_
+#define WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_
+
+// Empty event, just used for prodding the event loop into responding.
+const short kEventSubtypeNone = 0;
+// Tracer event, used for timing the event loop responsiveness.
+const short kEventSubtypeTrace = 1;
+
+#endif /* WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_ */
diff --git a/widget/cocoa/GfxInfo.h b/widget/cocoa/GfxInfo.h
new file mode 100644
index 000000000..05bdad158
--- /dev/null
+++ b/widget/cocoa/GfxInfo.h
@@ -0,0 +1,95 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __mozilla_widget_GfxInfo_h__
+#define __mozilla_widget_GfxInfo_h__
+
+#include "GfxInfoBase.h"
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo : public GfxInfoBase
+{
+public:
+
+ GfxInfo();
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool *aD2DEnabled) override;
+ NS_IMETHOD GetDWriteEnabled(bool *aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString & aDwriteVersion) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString & aCleartypeParams) override;
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active) override;
+
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+ virtual nsresult Init() override;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ virtual uint32_t OperatingSystemVersion() override { return mOSXVersion; }
+
+ nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override;
+
+protected:
+
+ virtual ~GfxInfo() {}
+
+ virtual nsresult GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString &aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+private:
+
+ void GetDeviceInfo();
+ void GetSelectedCityInfo();
+ void AddCrashReportAnnotations();
+
+ nsString mAdapterRAMString;
+ nsString mDeviceID;
+ nsString mDriverVersion;
+ nsString mDriverDate;
+ nsString mDeviceKey;
+
+ nsString mAdapterVendorID;
+ nsString mAdapterDeviceID;
+
+ uint32_t mOSXVersion;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/cocoa/GfxInfo.mm b/widget/cocoa/GfxInfo.mm
new file mode 100644
index 000000000..6789ae8b2
--- /dev/null
+++ b/widget/cocoa/GfxInfo.mm
@@ -0,0 +1,433 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <OpenGL/OpenGL.h>
+#include <OpenGL/CGLRenderers.h>
+
+#include "mozilla/ArrayUtils.h"
+
+#include "GfxInfo.h"
+#include "nsUnicharUtils.h"
+#include "nsCocoaFeatures.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+
+#import <Foundation/Foundation.h>
+#import <IOKit/IOKitLib.h>
+#import <Cocoa/Cocoa.h>
+
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#include "nsICrashReporter.h"
+#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+GfxInfo::GfxInfo()
+{
+}
+
+static OperatingSystem
+OSXVersionToOperatingSystem(uint32_t aOSXVersion)
+{
+ if (nsCocoaFeatures::ExtractMajorVersion(aOSXVersion) == 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;
+ }
+ }
+
+ return OperatingSystem::Unknown;
+}
+// The following three functions are derived from Chromium code
+static CFTypeRef SearchPortForProperty(io_registry_entry_t dspPort,
+ CFStringRef propertyName)
+{
+ return IORegistryEntrySearchCFProperty(dspPort,
+ kIOServicePlane,
+ propertyName,
+ kCFAllocatorDefault,
+ kIORegistryIterateRecursively |
+ kIORegistryIterateParents);
+}
+
+static uint32_t IntValueOfCFData(CFDataRef d)
+{
+ uint32_t value = 0;
+
+ if (d) {
+ const uint32_t *vp = reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(d));
+ if (vp != NULL)
+ value = *vp;
+ }
+
+ return value;
+}
+
+void
+GfxInfo::GetDeviceInfo()
+{
+ io_registry_entry_t dsp_port = CGDisplayIOServicePort(kCGDirectMainDisplay);
+ CFTypeRef vendor_id_ref = SearchPortForProperty(dsp_port, CFSTR("vendor-id"));
+ if (vendor_id_ref) {
+ mAdapterVendorID.AppendPrintf("0x%04x", IntValueOfCFData((CFDataRef)vendor_id_ref));
+ CFRelease(vendor_id_ref);
+ }
+ CFTypeRef device_id_ref = SearchPortForProperty(dsp_port, CFSTR("device-id"));
+ if (device_id_ref) {
+ mAdapterDeviceID.AppendPrintf("0x%04x", IntValueOfCFData((CFDataRef)device_id_ref));
+ CFRelease(device_id_ref);
+ }
+}
+
+nsresult
+GfxInfo::Init()
+{
+ nsresult rv = GfxInfoBase::Init();
+
+ // Calling CGLQueryRendererInfo causes us to switch to the discrete GPU
+ // even when we don't want to. We'll avoid doing so for now and just
+ // use the device ids.
+
+ GetDeviceInfo();
+
+ AddCrashReportAnnotations();
+
+ mOSXVersion = nsCocoaFeatures::OSXVersion();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetD2DEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString DWriteVersion; */
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString & aDwriteVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString cleartypeParameters; */
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString & aCleartypeParams)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDescription; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString & aAdapterDescription)
+{
+ aAdapterDescription.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDescription2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString & aAdapterDescription)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterRAM; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(nsAString & aAdapterRAM)
+{
+ aAdapterRAM = mAdapterRAMString;
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterRAM2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & aAdapterRAM)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDriver; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString & aAdapterDriver)
+{
+ aAdapterDriver.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriver2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString & aAdapterDriver)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDriverVersion; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion)
+{
+ aAdapterDriverVersion.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverVersion2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDriverDate; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString & aAdapterDriverDate)
+{
+ aAdapterDriverDate.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverDate2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString & aAdapterDriverDate)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterVendorID; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString & aAdapterVendorID)
+{
+ aAdapterVendorID = mAdapterVendorID;
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterVendorID2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDeviceID; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID)
+{
+ aAdapterDeviceID = mAdapterDeviceID;
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDeviceID2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString & aAdapterDeviceID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterSubsysID; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterSubsysID2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute boolean isGPU2Active; */
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active)
+{
+ return NS_ERROR_FAILURE;
+}
+
+void
+GfxInfo::AddCrashReportAnnotations()
+{
+#if defined(MOZ_CRASHREPORTER)
+ 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(NS_LITERAL_CSTRING("AdapterVendorID"),
+ narrowVendorID);
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDeviceID"),
+ narrowDeviceID);
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDriverVersion"),
+ narrowDriverVersion);
+ /* Add an App Note for now so that we get the data immediately. These
+ * can go away after we store the above in the socorro db */
+ nsAutoCString note;
+ /* AppendPrintf only supports 32 character strings, mrghh. */
+ note.Append("AdapterVendorID: ");
+ note.Append(narrowVendorID);
+ note.Append(", AdapterDeviceID: ");
+ note.Append(narrowDeviceID);
+ CrashReporter::AppendAppNotesToCrashReport(note);
+#endif
+}
+
+// We don't support checking driver versions on Mac.
+#define IMPLEMENT_MAC_DRIVER_BLOCKLIST(os, vendor, device, features, blockOn, ruleId) \
+ APPEND_TO_DRIVER_BLOCKLIST(os, vendor, device, features, blockOn, \
+ DRIVER_COMPARISON_IGNORED, V(0,0,0,0), ruleId, "")
+
+
+const nsTArray<GfxDriverInfo>&
+GfxInfo::GetGfxDriverInfo()
+{
+ if (!mDriverInfo->Length()) {
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_WEBGL_MSAA, nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION, "FEATURE_FAILURE_MAC_ATI_NO_MSAA");
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(RadeonX1000),
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, "FEATURE_FAILURE_MAC_RADEONX1000_NO_TEXTURE2D");
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(Geforce7300GT),
+ nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, "FEATURE_FAILURE_MAC_7300_NO_WEBGL");
+ }
+ return *mDriverInfo;
+}
+
+nsresult
+GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
+ int32_t* aStatus,
+ nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ OperatingSystem os = OSXVersionToOperatingSystem(mOSXVersion);
+ if (aOS)
+ *aOS = os;
+
+ // Don't evaluate special cases when we're evaluating the downloaded blocklist.
+ if (!aDriverInfo.Length()) {
+ if (aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA) {
+ // Blacklist all ATI cards on OSX, except for
+ // 0x6760 and 0x9488
+ if (mAdapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorATI), nsCaseInsensitiveStringComparator()) &&
+ (mAdapterDeviceID.LowerCaseEqualsLiteral("0x6760") ||
+ mAdapterDeviceID.LowerCaseEqualsLiteral("0x9488"))) {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ return NS_OK;
+ }
+ } else if (aFeature == nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION) {
+ // See bug 1249659
+ switch(os) {
+ case OperatingSystem::OSX10_5:
+ case OperatingSystem::OSX10_6:
+ case OperatingSystem::OSX10_7:
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
+ aFailureId = "FEATURE_FAILURE_CANVAS_OSX_VERSION";
+ break;
+ default:
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ break;
+ }
+ return NS_OK;
+ }
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
+}
+
+nsresult
+GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray)
+{
+ // Getting the refresh rate is a little hard on OS X. We could use
+ // CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little
+ // involved. Ideally we could query it from vsync. For now, we leave it out.
+ int32_t deviceCount = 0;
+ for (NSScreen* screen in [NSScreen screens]) {
+ NSRect rect = [screen frame];
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+ JS::Rooted<JS::Value> screenWidth(aCx, JS::Int32Value((int)rect.size.width));
+ JS_SetProperty(aCx, obj, "screenWidth", screenWidth);
+
+ JS::Rooted<JS::Value> screenHeight(aCx, JS::Int32Value((int)rect.size.height));
+ JS_SetProperty(aCx, obj, "screenHeight", screenHeight);
+
+ JS::Rooted<JS::Value> scale(aCx, JS::NumberValue(nsCocoaUtils::GetBackingScaleFactor(screen)));
+ JS_SetProperty(aCx, obj, "scale", scale);
+
+ JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
+ JS_SetElement(aCx, aOutArray, deviceCount++, element);
+ }
+ return NS_OK;
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+/* void spoofVendorID (in DOMString aVendorID); */
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString & aVendorID)
+{
+ mAdapterVendorID = aVendorID;
+ return NS_OK;
+}
+
+/* void spoofDeviceID (in unsigned long aDeviceID); */
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString & aDeviceID)
+{
+ mAdapterDeviceID = aDeviceID;
+ return NS_OK;
+}
+
+/* void spoofDriverVersion (in DOMString aDriverVersion); */
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString & aDriverVersion)
+{
+ mDriverVersion = aDriverVersion;
+ return NS_OK;
+}
+
+/* void spoofOSVersion (in unsigned long aVersion); */
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion)
+{
+ mOSXVersion = aVersion;
+ return NS_OK;
+}
+
+#endif
diff --git a/widget/cocoa/NativeKeyBindings.h b/widget/cocoa/NativeKeyBindings.h
new file mode 100644
index 000000000..d1ba2c370
--- /dev/null
+++ b/widget/cocoa/NativeKeyBindings.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_NativeKeyBindings_h_
+#define mozilla_widget_NativeKeyBindings_h_
+
+#import <Cocoa/Cocoa.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsDataHashtable.h"
+#include "nsIWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+typedef nsDataHashtable<nsPtrHashKey<struct objc_selector>, CommandInt>
+ SelectorCommandHashtable;
+
+class NativeKeyBindings final
+{
+ typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType;
+ typedef nsIWidget::DoCommandCallback DoCommandCallback;
+
+public:
+ static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
+ static void Shutdown();
+
+ void Init(NativeKeyBindingsType aType);
+
+ bool Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData);
+
+private:
+ NativeKeyBindings();
+
+ SelectorCommandHashtable mSelectorToCommand;
+
+ static NativeKeyBindings* sInstanceForSingleLineEditor;
+ static NativeKeyBindings* sInstanceForMultiLineEditor;
+}; // NativeKeyBindings
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_NativeKeyBindings_h_
diff --git a/widget/cocoa/NativeKeyBindings.mm b/widget/cocoa/NativeKeyBindings.mm
new file mode 100644
index 000000000..2f4ecadff
--- /dev/null
+++ b/widget/cocoa/NativeKeyBindings.mm
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NativeKeyBindings.h"
+
+#include "nsTArray.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/TextEvents.h"
+
+namespace mozilla {
+namespace widget {
+
+PRLogModuleInfo* gNativeKeyBindingsLog = nullptr;
+
+NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
+NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
+
+// static
+NativeKeyBindings*
+NativeKeyBindings::GetInstance(NativeKeyBindingsType aType)
+{
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ if (!sInstanceForSingleLineEditor) {
+ sInstanceForSingleLineEditor = new NativeKeyBindings();
+ sInstanceForSingleLineEditor->Init(aType);
+ }
+ return sInstanceForSingleLineEditor;
+ case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ if (!sInstanceForMultiLineEditor) {
+ sInstanceForMultiLineEditor = new NativeKeyBindings();
+ sInstanceForMultiLineEditor->Init(aType);
+ }
+ return sInstanceForMultiLineEditor;
+ default:
+ MOZ_CRASH("Not implemented");
+ return nullptr;
+ }
+}
+
+// static
+void
+NativeKeyBindings::Shutdown()
+{
+ delete sInstanceForSingleLineEditor;
+ sInstanceForSingleLineEditor = nullptr;
+ delete sInstanceForMultiLineEditor;
+ sInstanceForMultiLineEditor = nullptr;
+}
+
+NativeKeyBindings::NativeKeyBindings()
+{
+}
+
+#define SEL_TO_COMMAND(aSel, aCommand) \
+ mSelectorToCommand.Put( \
+ reinterpret_cast<struct objc_selector *>(@selector(aSel)), aCommand)
+
+void
+NativeKeyBindings::Init(NativeKeyBindingsType aType)
+{
+ if (!gNativeKeyBindingsLog) {
+ gNativeKeyBindingsLog = PR_NewLogModule("NativeKeyBindings");
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::Init", this));
+
+ // Many selectors have a one-to-one mapping to a Gecko command. Those mappings
+ // are registered in mSelectorToCommand.
+
+ // Selectors from NSResponder's "Responding to Action Messages" section and
+ // from NSText's "Action Methods for Editing" section
+
+ // TODO: Improves correctness of left / right meaning
+ // TODO: Add real paragraph motions
+
+ // SEL_TO_COMMAND(cancelOperation:, );
+ // SEL_TO_COMMAND(capitalizeWord:, );
+ // SEL_TO_COMMAND(centerSelectionInVisibleArea:, );
+ // SEL_TO_COMMAND(changeCaseOfLetter:, );
+ // SEL_TO_COMMAND(complete:, );
+ SEL_TO_COMMAND(copy:, CommandCopy);
+ // SEL_TO_COMMAND(copyFont:, );
+ // SEL_TO_COMMAND(copyRuler:, );
+ SEL_TO_COMMAND(cut:, CommandCut);
+ SEL_TO_COMMAND(delete:, CommandDelete);
+ SEL_TO_COMMAND(deleteBackward:, CommandDeleteCharBackward);
+ // SEL_TO_COMMAND(deleteBackwardByDecomposingPreviousCharacter:, );
+ SEL_TO_COMMAND(deleteForward:, CommandDeleteCharForward);
+
+ // TODO: deleteTo* selectors are also supposed to add text to a kill buffer
+ SEL_TO_COMMAND(deleteToBeginningOfLine:, CommandDeleteToBeginningOfLine);
+ SEL_TO_COMMAND(deleteToBeginningOfParagraph:, CommandDeleteToBeginningOfLine);
+ SEL_TO_COMMAND(deleteToEndOfLine:, CommandDeleteToEndOfLine);
+ SEL_TO_COMMAND(deleteToEndOfParagraph:, CommandDeleteToEndOfLine);
+ // SEL_TO_COMMAND(deleteToMark:, );
+
+ SEL_TO_COMMAND(deleteWordBackward:, CommandDeleteWordBackward);
+ SEL_TO_COMMAND(deleteWordForward:, CommandDeleteWordForward);
+ // SEL_TO_COMMAND(indent:, );
+ // SEL_TO_COMMAND(insertBacktab:, );
+ // SEL_TO_COMMAND(insertContainerBreak:, );
+ // SEL_TO_COMMAND(insertLineBreak:, );
+ // SEL_TO_COMMAND(insertNewline:, );
+ // SEL_TO_COMMAND(insertNewlineIgnoringFieldEditor:, );
+ // SEL_TO_COMMAND(insertParagraphSeparator:, );
+ // SEL_TO_COMMAND(insertTab:, );
+ // SEL_TO_COMMAND(insertTabIgnoringFieldEditor:, );
+ // SEL_TO_COMMAND(insertDoubleQuoteIgnoringSubstitution:, );
+ // SEL_TO_COMMAND(insertSingleQuoteIgnoringSubstitution:, );
+ // SEL_TO_COMMAND(lowercaseWord:, );
+ SEL_TO_COMMAND(moveBackward:, CommandCharPrevious);
+ SEL_TO_COMMAND(moveBackwardAndModifySelection:, CommandSelectCharPrevious);
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(moveDown:, CommandEndLine);
+ } else {
+ SEL_TO_COMMAND(moveDown:, CommandLineNext);
+ }
+ SEL_TO_COMMAND(moveDownAndModifySelection:, CommandSelectLineNext);
+ SEL_TO_COMMAND(moveForward:, CommandCharNext);
+ SEL_TO_COMMAND(moveForwardAndModifySelection:, CommandSelectCharNext);
+ SEL_TO_COMMAND(moveLeft:, CommandCharPrevious);
+ SEL_TO_COMMAND(moveLeftAndModifySelection:, CommandSelectCharPrevious);
+ SEL_TO_COMMAND(moveParagraphBackwardAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveParagraphForwardAndModifySelection:, CommandSelectEndLine);
+ SEL_TO_COMMAND(moveRight:, CommandCharNext);
+ SEL_TO_COMMAND(moveRightAndModifySelection:, CommandSelectCharNext);
+ SEL_TO_COMMAND(moveToBeginningOfDocument:, CommandMoveTop);
+ SEL_TO_COMMAND(moveToBeginningOfDocumentAndModifySelection:,
+ CommandSelectTop);
+ SEL_TO_COMMAND(moveToBeginningOfLine:, CommandBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfLineAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfParagraph:, CommandBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfParagraphAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveToEndOfDocument:, CommandMoveBottom);
+ SEL_TO_COMMAND(moveToEndOfDocumentAndModifySelection:, CommandSelectBottom);
+ SEL_TO_COMMAND(moveToEndOfLine:, CommandEndLine);
+ SEL_TO_COMMAND(moveToEndOfLineAndModifySelection:, CommandSelectEndLine);
+ SEL_TO_COMMAND(moveToEndOfParagraph:, CommandEndLine);
+ SEL_TO_COMMAND(moveToEndOfParagraphAndModifySelection:, CommandSelectEndLine);
+ SEL_TO_COMMAND(moveToLeftEndOfLine:, CommandBeginLine);
+ SEL_TO_COMMAND(moveToLeftEndOfLineAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveToRightEndOfLine:, CommandEndLine);
+ SEL_TO_COMMAND(moveToRightEndOfLineAndModifySelection:, CommandSelectEndLine);
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(moveUp:, CommandBeginLine);
+ } else {
+ SEL_TO_COMMAND(moveUp:, CommandLinePrevious);
+ }
+ SEL_TO_COMMAND(moveUpAndModifySelection:, CommandSelectLinePrevious);
+ SEL_TO_COMMAND(moveWordBackward:, CommandWordPrevious);
+ SEL_TO_COMMAND(moveWordBackwardAndModifySelection:,
+ CommandSelectWordPrevious);
+ SEL_TO_COMMAND(moveWordForward:, CommandWordNext);
+ SEL_TO_COMMAND(moveWordForwardAndModifySelection:, CommandSelectWordNext);
+ SEL_TO_COMMAND(moveWordLeft:, CommandWordPrevious);
+ SEL_TO_COMMAND(moveWordLeftAndModifySelection:, CommandSelectWordPrevious);
+ SEL_TO_COMMAND(moveWordRight:, CommandWordNext);
+ SEL_TO_COMMAND(moveWordRightAndModifySelection:, CommandSelectWordNext);
+ SEL_TO_COMMAND(pageDown:, CommandMovePageDown);
+ SEL_TO_COMMAND(pageDownAndModifySelection:, CommandSelectPageDown);
+ SEL_TO_COMMAND(pageUp:, CommandMovePageUp);
+ SEL_TO_COMMAND(pageUpAndModifySelection:, CommandSelectPageUp);
+ SEL_TO_COMMAND(paste:, CommandPaste);
+ // SEL_TO_COMMAND(pasteFont:, );
+ // SEL_TO_COMMAND(pasteRuler:, );
+ SEL_TO_COMMAND(scrollLineDown:, CommandScrollLineDown);
+ SEL_TO_COMMAND(scrollLineUp:, CommandScrollLineUp);
+ SEL_TO_COMMAND(scrollPageDown:, CommandScrollPageDown);
+ SEL_TO_COMMAND(scrollPageUp:, CommandScrollPageUp);
+ SEL_TO_COMMAND(scrollToBeginningOfDocument:, CommandScrollTop);
+ SEL_TO_COMMAND(scrollToEndOfDocument:, CommandScrollBottom);
+ SEL_TO_COMMAND(selectAll:, CommandSelectAll);
+ // selectLine: is complex, see KeyDown
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(selectParagraph:, CommandSelectAll);
+ }
+ // SEL_TO_COMMAND(selectToMark:, );
+ // selectWord: is complex, see KeyDown
+ // SEL_TO_COMMAND(setMark:, );
+ // SEL_TO_COMMAND(showContextHelp:, );
+ // SEL_TO_COMMAND(supplementalTargetForAction:sender:, );
+ // SEL_TO_COMMAND(swapWithMark:, );
+ // SEL_TO_COMMAND(transpose:, );
+ // SEL_TO_COMMAND(transposeWords:, );
+ // SEL_TO_COMMAND(uppercaseWord:, );
+ // SEL_TO_COMMAND(yank:, );
+}
+
+#undef SEL_TO_COMMAND
+
+bool
+NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress", this));
+
+ // Recover the current event, which should always be the key down we are
+ // responding to.
+
+ NSEvent* cocoaEvent = reinterpret_cast<NSEvent*>(aEvent.mNativeKeyEvent);
+
+ if (!cocoaEvent || [cocoaEvent type] != NSKeyDown) {
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, no Cocoa key down event", this));
+
+ return false;
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, interpreting", this));
+
+ AutoTArray<KeyBindingsCommand, 2> bindingCommands;
+ nsCocoaUtils::GetCommandsFromKeyEvent(cocoaEvent, bindingCommands);
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, bindingCommands=%u",
+ this, bindingCommands.Length()));
+
+ AutoTArray<Command, 4> geckoCommands;
+
+ for (uint32_t i = 0; i < bindingCommands.Length(); i++) {
+ SEL selector = bindingCommands[i].selector;
+
+ if (MOZ_LOG_TEST(gNativeKeyBindingsLog, LogLevel::Info)) {
+ NSString* selectorString = NSStringFromSelector(selector);
+ nsAutoString nsSelectorString;
+ nsCocoaUtils::GetStringForNSString(selectorString, nsSelectorString);
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, selector=%s",
+ this, NS_LossyConvertUTF16toASCII(nsSelectorString).get()));
+ }
+
+ // Try to find a simple mapping in the hashtable
+ Command geckoCommand = static_cast<Command>(mSelectorToCommand.Get(
+ reinterpret_cast<struct objc_selector*>(selector)));
+
+ if (geckoCommand) {
+ geckoCommands.AppendElement(geckoCommand);
+ } else if (selector == @selector(selectLine:)) {
+ // This is functional, but Cocoa's version is direction-less in that
+ // selection direction is not determined until some future directed action
+ // is taken. See bug 282097, comment 79 for more details.
+ geckoCommands.AppendElement(CommandBeginLine);
+ geckoCommands.AppendElement(CommandSelectEndLine);
+ } else if (selector == @selector(selectWord:)) {
+ // This is functional, but Cocoa's version is direction-less in that
+ // selection direction is not determined until some future directed action
+ // is taken. See bug 282097, comment 79 for more details.
+ geckoCommands.AppendElement(CommandWordPrevious);
+ geckoCommands.AppendElement(CommandSelectWordNext);
+ }
+ }
+
+ if (geckoCommands.IsEmpty()) {
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, handled=false", this));
+
+ return false;
+ }
+
+ for (uint32_t i = 0; i < geckoCommands.Length(); i++) {
+ Command geckoCommand = geckoCommands[i];
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, command=%s",
+ this, WidgetKeyboardEvent::GetCommandStr(geckoCommand)));
+
+ // Execute the Gecko command
+ aCallback(geckoCommand, aCallbackData);
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, handled=true", this));
+
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/OSXNotificationCenter.h b/widget/cocoa/OSXNotificationCenter.h
new file mode 100644
index 000000000..30767b5c5
--- /dev/null
+++ b/widget/cocoa/OSXNotificationCenter.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef OSXNotificationCenter_h
+#define OSXNotificationCenter_h
+
+#import <Foundation/Foundation.h>
+#include "nsIAlertsService.h"
+#include "imgINotificationObserver.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+@class mozNotificationCenterDelegate;
+
+#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
+typedef NSInteger NSUserNotificationActivationType;
+#endif
+
+namespace mozilla {
+
+class OSXNotificationInfo;
+
+class OSXNotificationCenter : public nsIAlertsService,
+ public nsIAlertsIconData,
+ public nsIAlertNotificationImageListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_NSIALERTSICONDATA
+ NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+
+ OSXNotificationCenter();
+
+ nsresult Init();
+ void CloseAlertCocoaString(NSString *aAlertName);
+ void OnActivate(NSString *aAlertName, NSUserNotificationActivationType aActivationType,
+ unsigned long long aAdditionalActionIndex);
+ void ShowPendingNotification(OSXNotificationInfo *osxni);
+
+protected:
+ virtual ~OSXNotificationCenter();
+
+private:
+ mozNotificationCenterDelegate *mDelegate;
+ nsTArray<RefPtr<OSXNotificationInfo> > mActiveAlerts;
+ nsTArray<RefPtr<OSXNotificationInfo> > mPendingAlerts;
+};
+
+} // namespace mozilla
+
+#endif // OSXNotificationCenter_h
diff --git a/widget/cocoa/OSXNotificationCenter.mm b/widget/cocoa/OSXNotificationCenter.mm
new file mode 100644
index 000000000..e9e36a96b
--- /dev/null
+++ b/widget/cocoa/OSXNotificationCenter.mm
@@ -0,0 +1,589 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSXNotificationCenter.h"
+#import <AppKit/AppKit.h>
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "nsICancelable.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#import "nsCocoaUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+
+using namespace mozilla;
+
+#define MAX_NOTIFICATION_NAME_LEN 5000
+
+#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
+@protocol NSUserNotificationCenterDelegate
+@end
+static NSString * const NSUserNotificationDefaultSoundName = @"DefaultSoundName";
+enum {
+ NSUserNotificationActivationTypeNone = 0,
+ NSUserNotificationActivationTypeContentsClicked = 1,
+ NSUserNotificationActivationTypeActionButtonClicked = 2,
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_9) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9)
+enum {
+ NSUserNotificationActivationTypeReplied = 3,
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
+enum {
+ NSUserNotificationActivationTypeAdditionalActionClicked = 4
+};
+#endif
+
+@protocol FakeNSUserNotification <NSObject>
+@property (copy) NSString* title;
+@property (copy) NSString* subtitle;
+@property (copy) NSString* informativeText;
+@property (copy) NSString* actionButtonTitle;
+@property (copy) NSDictionary* userInfo;
+@property (copy) NSDate* deliveryDate;
+@property (copy) NSTimeZone* deliveryTimeZone;
+@property (copy) NSDateComponents* deliveryRepeatInterval;
+@property (readonly) NSDate* actualDeliveryDate;
+@property (readonly, getter=isPresented) BOOL presented;
+@property (readonly, getter=isRemote) BOOL remote;
+@property (copy) NSString* soundName;
+@property BOOL hasActionButton;
+@property (readonly) NSUserNotificationActivationType activationType;
+@property (copy) NSString *otherButtonTitle;
+@property (copy) NSImage *contentImage;
+@end
+
+@protocol FakeNSUserNotificationCenter <NSObject>
++ (id<FakeNSUserNotificationCenter>)defaultUserNotificationCenter;
+@property (assign) id <NSUserNotificationCenterDelegate> delegate;
+@property (copy) NSArray *scheduledNotifications;
+- (void)scheduleNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeScheduledNotification:(id<FakeNSUserNotification>)notification;
+@property (readonly) NSArray *deliveredNotifications;
+- (void)deliverNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeDeliveredNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeAllDeliveredNotifications;
+- (void)_removeAllDisplayedNotifications;
+- (void)_removeDisplayedNotification:(id<FakeNSUserNotification>)notification;
+@end
+
+@interface mozNotificationCenterDelegate : NSObject <NSUserNotificationCenterDelegate>
+{
+ OSXNotificationCenter *mOSXNC;
+}
+ - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc;
+@end
+
+@implementation mozNotificationCenterDelegate
+
+- (id)initWithOSXNC:(OSXNotificationCenter*)osxnc
+{
+ [super init];
+ // We should *never* outlive this OSXNotificationCenter.
+ mOSXNC = osxnc;
+ return self;
+}
+
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didDeliverNotification:(id<FakeNSUserNotification>)notification
+{
+
+}
+
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didActivateNotification:(id<FakeNSUserNotification>)notification
+{
+ unsigned long long additionalActionIndex = ULLONG_MAX;
+ if ([notification respondsToSelector:@selector(_alternateActionIndex)]) {
+ NSNumber *alternateActionIndex = [(NSObject*)notification valueForKey:@"_alternateActionIndex"];
+ additionalActionIndex = [alternateActionIndex unsignedLongLongValue];
+ }
+ mOSXNC->OnActivate([[notification userInfo] valueForKey:@"name"],
+ notification.activationType,
+ additionalActionIndex);
+}
+
+- (BOOL)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ shouldPresentNotification:(id<FakeNSUserNotification>)notification
+{
+ return YES;
+}
+
+// This is an undocumented method that we need for parity with Safari.
+// Apple bug #15440664.
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didRemoveDeliveredNotifications:(NSArray *)notifications
+{
+ for (id<FakeNSUserNotification> notification in notifications) {
+ NSString *name = [[notification userInfo] valueForKey:@"name"];
+ mOSXNC->CloseAlertCocoaString(name);
+ }
+}
+
+// This is an undocumented method that we need to be notified if a user clicks the close button.
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didDismissAlert:(id<FakeNSUserNotification>)notification
+{
+ NSString *name = [[notification userInfo] valueForKey:@"name"];
+ mOSXNC->CloseAlertCocoaString(name);
+}
+
+@end
+
+namespace mozilla {
+
+enum {
+ OSXNotificationActionDisable = 0,
+ OSXNotificationActionSettings = 1,
+};
+
+class OSXNotificationInfo final : public nsISupports {
+private:
+ virtual ~OSXNotificationInfo();
+
+public:
+ NS_DECL_ISUPPORTS
+ OSXNotificationInfo(NSString *name, nsIObserver *observer,
+ const nsAString & alertCookie);
+
+ NSString *mName;
+ nsCOMPtr<nsIObserver> mObserver;
+ nsString mCookie;
+ RefPtr<nsICancelable> mIconRequest;
+ id<FakeNSUserNotification> mPendingNotifiction;
+};
+
+NS_IMPL_ISUPPORTS0(OSXNotificationInfo)
+
+OSXNotificationInfo::OSXNotificationInfo(NSString *name, nsIObserver *observer,
+ const nsAString & alertCookie)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ASSERTION(name, "Cannot create OSXNotificationInfo without a name!");
+ mName = [name retain];
+ mObserver = observer;
+ mCookie = alertCookie;
+ mPendingNotifiction = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+OSXNotificationInfo::~OSXNotificationInfo()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mName release];
+ [mPendingNotifiction release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static id<FakeNSUserNotificationCenter> GetNotificationCenter() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ Class c = NSClassFromString(@"NSUserNotificationCenter");
+ return [c performSelector:@selector(defaultUserNotificationCenter)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+OSXNotificationCenter::OSXNotificationCenter()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mDelegate = [[mozNotificationCenterDelegate alloc] initWithOSXNC:this];
+ GetNotificationCenter().delegate = mDelegate;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+OSXNotificationCenter::~OSXNotificationCenter()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [GetNotificationCenter() removeAllDeliveredNotifications];
+ [mDelegate release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMPL_ISUPPORTS(OSXNotificationCenter, nsIAlertsService, nsIAlertsIconData,
+ nsIAlertNotificationImageListener)
+
+nsresult OSXNotificationCenter::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ return (!!NSClassFromString(@"NSUserNotification")) ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle,
+ const nsAString & aAlertText, bool aAlertTextClickable,
+ const nsAString & aAlertCookie,
+ nsIObserver * aAlertListener,
+ const nsAString & aAlertName,
+ const nsAString & aBidi,
+ const nsAString & aLang,
+ const nsAString & aData,
+ nsIPrincipal * aPrincipal,
+ bool aInPrivateBrowsing,
+ bool aRequireInteraction)
+{
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
+ nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle,
+ aAlertText, aAlertTextClickable,
+ aAlertCookie, aBidi, aLang, aData,
+ aPrincipal, aInPrivateBrowsing,
+ aRequireInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowPersistentNotification(const nsAString& aPersistentData,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ return ShowAlertWithIconData(aAlert, aAlertListener, 0, nullptr);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlertWithIconData(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener,
+ uint32_t aIconSize,
+ const uint8_t* aIconData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_ARG(aAlert);
+
+ Class unClass = NSClassFromString(@"NSUserNotification");
+ id<FakeNSUserNotification> notification = [[unClass alloc] init];
+
+ nsAutoString title;
+ nsresult rv = aAlert->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ notification.title = nsCocoaUtils::ToNSString(title);
+
+ nsAutoString hostPort;
+ rv = aAlert->GetSource(hostPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ sbs->CreateBundle("chrome://alerts/locale/alert.properties", getter_AddRefs(bundle));
+
+ if (!hostPort.IsEmpty() && bundle) {
+ const char16_t* formatStrings[] = { hostPort.get() };
+ nsXPIDLString notificationSource;
+ bundle->FormatStringFromName(u"source.label",
+ formatStrings,
+ ArrayLength(formatStrings),
+ getter_Copies(notificationSource));
+ notification.subtitle = nsCocoaUtils::ToNSString(notificationSource);
+ }
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+ notification.informativeText = nsCocoaUtils::ToNSString(text);
+
+ notification.soundName = NSUserNotificationDefaultSoundName;
+ notification.hasActionButton = NO;
+
+ // If this is not an application/extension alert, show additional actions dealing with permissions.
+ bool isActionable;
+ if (bundle && NS_SUCCEEDED(aAlert->GetActionable(&isActionable)) && isActionable) {
+ nsXPIDLString closeButtonTitle, actionButtonTitle, disableButtonTitle, settingsButtonTitle;
+ bundle->GetStringFromName(u"closeButton.title",
+ getter_Copies(closeButtonTitle));
+ bundle->GetStringFromName(u"actionButton.label",
+ getter_Copies(actionButtonTitle));
+ if (!hostPort.IsEmpty()) {
+ const char16_t* formatStrings[] = { hostPort.get() };
+ bundle->FormatStringFromName(u"webActions.disableForOrigin.label",
+ formatStrings,
+ ArrayLength(formatStrings),
+ getter_Copies(disableButtonTitle));
+ }
+ bundle->GetStringFromName(u"webActions.settings.label",
+ getter_Copies(settingsButtonTitle));
+
+ notification.otherButtonTitle = nsCocoaUtils::ToNSString(closeButtonTitle);
+
+ // OS X 10.8 only shows action buttons if the "Alerts" style is set in
+ // Notification Center preferences, and doesn't support the alternate
+ // action menu.
+ if ([notification respondsToSelector:@selector(set_showsButtons:)] &&
+ [notification respondsToSelector:@selector(set_alwaysShowAlternateActionMenu:)] &&
+ [notification respondsToSelector:@selector(set_alternateActionButtonTitles:)]) {
+
+ notification.hasActionButton = YES;
+ notification.actionButtonTitle = nsCocoaUtils::ToNSString(actionButtonTitle);
+
+ [(NSObject*)notification setValue:@(YES) forKey:@"_showsButtons"];
+ [(NSObject*)notification setValue:@(YES) forKey:@"_alwaysShowAlternateActionMenu"];
+ [(NSObject*)notification setValue:@[
+ nsCocoaUtils::ToNSString(disableButtonTitle),
+ nsCocoaUtils::ToNSString(settingsButtonTitle)
+ ]
+ forKey:@"_alternateActionButtonTitles"];
+ }
+ }
+ nsAutoString name;
+ rv = aAlert->GetName(name);
+ // Don't let an alert name be more than MAX_NOTIFICATION_NAME_LEN characters.
+ // More than that shouldn't be necessary and userInfo (assigned to below) has
+ // a length limit of 16k on OS X 10.11. Exception thrown if limit exceeded.
+ if (name.Length() > MAX_NOTIFICATION_NAME_LEN) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ NSString *alertName = nsCocoaUtils::ToNSString(name);
+ if (!alertName) {
+ return NS_ERROR_FAILURE;
+ }
+ notification.userInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil]
+ forKeys:[NSArray arrayWithObjects:@"name", nil]];
+
+ nsAutoString cookie;
+ rv = aAlert->GetCookie(cookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, cookie);
+
+ // Show the favicon if supported on this version of OS X.
+ if (aIconSize > 0 &&
+ [notification respondsToSelector:@selector(set_identityImage:)] &&
+ [notification respondsToSelector:@selector(set_identityImageHasBorder:)]) {
+
+ NSData *iconData = [NSData dataWithBytes:aIconData length:aIconSize];
+ NSImage *icon = [[[NSImage alloc] initWithData:iconData] autorelease];
+
+ [(NSObject*)notification setValue:icon forKey:@"_identityImage"];
+ [(NSObject*)notification setValue:@(NO) forKey:@"_identityImageHasBorder"];
+ }
+
+ bool inPrivateBrowsing;
+ rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Show the notification without waiting for an image if there is no icon URL or
+ // notification icons are not supported on this version of OS X.
+ if (![unClass instancesRespondToSelector:@selector(setContentImage:)]) {
+ CloseAlertCocoaString(alertName);
+ mActiveAlerts.AppendElement(osxni);
+ [GetNotificationCenter() deliverNotification:notification];
+ [notification release];
+ if (aAlertListener) {
+ aAlertListener->Observe(nullptr, "alertshow", cookie.get());
+ }
+ } else {
+ mPendingAlerts.AppendElement(osxni);
+ osxni->mPendingNotifiction = notification;
+ // Wait six seconds for the image to load.
+ rv = aAlert->LoadImage(6000, this, osxni,
+ getter_AddRefs(osxni->mIconRequest));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ShowPendingNotification(osxni);
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::CloseAlert(const nsAString& aAlertName,
+ nsIPrincipal* aPrincipal)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString *alertName = nsCocoaUtils::ToNSString(aAlertName);
+ CloseAlertCocoaString(alertName);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+OSXNotificationCenter::CloseAlertCocoaString(NSString *aAlertName)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aAlertName) {
+ return; // Can't do anything without a name
+ }
+
+ NSArray *notifications = [GetNotificationCenter() deliveredNotifications];
+ for (id<FakeNSUserNotification> notification in notifications) {
+ NSString *name = [[notification userInfo] valueForKey:@"name"];
+ if ([name isEqualToString:aAlertName]) {
+ [GetNotificationCenter() removeDeliveredNotification:notification];
+ [GetNotificationCenter() _removeDisplayedNotification:notification];
+ break;
+ }
+ }
+
+ for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
+ OSXNotificationInfo *osxni = mActiveAlerts[i];
+ if ([aAlertName isEqualToString:osxni->mName]) {
+ if (osxni->mObserver) {
+ osxni->mObserver->Observe(nullptr, "alertfinished", osxni->mCookie.get());
+ }
+ if (osxni->mIconRequest) {
+ osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
+ osxni->mIconRequest = nullptr;
+ }
+ mActiveAlerts.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+OSXNotificationCenter::OnActivate(NSString *aAlertName,
+ NSUserNotificationActivationType aActivationType,
+ unsigned long long aAdditionalActionIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aAlertName) {
+ return; // Can't do anything without a name
+ }
+
+ for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
+ OSXNotificationInfo *osxni = mActiveAlerts[i];
+ if ([aAlertName isEqualToString:osxni->mName]) {
+ if (osxni->mObserver) {
+ switch ((int)aActivationType) {
+ case NSUserNotificationActivationTypeAdditionalActionClicked:
+ case NSUserNotificationActivationTypeActionButtonClicked:
+ switch (aAdditionalActionIndex) {
+ case OSXNotificationActionDisable:
+ osxni->mObserver->Observe(nullptr, "alertdisablecallback", osxni->mCookie.get());
+ break;
+ case OSXNotificationActionSettings:
+ osxni->mObserver->Observe(nullptr, "alertsettingscallback", osxni->mCookie.get());
+ break;
+ default:
+ NS_WARNING("Unknown NSUserNotification additional action clicked");
+ break;
+ }
+ break;
+ default:
+ osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get());
+ break;
+ }
+ }
+ return;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+OSXNotificationCenter::ShowPendingNotification(OSXNotificationInfo *osxni)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (osxni->mIconRequest) {
+ osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
+ osxni->mIconRequest = nullptr;
+ }
+
+ CloseAlertCocoaString(osxni->mName);
+
+ for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
+ if (mPendingAlerts[i] == osxni) {
+ mActiveAlerts.AppendElement(osxni);
+ mPendingAlerts.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ [GetNotificationCenter() deliverNotification:osxni->mPendingNotifiction];
+
+ if (osxni->mObserver) {
+ osxni->mObserver->Observe(nullptr, "alertshow", osxni->mCookie.get());
+ }
+
+ [osxni->mPendingNotifiction release];
+ osxni->mPendingNotifiction = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::OnImageMissing(nsISupports* aUserData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ OSXNotificationInfo *osxni = static_cast<OSXNotificationInfo*>(aUserData);
+ if (osxni->mPendingNotifiction) {
+ // If there was an error getting the image, or the request timed out, show
+ // the notification without a content image.
+ ShowPendingNotification(osxni);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::OnImageReady(nsISupports* aUserData,
+ imgIRequest* aRequest)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCOMPtr<imgIContainer> image;
+ nsresult rv = aRequest->GetImage(getter_AddRefs(image));
+ if (NS_WARN_IF(NS_FAILED(rv) || !image)) {
+ return rv;
+ }
+
+ OSXNotificationInfo *osxni = static_cast<OSXNotificationInfo*>(aUserData);
+ if (!osxni->mPendingNotifiction) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSImage *cocoaImage = nil;
+ nsCocoaUtils::CreateNSImageFromImageContainer(image, imgIContainer::FRAME_FIRST, &cocoaImage, 1.0f);
+ (osxni->mPendingNotifiction).contentImage = cocoaImage;
+ [cocoaImage release];
+ ShowPendingNotification(osxni);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/RectTextureImage.h b/widget/cocoa/RectTextureImage.h
new file mode 100644
index 000000000..022b216c6
--- /dev/null
+++ b/widget/cocoa/RectTextureImage.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RectTextureImage_h_
+#define RectTextureImage_h_
+
+#include "mozilla/RefPtr.h"
+
+class MacIOSurface;
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+} // namespace gl
+
+namespace widget {
+
+// Manages a texture which can resize dynamically, binds to the
+// LOCAL_GL_TEXTURE_RECTANGLE_ARB texture target and is automatically backed
+// by a power-of-two size GL texture. The latter two features are used for
+// compatibility with older Mac hardware which we block GL layers on.
+// RectTextureImages are used both for accelerated GL layers drawing and for
+// OMTC BasicLayers drawing.
+class RectTextureImage {
+public:
+ RectTextureImage();
+
+ virtual ~RectTextureImage();
+
+ already_AddRefed<gfx::DrawTarget>
+ BeginUpdate(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion =
+ LayoutDeviceIntRegion());
+ void EndUpdate();
+
+ void UpdateIfNeeded(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion,
+ void (^aCallback)(gfx::DrawTarget*,
+ const LayoutDeviceIntRegion&))
+ {
+ RefPtr<gfx::DrawTarget> drawTarget = BeginUpdate(aNewSize, aDirtyRegion);
+ if (drawTarget) {
+ aCallback(drawTarget, GetUpdateRegion());
+ EndUpdate();
+ }
+ }
+
+ void UpdateFromCGContext(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion,
+ CGContextRef aCGContext);
+
+ LayoutDeviceIntRegion GetUpdateRegion() {
+ MOZ_ASSERT(mInUpdate, "update region only valid during update");
+ return mUpdateRegion;
+ }
+
+ void Draw(mozilla::layers::GLManager* aManager,
+ const LayoutDeviceIntPoint& aLocation,
+ const gfx::Matrix4x4& aTransform = gfx::Matrix4x4());
+
+
+protected:
+ void DeleteTexture();
+ void BindIOSurfaceToTexture(gl::GLContext* aGL);
+
+ RefPtr<MacIOSurface> mIOSurface;
+ gl::GLContext* mGLContext;
+ LayoutDeviceIntRegion mUpdateRegion;
+ LayoutDeviceIntSize mBufferSize;
+ GLuint mTexture;
+ bool mInUpdate;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // RectTextureImage_h_
diff --git a/widget/cocoa/RectTextureImage.mm b/widget/cocoa/RectTextureImage.mm
new file mode 100644
index 000000000..c67af97d0
--- /dev/null
+++ b/widget/cocoa/RectTextureImage.mm
@@ -0,0 +1,171 @@
+/* -*- Mode: objc; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RectTextureImage.h"
+
+#include "gfxUtils.h"
+#include "GLContextCGL.h"
+#include "mozilla/layers/GLManager.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "OGLShaderProgram.h"
+#include "ScopedGLHelpers.h"
+
+namespace mozilla {
+namespace widget {
+
+RectTextureImage::RectTextureImage()
+ : mGLContext(nullptr)
+ , mTexture(0)
+ , mInUpdate(false)
+{
+}
+
+RectTextureImage::~RectTextureImage()
+{
+ DeleteTexture();
+}
+
+already_AddRefed<gfx::DrawTarget>
+RectTextureImage::BeginUpdate(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion)
+{
+ MOZ_ASSERT(!mInUpdate, "Beginning update during update!");
+ mUpdateRegion = aDirtyRegion;
+ bool needRecreate = false;
+ if (aNewSize != mBufferSize) {
+ mBufferSize = aNewSize;
+ mUpdateRegion =
+ LayoutDeviceIntRect(LayoutDeviceIntPoint(0, 0), aNewSize);
+ needRecreate = true;
+ }
+
+ if (mUpdateRegion.IsEmpty()) {
+ return nullptr;
+ }
+
+ if (!mIOSurface || needRecreate) {
+ DeleteTexture();
+ mIOSurface = MacIOSurface::CreateIOSurface(mBufferSize.width,
+ mBufferSize.height);
+
+ if (!mIOSurface) {
+ return nullptr;
+ }
+ }
+
+ mInUpdate = true;
+
+ mIOSurface->Lock(false);
+ unsigned char* ioData = (unsigned char*)mIOSurface->GetBaseAddress();
+ gfx::IntSize size(mBufferSize.width, mBufferSize.height);
+ int32_t stride = mIOSurface->GetBytesPerRow();
+ gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
+ RefPtr<gfx::DrawTarget> drawTarget =
+ gfx::Factory::CreateDrawTargetForData(gfx::BackendType::SKIA,
+ ioData, size,
+ stride, format);
+ return drawTarget.forget();
+}
+
+void
+RectTextureImage::EndUpdate()
+{
+ MOZ_ASSERT(mInUpdate, "Ending update while not in update");
+ mIOSurface->Unlock(false);
+ mInUpdate = false;
+}
+
+void
+RectTextureImage::UpdateFromCGContext(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion,
+ CGContextRef aCGContext)
+{
+ gfx::IntSize size = gfx::IntSize(CGBitmapContextGetWidth(aCGContext),
+ CGBitmapContextGetHeight(aCGContext));
+ RefPtr<gfx::DrawTarget> dt = BeginUpdate(aNewSize, aDirtyRegion);
+ if (dt) {
+ gfx::Rect rect(0, 0, size.width, size.height);
+ gfxUtils::ClipToRegion(dt, GetUpdateRegion().ToUnknownRegion());
+ RefPtr<gfx::SourceSurface> sourceSurface =
+ dt->CreateSourceSurfaceFromData(static_cast<uint8_t *>(CGBitmapContextGetData(aCGContext)),
+ size,
+ CGBitmapContextGetBytesPerRow(aCGContext),
+ gfx::SurfaceFormat::B8G8R8A8);
+ dt->DrawSurface(sourceSurface, rect, rect,
+ gfx::DrawSurfaceOptions(),
+ gfx::DrawOptions(1.0, gfx::CompositionOp::OP_SOURCE));
+ dt->PopClip();
+ EndUpdate();
+ }
+}
+
+void
+RectTextureImage::Draw(layers::GLManager* aManager,
+ const LayoutDeviceIntPoint& aLocation,
+ const gfx::Matrix4x4& aTransform)
+{
+ gl::GLContext* gl = aManager->gl();
+
+ BindIOSurfaceToTexture(gl);
+
+ layers::ShaderProgramOGL* program =
+ aManager->GetProgram(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ gfx::SurfaceFormat::R8G8B8A8);
+
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0);
+ gl::ScopedBindTexture texture(gl, mTexture, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+
+ aManager->ActivateProgram(program);
+ program->SetProjectionMatrix(aManager->GetProjMatrix());
+ program->SetLayerTransform(gfx::Matrix4x4(aTransform).PostTranslate(aLocation.x, aLocation.y, 0));
+ program->SetTextureTransform(gfx::Matrix4x4());
+ program->SetRenderOffset(nsIntPoint(0, 0));
+ program->SetTexCoordMultiplier(mBufferSize.width, mBufferSize.height);
+ program->SetTextureUnit(0);
+
+ aManager->BindAndDrawQuad(program,
+ gfx::Rect(0.0, 0.0, mBufferSize.width, mBufferSize.height),
+ gfx::Rect(0.0, 0.0, 1.0f, 1.0f));
+}
+
+void
+RectTextureImage::DeleteTexture()
+{
+ if (mTexture) {
+ MOZ_ASSERT(mGLContext);
+ mGLContext->MakeCurrent();
+ mGLContext->fDeleteTextures(1, &mTexture);
+ mTexture = 0;
+ }
+}
+
+void
+RectTextureImage::BindIOSurfaceToTexture(gl::GLContext* aGL)
+{
+ if (!mTexture) {
+ MOZ_ASSERT(aGL);
+ aGL->fGenTextures(1, &mTexture);
+ aGL->fActiveTexture(LOCAL_GL_TEXTURE0);
+ gl::ScopedBindTexture texture(aGL, mTexture, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_LINEAR);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_LINEAR);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+
+ mIOSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(aGL)->GetCGLContext());
+ mGLContext = aGL;
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/SwipeTracker.h b/widget/cocoa/SwipeTracker.h
new file mode 100644
index 000000000..b5eb2a481
--- /dev/null
+++ b/widget/cocoa/SwipeTracker.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 SwipeTracker_h
+#define SwipeTracker_h
+
+#include "EventForwards.h"
+#include "mozilla/layers/AxisPhysicsMSDModel.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsRefreshDriver.h"
+#include "Units.h"
+
+class nsIPresShell;
+
+namespace mozilla {
+
+class PanGestureInput;
+
+/**
+ * SwipeTracker turns PanGestureInput events into swipe events
+ * (WidgetSimpleGestureEvent) and dispatches them into Gecko.
+ * The swiping behavior mirrors the behavior of the Cocoa API
+ * -[NSEvent trackSwipeEventWithOptions:dampenAmountThresholdMin:max:usingHandler:].
+ * The advantage of using this class over the Cocoa API is that this class
+ * properly supports submitting queued up events to it, and that it hopefully
+ * doesn't intermittently break scrolling the way the Cocoa API does (bug 927702).
+ *
+ * The swipe direction is either left or right. It is determined before the
+ * SwipeTracker is created and stays fixed during the swipe.
+ * During the swipe, the swipe has a current "value" which is between 0 and the
+ * target value. The target value is either 1 (swiping left) or -1 (swiping
+ * right) - see SwipeSuccessTargetValue().
+ * A swipe can either succeed or fail. If it succeeds, the swipe animation
+ * animates towards the success target value; if it fails, it animates back to
+ * a value of 0. A swipe can only succeed if the user is swiping in an allowed
+ * direction. (Since both the allowed directions and the swipe direction are
+ * known at swipe start time, it's clear from the beginning whether a swipe is
+ * doomed to fail. In that case, the purpose of the SwipeTracker is to simulate
+ * a bounce-back animation.)
+ */
+class SwipeTracker final : public nsARefreshObserver {
+public:
+ NS_INLINE_DECL_REFCOUNTING(SwipeTracker, override)
+
+ SwipeTracker(nsChildView& aWidget,
+ const PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections,
+ uint32_t aSwipeDirection);
+
+ void Destroy();
+
+ nsEventStatus ProcessEvent(const PanGestureInput& aEvent);
+ void CancelSwipe();
+
+ static WidgetSimpleGestureEvent
+ CreateSwipeGestureEvent(EventMessage aMsg, nsIWidget* aWidget,
+ const LayoutDeviceIntPoint& aPosition);
+
+
+ // nsARefreshObserver
+ void WillRefresh(mozilla::TimeStamp aTime) override;
+
+protected:
+ ~SwipeTracker();
+
+ bool SwipingInAllowedDirection() const { return mAllowedDirections & mSwipeDirection; }
+ double SwipeSuccessTargetValue() const;
+ double ClampToAllowedRange(double aGestureAmount) const;
+ bool ComputeSwipeSuccess() const;
+ void StartAnimating(double aTargetValue);
+ void SwipeFinished();
+ void UnregisterFromRefreshDriver();
+ bool SendSwipeEvent(EventMessage aMsg, uint32_t aDirection, double aDelta);
+
+ nsChildView& mWidget;
+ RefPtr<nsRefreshDriver> mRefreshDriver;
+ layers::AxisPhysicsMSDModel mAxis;
+ const LayoutDeviceIntPoint mEventPosition;
+ TimeStamp mLastEventTimeStamp;
+ TimeStamp mLastAnimationFrameTime;
+ const uint32_t mAllowedDirections;
+ const uint32_t mSwipeDirection;
+ double mGestureAmount;
+ double mCurrentVelocity;
+ bool mEventsAreControllingSwipe;
+ bool mEventsHaveStartedNewGesture;
+ bool mRegisteredWithRefreshDriver;
+};
+
+} // namespace mozilla
+
+#endif // SwipeTracker_h
diff --git a/widget/cocoa/SwipeTracker.mm b/widget/cocoa/SwipeTracker.mm
new file mode 100644
index 000000000..9c06ee0c3
--- /dev/null
+++ b/widget/cocoa/SwipeTracker.mm
@@ -0,0 +1,220 @@
+/* -*- 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/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "nsAlgorithm.h"
+#include "nsChildView.h"
+#include "UnitTransforms.h"
+
+// These values were tweaked to make the physics feel similar to the native swipe.
+static const double kSpringForce = 250.0;
+static const double kVelocityTwitchTolerance = 0.0000001;
+static const double kWholePagePixelSize = 1000.0;
+static const double kRubberBandResistanceFactor = 4.0;
+static const double kSwipeSuccessThreshold = 0.25;
+static const double kSwipeSuccessVelocityContribution = 0.3;
+
+namespace mozilla {
+
+static already_AddRefed<nsRefreshDriver>
+GetRefreshDriver(nsIWidget& aWidget)
+{
+ nsIWidgetListener* widgetListener = aWidget.GetWidgetListener();
+ nsIPresShell* presShell = widgetListener ? widgetListener->GetPresShell() : nullptr;
+ nsPresContext* presContext = presShell ? presShell->GetPresContext() : nullptr;
+ RefPtr<nsRefreshDriver> refreshDriver = presContext ? presContext->RefreshDriver() : nullptr;
+ return refreshDriver.forget();
+}
+
+SwipeTracker::SwipeTracker(nsChildView& aWidget,
+ const PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections,
+ uint32_t aSwipeDirection)
+ : mWidget(aWidget)
+ , mRefreshDriver(GetRefreshDriver(mWidget))
+ , mAxis(0.0, 0.0, 0.0, kSpringForce, 1.0)
+ , mEventPosition(RoundedToInt(ViewAs<LayoutDevicePixel>(aSwipeStartEvent.mPanStartPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent)))
+ , mLastEventTimeStamp(aSwipeStartEvent.mTimeStamp)
+ , mAllowedDirections(aAllowedDirections)
+ , mSwipeDirection(aSwipeDirection)
+ , mGestureAmount(0.0)
+ , mCurrentVelocity(0.0)
+ , mEventsAreControllingSwipe(true)
+ , mEventsHaveStartedNewGesture(false)
+ , mRegisteredWithRefreshDriver(false)
+{
+ SendSwipeEvent(eSwipeGestureStart, 0, 0.0);
+ ProcessEvent(aSwipeStartEvent);
+}
+
+void
+SwipeTracker::Destroy()
+{
+ UnregisterFromRefreshDriver();
+}
+
+SwipeTracker::~SwipeTracker()
+{
+ MOZ_ASSERT(!mRegisteredWithRefreshDriver, "Destroy needs to be called before deallocating");
+}
+
+double
+SwipeTracker::SwipeSuccessTargetValue() const
+{
+ return (mSwipeDirection == nsIDOMSimpleGestureEvent::DIRECTION_RIGHT) ? -1.0 : 1.0;
+}
+
+double
+SwipeTracker::ClampToAllowedRange(double aGestureAmount) const
+{
+ // gestureAmount needs to stay between -1 and 0 when swiping right and
+ // between 0 and 1 when swiping left.
+ double min = (mSwipeDirection == nsIDOMSimpleGestureEvent::DIRECTION_RIGHT) ? -1.0 : 0.0;
+ double max = (mSwipeDirection == nsIDOMSimpleGestureEvent::DIRECTION_LEFT) ? 1.0 : 0.0;
+ return clamped(aGestureAmount, min, max);
+}
+
+bool
+SwipeTracker::ComputeSwipeSuccess() const
+{
+ double targetValue = SwipeSuccessTargetValue();
+
+ // If the fingers were moving away from the target direction when they were
+ // lifted from the touchpad, abort the swipe.
+ if (mCurrentVelocity * targetValue < -kVelocityTwitchTolerance) {
+ return false;
+ }
+
+ return (mGestureAmount * targetValue +
+ mCurrentVelocity * targetValue * kSwipeSuccessVelocityContribution) >= kSwipeSuccessThreshold;
+}
+
+nsEventStatus
+SwipeTracker::ProcessEvent(const PanGestureInput& aEvent)
+{
+ // If the fingers have already been lifted, don't process this event for swiping.
+ if (!mEventsAreControllingSwipe) {
+ // Return nsEventStatus_eConsumeNoDefault for events from the swipe gesture
+ // and nsEventStatus_eIgnore for events of subsequent scroll gestures.
+ if (aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART ||
+ aEvent.mType == PanGestureInput::PANGESTURE_START) {
+ mEventsHaveStartedNewGesture = true;
+ }
+ return mEventsHaveStartedNewGesture ? nsEventStatus_eIgnore : nsEventStatus_eConsumeNoDefault;
+ }
+
+ double delta = -aEvent.mPanDisplacement.x / mWidget.BackingScaleFactor() / kWholePagePixelSize;
+ if (!SwipingInAllowedDirection()) {
+ delta /= kRubberBandResistanceFactor;
+ }
+ mGestureAmount = ClampToAllowedRange(mGestureAmount + delta);
+ SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount);
+
+ if (aEvent.mType != PanGestureInput::PANGESTURE_END) {
+ double elapsedSeconds = std::max(0.008, (aEvent.mTimeStamp - mLastEventTimeStamp).ToSeconds());
+ mCurrentVelocity = delta / elapsedSeconds;
+ mLastEventTimeStamp = aEvent.mTimeStamp;
+ } else {
+ mEventsAreControllingSwipe = false;
+ bool didSwipeSucceed = SwipingInAllowedDirection() && ComputeSwipeSuccess();
+ double targetValue = 0.0;
+ if (didSwipeSucceed) {
+ SendSwipeEvent(eSwipeGesture, mSwipeDirection, 0.0);
+ targetValue = SwipeSuccessTargetValue();
+ }
+ StartAnimating(targetValue);
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+void
+SwipeTracker::StartAnimating(double aTargetValue)
+{
+ mAxis.SetPosition(mGestureAmount);
+ mAxis.SetDestination(aTargetValue);
+ mAxis.SetVelocity(mCurrentVelocity);
+
+ mLastAnimationFrameTime = TimeStamp::Now();
+
+ // Add ourselves as a refresh driver observer. The refresh driver
+ // will call WillRefresh for each animation frame until we
+ // unregister ourselves.
+ MOZ_ASSERT(!mRegisteredWithRefreshDriver);
+ if (mRefreshDriver) {
+ mRefreshDriver->AddRefreshObserver(this, Flush_Style);
+ mRegisteredWithRefreshDriver = true;
+ }
+}
+
+void
+SwipeTracker::WillRefresh(mozilla::TimeStamp aTime)
+{
+ TimeStamp now = TimeStamp::Now();
+ mAxis.Simulate(now - mLastAnimationFrameTime);
+ mLastAnimationFrameTime = now;
+
+ bool isFinished = mAxis.IsFinished(1.0 / kWholePagePixelSize);
+ mGestureAmount = (isFinished ? mAxis.GetDestination() : mAxis.GetPosition());
+ SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount);
+
+ if (isFinished) {
+ UnregisterFromRefreshDriver();
+ SwipeFinished();
+ }
+}
+
+void
+SwipeTracker::CancelSwipe()
+{
+ SendSwipeEvent(eSwipeGestureEnd, 0, 0.0);
+}
+
+void SwipeTracker::SwipeFinished()
+{
+ SendSwipeEvent(eSwipeGestureEnd, 0, 0.0);
+ mWidget.SwipeFinished();
+}
+
+void
+SwipeTracker::UnregisterFromRefreshDriver()
+{
+ if (mRegisteredWithRefreshDriver) {
+ MOZ_ASSERT(mRefreshDriver, "How were we able to register, then?");
+ mRefreshDriver->RemoveRefreshObserver(this, Flush_Style);
+ }
+ mRegisteredWithRefreshDriver = false;
+}
+
+/* static */ WidgetSimpleGestureEvent
+SwipeTracker::CreateSwipeGestureEvent(EventMessage aMsg, nsIWidget* aWidget,
+ const LayoutDeviceIntPoint& aPosition)
+{
+ WidgetSimpleGestureEvent geckoEvent(true, aMsg, aWidget);
+ geckoEvent.mModifiers = 0;
+ geckoEvent.mTimeStamp = TimeStamp::Now();
+ geckoEvent.mRefPoint = aPosition;
+ geckoEvent.buttons = 0;
+ return geckoEvent;
+}
+
+bool
+SwipeTracker::SendSwipeEvent(EventMessage aMsg, uint32_t aDirection, double aDelta)
+{
+ WidgetSimpleGestureEvent geckoEvent =
+ CreateSwipeGestureEvent(aMsg, &mWidget, mEventPosition);
+ geckoEvent.mDirection = aDirection;
+ geckoEvent.mDelta = aDelta;
+ geckoEvent.mAllowedDirections = mAllowedDirections;
+ return mWidget.DispatchWindowEvent(geckoEvent);
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/TextInputHandler.h b/widget/cocoa/TextInputHandler.h
new file mode 100644
index 000000000..de7c77593
--- /dev/null
+++ b/widget/cocoa/TextInputHandler.h
@@ -0,0 +1,1195 @@
+/* -*- 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/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "WritingModes.h"
+
+class nsChildView;
+
+namespace mozilla {
+namespace widget {
+
+// Key code constants
+enum
+{
+#if !defined(MAC_OS_X_VERSION_10_12) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+ kVK_RightCommand = 0x36, // right command key
+#endif
+
+ kVK_PC_PrintScreen = kVK_F13,
+ kVK_PC_ScrollLock = kVK_F14,
+ kVK_PC_Pause = kVK_F15,
+
+ kVK_PC_Insert = kVK_Help,
+ kVK_PC_Backspace = kVK_Delete,
+ kVK_PC_Delete = kVK_ForwardDelete,
+
+ kVK_PC_ContextMenu = 0x6E,
+
+ kVK_Powerbook_KeypadEnter = 0x34 // Enter on Powerbook's keyboard is different
+};
+
+/**
+ * TISInputSourceWrapper is a wrapper for the TISInputSourceRef. If we get the
+ * TISInputSourceRef from InputSourceID, we need to release the CFArray instance
+ * which is returned by TISCreateInputSourceList. However, when we release the
+ * list, we cannot access the TISInputSourceRef. So, it's not usable, and it
+ * may cause the memory leak bugs. nsTISInputSource automatically releases the
+ * list when the instance is destroyed.
+ */
+class TISInputSourceWrapper
+{
+public:
+ static TISInputSourceWrapper& CurrentInputSource();
+ /**
+ * Shutdown() should be called when nobody doesn't need to use this class.
+ */
+ static void Shutdown();
+
+ TISInputSourceWrapper()
+ {
+ mInputSourceList = nullptr;
+ Clear();
+ }
+
+ explicit TISInputSourceWrapper(const char* aID)
+ {
+ mInputSourceList = nullptr;
+ InitByInputSourceID(aID);
+ }
+
+ explicit TISInputSourceWrapper(SInt32 aLayoutID)
+ {
+ mInputSourceList = nullptr;
+ InitByLayoutID(aLayoutID);
+ }
+
+ explicit TISInputSourceWrapper(TISInputSourceRef aInputSource)
+ {
+ mInputSourceList = nullptr;
+ InitByTISInputSourceRef(aInputSource);
+ }
+
+ ~TISInputSourceWrapper() { Clear(); }
+
+ void InitByInputSourceID(const char* aID);
+ void InitByInputSourceID(const nsAFlatString &aID);
+ void InitByInputSourceID(const CFStringRef aID);
+ /**
+ * InitByLayoutID() initializes the keyboard layout by the layout ID.
+ *
+ * @param aLayoutID An ID of keyboard layout.
+ * 0: US
+ * 1: Greek
+ * 2: German
+ * 3: Swedish-Pro
+ * 4: Dvorak-Qwerty Cmd
+ * 5: Thai
+ * 6: Arabic
+ * 7: French
+ * 8: Hebrew
+ * 9: Lithuanian
+ * 10: Norwegian
+ * 11: Spanish
+ * @param aOverrideKeyboard When testing set to TRUE, otherwise, set to
+ * FALSE. When TRUE, we use an ANSI keyboard
+ * instead of the actual keyboard.
+ */
+ void InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard = false);
+ void InitByCurrentInputSource();
+ void InitByCurrentKeyboardLayout();
+ void InitByCurrentASCIICapableInputSource();
+ void InitByCurrentASCIICapableKeyboardLayout();
+ void InitByCurrentInputMethodKeyboardLayoutOverride();
+ void InitByTISInputSourceRef(TISInputSourceRef aInputSource);
+ void InitByLanguage(CFStringRef aLanguage);
+
+ /**
+ * If the instance is initialized with a keyboard layout input source,
+ * returns it.
+ * If the instance is initialized with an IME mode input source, the result
+ * references the keyboard layout for the IME mode. However, this can be
+ * initialized only when the IME mode is actually selected. I.e, if IME mode
+ * input source is initialized with LayoutID or SourceID, this returns null.
+ */
+ TISInputSourceRef GetKeyboardLayoutInputSource() const
+ {
+ return mKeyboardLayout;
+ }
+ const UCKeyboardLayout* GetUCKeyboardLayout();
+
+ bool IsOpenedIMEMode();
+ bool IsIMEMode();
+ bool IsKeyboardLayout();
+
+ bool IsASCIICapable()
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetBoolProperty(kTISPropertyInputSourceIsASCIICapable);
+ }
+
+ bool IsEnabled()
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetBoolProperty(kTISPropertyInputSourceIsEnabled);
+ }
+
+ bool GetLanguageList(CFArrayRef &aLanguageList);
+ bool GetPrimaryLanguage(CFStringRef &aPrimaryLanguage);
+ bool GetPrimaryLanguage(nsAString &aPrimaryLanguage);
+
+ bool GetLocalizedName(CFStringRef &aName)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyLocalizedName, aName);
+ }
+
+ bool GetLocalizedName(nsAString &aName)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyLocalizedName, aName);
+ }
+
+ bool GetInputSourceID(CFStringRef &aID)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceID, aID);
+ }
+
+ bool GetInputSourceID(nsAString &aID)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceID, aID);
+ }
+
+ bool GetBundleID(CFStringRef &aBundleID)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyBundleID, aBundleID);
+ }
+
+ bool GetBundleID(nsAString &aBundleID)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyBundleID, aBundleID);
+ }
+
+ bool GetInputSourceType(CFStringRef &aType)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceType, aType);
+ }
+
+ bool GetInputSourceType(nsAString &aType)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceType, aType);
+ }
+
+ bool IsForRTLLanguage();
+ bool IsInitializedByCurrentInputSource();
+
+ enum {
+ // 40 is an actual result of the ::LMGetKbdType() when we connect an
+ // unknown keyboard and set the keyboard type to ANSI manually on the
+ // set up dialog.
+ eKbdType_ANSI = 40
+ };
+
+ void Select();
+ void Clear();
+
+ /**
+ * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
+ *
+ * @param aNativeKeyEvent A native key event for which you want to
+ * dispatch a Gecko key event.
+ * @param aKeyEvent The result -- a Gecko key event initialized
+ * from the native key event.
+ * @param aInsertString If caller expects that the event will cause
+ * a character to be input (say in an editor),
+ * the caller should set this. Otherwise,
+ * if caller sets null to this, this method will
+ * compute the character to be input from
+ * characters of aNativeKeyEvent.
+ */
+ void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+ const nsAString *aInsertString = nullptr);
+
+ /**
+ * WillDispatchKeyboardEvent() computes aKeyEvent.mAlternativeCharCodes and
+ * recompute aKeyEvent.mCharCode if it's necessary.
+ *
+ * @param aNativeKeyEvent A native key event for which you want to
+ * dispatch a Gecko key event.
+ * @param aInsertString If caller expects that the event will cause
+ * a character to be input (say in an editor),
+ * the caller should set this. Otherwise,
+ * if caller sets null to this, this method will
+ * compute the character to be input from
+ * characters of aNativeKeyEvent.
+ * @param aKeyEvent The result -- a Gecko key event initialized
+ * from the native key event. This must be
+ * eKeyPress event.
+ */
+ void WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent,
+ const nsAString* aInsertString,
+ WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * ComputeGeckoKeyCode() returns Gecko keycode for aNativeKeyCode on current
+ * keyboard layout.
+ *
+ * @param aNativeKeyCode A native keycode.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @param aCmdIsPressed TRUE if Cmd key is pressed. Otherwise, FALSE.
+ * @return The computed Gecko keycode.
+ */
+ uint32_t ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType,
+ bool aCmdIsPressed);
+
+ /**
+ * ComputeGeckoKeyNameIndex() returns Gecko key name index for the key.
+ *
+ * @param aNativeKeyCode A native keycode.
+ */
+ static KeyNameIndex ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode);
+
+ /**
+ * ComputeGeckoCodeNameIndex() returns Gecko code name index for the key.
+ *
+ * @param aNativeKeyCode A native keycode.
+ */
+ static CodeNameIndex ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode);
+
+protected:
+ /**
+ * TranslateToString() computes the inputted text from the native keyCode,
+ * modifier flags and keyboard type.
+ *
+ * @param aKeyCode A native keyCode.
+ * @param aModifiers Combination of native modifier flags.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @param aStr Result, i.e., inputted text.
+ * The result can be two or more characters.
+ * @return If succeeded, TRUE. Otherwise, FALSE.
+ * Even if TRUE, aStr can be empty string.
+ */
+ bool TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
+ UInt32 aKbType, nsAString &aStr);
+
+ /**
+ * TranslateToChar() computes the inputted character from the native keyCode,
+ * modifier flags and keyboard type. If two or more characters would be
+ * input, this returns 0.
+ *
+ * @param aKeyCode A native keyCode.
+ * @param aModifiers Combination of native modifier flags.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @return If succeeded and the result is one character,
+ * returns the charCode of it. Otherwise,
+ * returns 0.
+ */
+ uint32_t TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType);
+
+ /**
+ * ComputeInsertString() computes string to be inserted with the key event.
+ *
+ * @param aNativeKeyEvent The native key event which causes our keyboard
+ * event(s).
+ * @param aKeyEvent A Gecko key event which was partially
+ * initialized with aNativeKeyEvent.
+ * @param aInsertString The string to be inputting by aNativeKeyEvent.
+ * This should be specified by InsertText().
+ * In other words, if the key event doesn't cause
+ * a call of InsertText(), this can be nullptr.
+ * @param aResult The string which should be set to charCode of
+ * keypress event(s).
+ */
+ void ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent,
+ const WidgetKeyboardEvent& aKeyEvent,
+ const nsAString* aInsertString,
+ nsAString& aResult);
+
+ /**
+ * IsPrintableKeyEvent() returns true if aNativeKeyEvent is caused by
+ * a printable key. Otherwise, returns false.
+ */
+ bool IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const;
+
+ /**
+ * GetKbdType() returns physical keyboard type.
+ */
+ UInt32 GetKbdType() const;
+
+ bool GetBoolProperty(const CFStringRef aKey);
+ bool GetStringProperty(const CFStringRef aKey, CFStringRef &aStr);
+ bool GetStringProperty(const CFStringRef aKey, nsAString &aStr);
+
+ TISInputSourceRef mInputSource;
+ TISInputSourceRef mKeyboardLayout;
+ CFArrayRef mInputSourceList;
+ const UCKeyboardLayout* mUCKeyboardLayout;
+ int8_t mIsRTL;
+
+ bool mOverrideKeyboard;
+
+ static TISInputSourceWrapper* sCurrentInputSource;
+};
+
+/**
+ * TextInputHandlerBase is a base class of IMEInputHandler and TextInputHandler.
+ * Utility methods should be implemented this level.
+ */
+
+class TextInputHandlerBase : public TextEventDispatcherListener
+{
+public:
+ /**
+ * Other TextEventDispatcherListener methods should be implemented in
+ * IMEInputHandler.
+ */
+ NS_DECL_ISUPPORTS
+
+ /**
+ * DispatchEvent() dispatches aEvent on mWidget.
+ *
+ * @param aEvent An event which you want to dispatch.
+ * @return TRUE if the event is consumed by web contents
+ * or chrome contents. Otherwise, FALSE.
+ */
+ bool DispatchEvent(WidgetGUIEvent& aEvent);
+
+ /**
+ * SetSelection() dispatches eSetSelection event for the aRange.
+ *
+ * @param aRange The range which will be selected.
+ * @return TRUE if setting selection is succeeded and
+ * the widget hasn't been destroyed.
+ * Otherwise, FALSE.
+ */
+ bool SetSelection(NSRange& aRange);
+
+ /**
+ * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
+ *
+ * @param aNativeKeyEvent A native key event for which you want to
+ * dispatch a Gecko key event.
+ * @param aKeyEvent The result -- a Gecko key event initialized
+ * from the native key event.
+ * @param aInsertString If caller expects that the event will cause
+ * a character to be input (say in an editor),
+ * the caller should set this. Otherwise,
+ * if caller sets null to this, this method will
+ * compute the character to be input from
+ * characters of aNativeKeyEvent.
+ */
+ void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+ const nsAString *aInsertString = nullptr);
+
+ /**
+ * SynthesizeNativeKeyEvent() is an implementation of
+ * nsIWidget::SynthesizeNativeKeyEvent(). See the document in nsIWidget.h
+ * for the detail.
+ */
+ nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters);
+
+ /**
+ * Utility method intended for testing. Attempts to construct a native key
+ * event that would have been generated during an actual key press. This
+ * *does not dispatch* the native event. Instead, it is attached to the
+ * |mNativeKeyEvent| field of the Gecko event that is passed in.
+ * @param aKeyEvent Gecko key event to attach the native event to
+ */
+ NS_IMETHOD AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * GetWindowLevel() returns the window level of current focused (in Gecko)
+ * window. E.g., if an <input> element in XUL panel has focus, this returns
+ * the XUL panel's window level.
+ */
+ NSInteger GetWindowLevel();
+
+ /**
+ * IsSpecialGeckoKey() checks whether aNativeKeyCode is mapped to a special
+ * Gecko keyCode. A key is "special" if it isn't used for text input.
+ *
+ * @param aNativeKeyCode A native keycode.
+ * @return If the keycode is mapped to a special key,
+ * TRUE. Otherwise, FALSE.
+ */
+ static bool IsSpecialGeckoKey(UInt32 aNativeKeyCode);
+
+
+ /**
+ * EnableSecureEventInput() and DisableSecureEventInput() wrap the Carbon
+ * Event Manager APIs with the same names. In addition they keep track of
+ * how many times we've called them (in the same process) -- unlike the
+ * Carbon Event Manager APIs, which only keep track of how many times they've
+ * been called from any and all processes.
+ *
+ * The Carbon Event Manager's IsSecureEventInputEnabled() returns whether
+ * secure event input mode is enabled (in any process). This class's
+ * IsSecureEventInputEnabled() returns whether we've made any calls to
+ * EnableSecureEventInput() that are not (yet) offset by the calls we've
+ * made to DisableSecureEventInput().
+ */
+ static void EnableSecureEventInput();
+ static void DisableSecureEventInput();
+ static bool IsSecureEventInputEnabled();
+
+ /**
+ * EnsureSecureEventInputDisabled() calls DisableSecureEventInput() until
+ * our call count becomes 0.
+ */
+ static void EnsureSecureEventInputDisabled();
+
+public:
+ /**
+ * mWidget must not be destroyed without OnDestroyWidget being called.
+ *
+ * @param aDestroyingWidget Destroying widget. This might not be mWidget.
+ * @return This result doesn't have any meaning for
+ * callers. When aDstroyingWidget isn't the same
+ * as mWidget, FALSE. Then, inherited methods in
+ * sub classes should return from this method
+ * without cleaning up.
+ */
+ virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget);
+
+protected:
+ // The creator of this instance, client and its text event dispatcher.
+ // These members must not be nullptr after initialized until
+ // OnDestroyWidget() is called.
+ nsChildView* mWidget; // [WEAK]
+ RefPtr<TextEventDispatcher> mDispatcher;
+
+ // The native view for mWidget.
+ // This view handles the actual text inputting.
+ NSView<mozView>* mView; // [STRONG]
+
+ TextInputHandlerBase(nsChildView* aWidget, NSView<mozView> *aNativeView);
+ virtual ~TextInputHandlerBase();
+
+ bool Destroyed() { return !mWidget; }
+
+ /**
+ * mCurrentKeyEvent indicates what key event we are handling. While
+ * handling a native keydown event, we need to store the event for insertText,
+ * doCommandBySelector and various action message handlers of NSResponder
+ * such as [NSResponder insertNewline:sender].
+ */
+ struct KeyEventState
+ {
+ // Handling native key event
+ NSEvent* mKeyEvent;
+ // String specified by InsertText(). This is not null only during a
+ // call of InsertText().
+ nsAString* mInsertString;
+ // String which are included in [mKeyEvent characters] and already handled
+ // by InsertText() call(s).
+ nsString mInsertedString;
+ // Whether keydown event was consumed by web contents or chrome contents.
+ bool mKeyDownHandled;
+ // Whether keypress event was dispatched for mKeyEvent.
+ bool mKeyPressDispatched;
+ // Whether keypress event was consumed by web contents or chrome contents.
+ bool mKeyPressHandled;
+ // Whether the key event causes other key events via IME or something.
+ bool mCausedOtherKeyEvents;
+ // Whether the key event causes composition change or committing
+ // composition. So, even if InsertText() is called, this may be false
+ // if it dispatches keypress event.
+ bool mCompositionDispatched;
+
+ KeyEventState() : mKeyEvent(nullptr)
+ {
+ Clear();
+ }
+
+ explicit KeyEventState(NSEvent* aNativeKeyEvent) : mKeyEvent(nullptr)
+ {
+ Clear();
+ Set(aNativeKeyEvent);
+ }
+
+ KeyEventState(const KeyEventState &aOther) = delete;
+
+ ~KeyEventState()
+ {
+ Clear();
+ }
+
+ void Set(NSEvent* aNativeKeyEvent)
+ {
+ NS_PRECONDITION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
+ Clear();
+ mKeyEvent = [aNativeKeyEvent retain];
+ }
+
+ void Clear()
+ {
+ if (mKeyEvent) {
+ [mKeyEvent release];
+ mKeyEvent = nullptr;
+ }
+ mInsertString = nullptr;
+ mInsertedString.Truncate();
+ mKeyDownHandled = false;
+ mKeyPressDispatched = false;
+ mKeyPressHandled = false;
+ mCausedOtherKeyEvents = false;
+ mCompositionDispatched = false;
+ }
+
+ bool IsDefaultPrevented() const
+ {
+ return mKeyDownHandled || mKeyPressHandled || mCausedOtherKeyEvents ||
+ mCompositionDispatched;
+ }
+
+ bool CanDispatchKeyPressEvent() const
+ {
+ return !mKeyPressDispatched && !IsDefaultPrevented();
+ }
+
+ void InitKeyEvent(TextInputHandlerBase* aHandler,
+ WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * GetUnhandledString() returns characters of the event which have not been
+ * handled with InsertText() yet. For example, if there is a composition
+ * caused by a dead key press like '`' and it's committed by some key
+ * combinations like |Cmd+v|, then, the |v|'s KeyDown event's |characters|
+ * is |`v|. Then, after |`| is committed with a call of InsertString(),
+ * this returns only 'v'.
+ */
+ void GetUnhandledString(nsAString& aUnhandledString) const;
+ };
+
+ /**
+ * Helper classes for guaranteeing cleaning mCurrentKeyEvent
+ */
+ class AutoKeyEventStateCleaner
+ {
+ public:
+ explicit AutoKeyEventStateCleaner(TextInputHandlerBase* aHandler) :
+ mHandler(aHandler)
+ {
+ }
+
+ ~AutoKeyEventStateCleaner()
+ {
+ mHandler->RemoveCurrentKeyEvent();
+ }
+ private:
+ RefPtr<TextInputHandlerBase> mHandler;
+ };
+
+ class MOZ_STACK_CLASS AutoInsertStringClearer
+ {
+ public:
+ explicit AutoInsertStringClearer(KeyEventState* aState)
+ : mState(aState)
+ {
+ }
+ ~AutoInsertStringClearer();
+
+ private:
+ KeyEventState* mState;
+ };
+
+ /**
+ * mCurrentKeyEvents stores all key events which are being processed.
+ * When we call interpretKeyEvents, IME may generate other key events.
+ * mCurrentKeyEvents[0] is the latest key event.
+ */
+ nsTArray<KeyEventState*> mCurrentKeyEvents;
+
+ /**
+ * mFirstKeyEvent must be used for first key event. This member prevents
+ * memory fragmentation for most key events.
+ */
+ KeyEventState mFirstKeyEvent;
+
+ /**
+ * PushKeyEvent() adds the current key event to mCurrentKeyEvents.
+ */
+ KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent)
+ {
+ uint32_t nestCount = mCurrentKeyEvents.Length();
+ for (uint32_t i = 0; i < nestCount; i++) {
+ // When the key event is caused by another key event, all key events
+ // which are being handled should be marked as "consumed".
+ mCurrentKeyEvents[i]->mCausedOtherKeyEvents = true;
+ }
+
+ KeyEventState* keyEvent = nullptr;
+ if (nestCount == 0) {
+ mFirstKeyEvent.Set(aNativeKeyEvent);
+ keyEvent = &mFirstKeyEvent;
+ } else {
+ keyEvent = new KeyEventState(aNativeKeyEvent);
+ }
+ return *mCurrentKeyEvents.AppendElement(keyEvent);
+ }
+
+ /**
+ * RemoveCurrentKeyEvent() removes the current key event from
+ * mCurrentKeyEvents.
+ */
+ void RemoveCurrentKeyEvent()
+ {
+ NS_ASSERTION(mCurrentKeyEvents.Length() > 0,
+ "RemoveCurrentKeyEvent() is called unexpectedly");
+ KeyEventState* keyEvent = GetCurrentKeyEvent();
+ mCurrentKeyEvents.RemoveElementAt(mCurrentKeyEvents.Length() - 1);
+ if (keyEvent == &mFirstKeyEvent) {
+ keyEvent->Clear();
+ } else {
+ delete keyEvent;
+ }
+ }
+
+ /**
+ * GetCurrentKeyEvent() returns current processing key event.
+ */
+ KeyEventState* GetCurrentKeyEvent()
+ {
+ if (mCurrentKeyEvents.Length() == 0) {
+ return nullptr;
+ }
+ return mCurrentKeyEvents[mCurrentKeyEvents.Length() - 1];
+ }
+
+ struct KeyboardLayoutOverride final
+ {
+ int32_t mKeyboardLayout;
+ bool mOverrideEnabled;
+
+ KeyboardLayoutOverride() :
+ mKeyboardLayout(0), mOverrideEnabled(false)
+ {
+ }
+ };
+
+ const KeyboardLayoutOverride& KeyboardLayoutOverrideRef() const
+ {
+ return mKeyboardOverride;
+ }
+
+ /**
+ * IsPrintableChar() checks whether the unicode character is
+ * a non-printable ASCII character or not. Note that this returns
+ * TRUE even if aChar is a non-printable UNICODE character.
+ *
+ * @param aChar A unicode character.
+ * @return TRUE if aChar is a printable ASCII character
+ * or a unicode character. Otherwise, i.e,
+ * if aChar is a non-printable ASCII character,
+ * FALSE.
+ */
+ static bool IsPrintableChar(char16_t aChar);
+
+ /**
+ * IsNormalCharInputtingEvent() checks whether aKeyEvent causes text input.
+ *
+ * @param aKeyEvent A key event.
+ * @return TRUE if the key event causes text input.
+ * Otherwise, FALSE.
+ */
+ static bool IsNormalCharInputtingEvent(const WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * IsModifierKey() checks whether the native keyCode is for a modifier key.
+ *
+ * @param aNativeKeyCode A native keyCode.
+ * @return TRUE if aNativeKeyCode is for a modifier key.
+ * Otherwise, FALSE.
+ */
+ static bool IsModifierKey(UInt32 aNativeKeyCode);
+
+private:
+ KeyboardLayoutOverride mKeyboardOverride;
+
+ static int32_t sSecureEventInputCount;
+};
+
+/**
+ * IMEInputHandler manages:
+ * 1. The IME/keyboard layout statement of nsChildView.
+ * 2. The IME composition statement of nsChildView.
+ * And also provides the methods which controls the current IME transaction of
+ * the instance.
+ *
+ * Note that an nsChildView handles one or more NSView's events. E.g., even if
+ * a text editor on XUL panel element, the input events handled on the parent
+ * (or its ancestor) widget handles it (the native focus is set to it). The
+ * actual focused view is notified by OnFocusChangeInGecko.
+ */
+
+class IMEInputHandler : public TextInputHandlerBase
+{
+public:
+ // TextEventDispatcherListener methods
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(void) OnRemovedFrom(
+ TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void) WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress,
+ void* aData) override;
+
+public:
+ virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget) override;
+
+ virtual void OnFocusChangeInGecko(bool aFocus);
+
+ void OnSelectionChange(const IMENotification& aIMENotification);
+
+ /**
+ * Call [NSTextInputContext handleEvent] for mouse event support of IME
+ */
+ bool OnHandleEvent(NSEvent* aEvent);
+
+ /**
+ * SetMarkedText() is a handler of setMarkedText of NSTextInput.
+ *
+ * @param aAttrString This mut be an instance of NSAttributedString.
+ * If the aString parameter to
+ * [ChildView setMarkedText:setSelectedRange:]
+ * isn't an instance of NSAttributedString,
+ * create an NSAttributedString from it and pass
+ * that instead.
+ * @param aSelectedRange Current selected range (or caret position).
+ * @param aReplacementRange The range which will be replaced with the
+ * aAttrString instead of current marked range.
+ */
+ void SetMarkedText(NSAttributedString* aAttrString,
+ NSRange& aSelectedRange,
+ NSRange* aReplacementRange = nullptr);
+
+ /**
+ * GetAttributedSubstringFromRange() returns an NSAttributedString instance
+ * which is allocated as autorelease for aRange.
+ *
+ * @param aRange The range of string which you want.
+ * @param aActualRange The actual range of the result.
+ * @return The string in aRange. If the string is empty,
+ * this returns nil. If succeeded, this returns
+ * an instance which is allocated as autorelease.
+ * If this has some troubles, returns nil.
+ */
+ NSAttributedString* GetAttributedSubstringFromRange(
+ NSRange& aRange,
+ NSRange* aActualRange = nullptr);
+
+ /**
+ * SelectedRange() returns current selected range.
+ *
+ * @return If an editor has focus, this returns selection
+ * range in the editor. Otherwise, this returns
+ * selection range in the focused document.
+ */
+ NSRange SelectedRange();
+
+ /**
+ * DrawsVerticallyForCharacterAtIndex() returns whether the character at
+ * the given index is being rendered vertically.
+ *
+ * @param aCharIndex The character offset to query.
+ *
+ * @return True if writing-mode is vertical at the given
+ * character offset; otherwise false.
+ */
+ bool DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex);
+
+ /**
+ * FirstRectForCharacterRange() returns first *character* rect in the range.
+ * Cocoa needs the first line rect in the range, but we cannot compute it
+ * on current implementation.
+ *
+ * @param aRange A range of text to examine. Its position is
+ * an offset from the beginning of the focused
+ * editor or document.
+ * @param aActualRange If this is not null, this returns the actual
+ * range used for computing the result.
+ * @return An NSRect containing the first character in
+ * aRange, in screen coordinates.
+ * If the length of aRange is 0, the width will
+ * be 0.
+ */
+ NSRect FirstRectForCharacterRange(NSRange& aRange,
+ NSRange* aActualRange = nullptr);
+
+ /**
+ * CharacterIndexForPoint() returns an offset of a character at aPoint.
+ * XXX This isn't implemented, always returns 0.
+ *
+ * @param The point in screen coordinates.
+ * @return The offset of the character at aPoint from
+ * the beginning of the focused editor or
+ * document.
+ */
+ NSUInteger CharacterIndexForPoint(NSPoint& aPoint);
+
+ /**
+ * GetValidAttributesForMarkedText() returns attributes which we support.
+ *
+ * @return Always empty array for now.
+ */
+ NSArray* GetValidAttributesForMarkedText();
+
+ bool HasMarkedText();
+ NSRange MarkedRange();
+
+ bool IsIMEComposing() { return mIsIMEComposing; }
+ bool IsIMEOpened();
+ bool IsIMEEnabled() { return mIsIMEEnabled; }
+ bool IsASCIICapableOnly() { return mIsASCIICapableOnly; }
+ bool IgnoreIMECommit() { return mIgnoreIMECommit; }
+
+ bool IgnoreIMEComposition()
+ {
+ // Ignore the IME composition events when we're pending to discard the
+ // composition and we are not to handle the IME composition now.
+ return (mPendingMethods & kDiscardIMEComposition) &&
+ (mIsInFocusProcessing || !IsFocused());
+ }
+
+ void CommitIMEComposition();
+ void CancelIMEComposition();
+
+ void EnableIME(bool aEnableIME);
+ void SetIMEOpenState(bool aOpen);
+ void SetASCIICapableOnly(bool aASCIICapableOnly);
+
+ /**
+ * True if OSX believes that our view has keyboard focus.
+ */
+ bool IsFocused();
+
+ static CFArrayRef CreateAllIMEModeList();
+ static void DebugPrintAllIMEModes();
+
+ // Don't use ::TSMGetActiveDocument() API directly, the document may not
+ // be what you want.
+ static TSMDocumentID GetCurrentTSMDocumentID();
+
+protected:
+ // We cannot do some jobs in the given stack by some reasons.
+ // Following flags and the timer provide the execution pending mechanism,
+ // See the comment in nsCocoaTextInputHandler.mm.
+ nsCOMPtr<nsITimer> mTimer;
+ enum {
+ kNotifyIMEOfFocusChangeInGecko = 1,
+ kDiscardIMEComposition = 2,
+ kSyncASCIICapableOnly = 4
+ };
+ uint32_t mPendingMethods;
+
+ IMEInputHandler(nsChildView* aWidget, NSView<mozView> *aNativeView);
+ virtual ~IMEInputHandler();
+
+ void ResetTimer();
+
+ virtual void ExecutePendingMethods();
+
+ /**
+ * InsertTextAsCommittingComposition() commits current composition. If there
+ * is no composition, this starts a composition and commits it immediately.
+ *
+ * @param aAttrString A string which is committed.
+ * @param aReplacementRange The range which will be replaced with the
+ * aAttrString instead of current selection.
+ */
+ void InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
+ NSRange* aReplacementRange);
+
+private:
+ // If mIsIMEComposing is true, the composition string is stored here.
+ NSString* mIMECompositionString;
+ // If mIsIMEComposing is true, the start offset of the composition string.
+ uint32_t mIMECompositionStart;
+
+ NSRange mMarkedRange;
+ NSRange mSelectedRange;
+
+ NSRange mRangeForWritingMode; // range within which mWritingMode applies
+ mozilla::WritingMode mWritingMode;
+
+ bool mIsIMEComposing;
+ bool mIsIMEEnabled;
+ bool mIsASCIICapableOnly;
+ bool mIgnoreIMECommit;
+ // This flag is enabled by OnFocusChangeInGecko, and will be cleared by
+ // ExecutePendingMethods. When this is true, IsFocus() returns TRUE. At
+ // that time, the focus processing in Gecko might not be finished yet. So,
+ // you cannot use WidgetQueryContentEvent or something.
+ bool mIsInFocusProcessing;
+ bool mIMEHasFocus;
+
+ void KillIMEComposition();
+ void SendCommittedText(NSString *aString);
+ void OpenSystemPreferredLanguageIME();
+
+ // Pending methods
+ void NotifyIMEOfFocusChangeInGecko();
+ void DiscardIMEComposition();
+ void SyncASCIICapableOnly();
+
+ static bool sStaticMembersInitialized;
+ static CFStringRef sLatestIMEOpenedModeInputSourceID;
+ static void InitStaticMembers();
+ static void OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
+ void* aObserver,
+ CFStringRef aName,
+ const void* aObject,
+ CFDictionaryRef aUserInfo);
+
+ static void FlushPendingMethods(nsITimer* aTimer, void* aClosure);
+
+ /**
+ * ConvertToTextRangeStyle converts the given native underline style to
+ * our defined text range type.
+ *
+ * @param aUnderlineStyle NSUnderlineStyleSingle or
+ * NSUnderlineStyleThick.
+ * @param aSelectedRange Current selected range (or caret position).
+ * @return NS_TEXTRANGE_*.
+ */
+ TextRangeType ConvertToTextRangeType(uint32_t aUnderlineStyle,
+ NSRange& aSelectedRange);
+
+ /**
+ * GetRangeCount() computes the range count of aAttrString.
+ *
+ * @param aAttrString An NSAttributedString instance whose number of
+ * NSUnderlineStyleAttributeName ranges you with
+ * to know.
+ * @return The count of NSUnderlineStyleAttributeName
+ * ranges in aAttrString.
+ */
+ uint32_t GetRangeCount(NSAttributedString *aString);
+
+ /**
+ * CreateTextRangeArray() returns text ranges for clauses and/or caret.
+ *
+ * @param aAttrString An NSAttributedString instance which indicates
+ * current composition string.
+ * @param aSelectedRange Current selected range (or caret position).
+ * @return The result is set to the
+ * NSUnderlineStyleAttributeName ranges in
+ * aAttrString.
+ */
+ already_AddRefed<mozilla::TextRangeArray>
+ CreateTextRangeArray(NSAttributedString *aAttrString,
+ NSRange& aSelectedRange);
+
+ /**
+ * DispatchCompositionStartEvent() dispatches a compositionstart event and
+ * initializes the members indicating composition state.
+ *
+ * @return true if it can continues handling composition.
+ * Otherwise, e.g., canceled by the web page,
+ * this returns false.
+ */
+ bool DispatchCompositionStartEvent();
+
+ /**
+ * DispatchCompositionChangeEvent() dispatches a compositionchange event on
+ * mWidget and modifies the members indicating composition state.
+ *
+ * @param aText User text input.
+ * @param aAttrString An NSAttributedString instance which indicates
+ * current composition string.
+ * @param aSelectedRange Current selected range (or caret position).
+ *
+ * @return true if it can continues handling composition.
+ * Otherwise, e.g., canceled by the web page,
+ * this returns false.
+ */
+ bool DispatchCompositionChangeEvent(const nsString& aText,
+ NSAttributedString* aAttrString,
+ NSRange& aSelectedRange);
+
+ /**
+ * DispatchCompositionCommitEvent() dispatches a compositioncommit event or
+ * compositioncommitasis event. If aCommitString is null, dispatches
+ * compositioncommitasis event. I.e., if aCommitString is null, this
+ * commits the composition with the last data. Otherwise, commits the
+ * composition with aCommitString value.
+ *
+ * @return true if the widget isn't destroyed.
+ * Otherwise, false.
+ */
+ bool DispatchCompositionCommitEvent(const nsAString* aCommitString = nullptr);
+
+ // The focused IME handler. Please note that the handler might lost the
+ // actual focus by deactivating the application. If we are active, this
+ // must have the actual focused handle.
+ // We cannot access to the NSInputManager during we aren't active, so, the
+ // focused handler can have an IME transaction even if we are deactive.
+ static IMEInputHandler* sFocusedIMEHandler;
+
+ static bool sCachedIsForRTLLangage;
+};
+
+/**
+ * TextInputHandler implements the NSTextInput protocol.
+ */
+class TextInputHandler : public IMEInputHandler
+{
+public:
+ static NSUInteger sLastModifierState;
+
+ static CFArrayRef CreateAllKeyboardLayoutList();
+ static void DebugPrintAllKeyboardLayouts();
+
+ TextInputHandler(nsChildView* aWidget, NSView<mozView> *aNativeView);
+ virtual ~TextInputHandler();
+
+ /**
+ * KeyDown event handler.
+ *
+ * @param aNativeEvent A native NSKeyDown event.
+ * @return TRUE if the event is consumed by web contents
+ * or chrome contents. Otherwise, FALSE.
+ */
+ bool HandleKeyDownEvent(NSEvent* aNativeEvent);
+
+ /**
+ * KeyUp event handler.
+ *
+ * @param aNativeEvent A native NSKeyUp event.
+ */
+ void HandleKeyUpEvent(NSEvent* aNativeEvent);
+
+ /**
+ * FlagsChanged event handler.
+ *
+ * @param aNativeEvent A native NSFlagsChanged event.
+ */
+ void HandleFlagsChanged(NSEvent* aNativeEvent);
+
+ /**
+ * Insert the string to content. I.e., this is a text input event handler.
+ * If this is called during keydown event handling, this may dispatch a
+ * eKeyPress event. If this is called during composition, this commits
+ * the composition by the aAttrString.
+ *
+ * @param aAttrString An inserted string.
+ * @param aReplacementRange The range which will be replaced with the
+ * aAttrString instead of current selection.
+ */
+ void InsertText(NSAttributedString *aAttrString,
+ NSRange* aReplacementRange = nullptr);
+
+ /**
+ * doCommandBySelector event handler.
+ *
+ * @param aSelector A selector of the command.
+ * @return TRUE if the command is consumed. Otherwise,
+ * FALSE.
+ */
+ bool DoCommandBySelector(const char* aSelector);
+
+ /**
+ * KeyPressWasHandled() checks whether keypress event was handled or not.
+ *
+ * @return TRUE if keypress event for latest native key
+ * event was handled. Otherwise, FALSE.
+ * If this handler isn't handling any key events,
+ * always returns FALSE.
+ */
+ bool KeyPressWasHandled()
+ {
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+ return currentKeyEvent && currentKeyEvent->mKeyPressHandled;
+ }
+
+protected:
+ // Stores the association of device dependent modifier flags with a modifier
+ // keyCode. Being device dependent, this association may differ from one kind
+ // of hardware to the next.
+ struct ModifierKey
+ {
+ NSUInteger flags;
+ unsigned short keyCode;
+
+ ModifierKey(NSUInteger aFlags, unsigned short aKeyCode) :
+ flags(aFlags), keyCode(aKeyCode)
+ {
+ }
+
+ NSUInteger GetDeviceDependentFlags() const
+ {
+ return (flags & ~NSDeviceIndependentModifierFlagsMask);
+ }
+
+ NSUInteger GetDeviceIndependentFlags() const
+ {
+ return (flags & NSDeviceIndependentModifierFlagsMask);
+ }
+ };
+ typedef nsTArray<ModifierKey> ModifierKeyArray;
+ ModifierKeyArray mModifierKeys;
+
+ /**
+ * GetModifierKeyForNativeKeyCode() returns the stored ModifierKey for
+ * the key.
+ */
+ const ModifierKey*
+ GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const;
+
+ /**
+ * GetModifierKeyForDeviceDependentFlags() returns the stored ModifierKey for
+ * the device dependent flags.
+ */
+ const ModifierKey*
+ GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const;
+
+ /**
+ * DispatchKeyEventForFlagsChanged() dispatches keydown event or keyup event
+ * for the aNativeEvent.
+ *
+ * @param aNativeEvent A native flagschanged event which you want to
+ * dispatch our key event for.
+ * @param aDispatchKeyDown TRUE if you want to dispatch a keydown event.
+ * Otherwise, i.e., to dispatch keyup event,
+ * FALSE.
+ */
+ void DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
+ bool aDispatchKeyDown);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // TextInputHandler_h_
diff --git a/widget/cocoa/TextInputHandler.mm b/widget/cocoa/TextInputHandler.mm
new file mode 100644
index 000000000..39965d013
--- /dev/null
+++ b/widget/cocoa/TextInputHandler.mm
@@ -0,0 +1,4534 @@
+/* -*- 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/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+
+#include "nsChildView.h"
+#include "nsObjCExceptions.h"
+#include "nsBidiUtils.h"
+#include "nsToolkit.h"
+#include "nsCocoaUtils.h"
+#include "WidgetUtils.h"
+#include "nsPrintfCString.h"
+#include "ComplexTextInputPanel.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+LazyLogModule gLog("TextInputHandlerWidgets");
+
+static const char*
+OnOrOff(bool aBool)
+{
+ return aBool ? "ON" : "off";
+}
+
+static const char*
+TrueOrFalse(bool aBool)
+{
+ return aBool ? "TRUE" : "FALSE";
+}
+
+static const char*
+GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode)
+{
+ switch (aNativeKeyCode) {
+ case kVK_Escape: return "Escape";
+ case kVK_RightCommand: return "Right-Command";
+ case kVK_Command: return "Command";
+ case kVK_Shift: return "Shift";
+ case kVK_CapsLock: return "CapsLock";
+ case kVK_Option: return "Option";
+ case kVK_Control: return "Control";
+ case kVK_RightShift: return "Right-Shift";
+ case kVK_RightOption: return "Right-Option";
+ case kVK_RightControl: return "Right-Control";
+ case kVK_ANSI_KeypadClear: return "Clear";
+
+ case kVK_F1: return "F1";
+ case kVK_F2: return "F2";
+ case kVK_F3: return "F3";
+ case kVK_F4: return "F4";
+ case kVK_F5: return "F5";
+ case kVK_F6: return "F6";
+ case kVK_F7: return "F7";
+ case kVK_F8: return "F8";
+ case kVK_F9: return "F9";
+ case kVK_F10: return "F10";
+ case kVK_F11: return "F11";
+ case kVK_F12: return "F12";
+ case kVK_F13: return "F13/PrintScreen";
+ case kVK_F14: return "F14/ScrollLock";
+ case kVK_F15: return "F15/Pause";
+
+ case kVK_ANSI_Keypad0: return "NumPad-0";
+ case kVK_ANSI_Keypad1: return "NumPad-1";
+ case kVK_ANSI_Keypad2: return "NumPad-2";
+ case kVK_ANSI_Keypad3: return "NumPad-3";
+ case kVK_ANSI_Keypad4: return "NumPad-4";
+ case kVK_ANSI_Keypad5: return "NumPad-5";
+ case kVK_ANSI_Keypad6: return "NumPad-6";
+ case kVK_ANSI_Keypad7: return "NumPad-7";
+ case kVK_ANSI_Keypad8: return "NumPad-8";
+ case kVK_ANSI_Keypad9: return "NumPad-9";
+
+ case kVK_ANSI_KeypadMultiply: return "NumPad-*";
+ case kVK_ANSI_KeypadPlus: return "NumPad-+";
+ case kVK_ANSI_KeypadMinus: return "NumPad--";
+ case kVK_ANSI_KeypadDecimal: return "NumPad-.";
+ case kVK_ANSI_KeypadDivide: return "NumPad-/";
+ case kVK_ANSI_KeypadEquals: return "NumPad-=";
+ case kVK_ANSI_KeypadEnter: return "NumPad-Enter";
+ case kVK_Return: return "Return";
+ case kVK_Powerbook_KeypadEnter: return "NumPad-EnterOnPowerBook";
+
+ case kVK_PC_Insert: return "Insert/Help";
+ case kVK_PC_Delete: return "Delete";
+ case kVK_Tab: return "Tab";
+ case kVK_PC_Backspace: return "Backspace";
+ case kVK_Home: return "Home";
+ case kVK_End: return "End";
+ case kVK_PageUp: return "PageUp";
+ case kVK_PageDown: return "PageDown";
+ case kVK_LeftArrow: return "LeftArrow";
+ case kVK_RightArrow: return "RightArrow";
+ case kVK_UpArrow: return "UpArrow";
+ case kVK_DownArrow: return "DownArrow";
+ case kVK_PC_ContextMenu: return "ContextMenu";
+
+ case kVK_Function: return "Function";
+ case kVK_VolumeUp: return "VolumeUp";
+ case kVK_VolumeDown: return "VolumeDown";
+ case kVK_Mute: return "Mute";
+
+ case kVK_ISO_Section: return "ISO_Section";
+
+ case kVK_JIS_Yen: return "JIS_Yen";
+ case kVK_JIS_Underscore: return "JIS_Underscore";
+ case kVK_JIS_KeypadComma: return "JIS_KeypadComma";
+ case kVK_JIS_Eisu: return "JIS_Eisu";
+ case kVK_JIS_Kana: return "JIS_Kana";
+
+ case kVK_ANSI_A: return "A";
+ case kVK_ANSI_B: return "B";
+ case kVK_ANSI_C: return "C";
+ case kVK_ANSI_D: return "D";
+ case kVK_ANSI_E: return "E";
+ case kVK_ANSI_F: return "F";
+ case kVK_ANSI_G: return "G";
+ case kVK_ANSI_H: return "H";
+ case kVK_ANSI_I: return "I";
+ case kVK_ANSI_J: return "J";
+ case kVK_ANSI_K: return "K";
+ case kVK_ANSI_L: return "L";
+ case kVK_ANSI_M: return "M";
+ case kVK_ANSI_N: return "N";
+ case kVK_ANSI_O: return "O";
+ case kVK_ANSI_P: return "P";
+ case kVK_ANSI_Q: return "Q";
+ case kVK_ANSI_R: return "R";
+ case kVK_ANSI_S: return "S";
+ case kVK_ANSI_T: return "T";
+ case kVK_ANSI_U: return "U";
+ case kVK_ANSI_V: return "V";
+ case kVK_ANSI_W: return "W";
+ case kVK_ANSI_X: return "X";
+ case kVK_ANSI_Y: return "Y";
+ case kVK_ANSI_Z: return "Z";
+
+ case kVK_ANSI_1: return "1";
+ case kVK_ANSI_2: return "2";
+ case kVK_ANSI_3: return "3";
+ case kVK_ANSI_4: return "4";
+ case kVK_ANSI_5: return "5";
+ case kVK_ANSI_6: return "6";
+ case kVK_ANSI_7: return "7";
+ case kVK_ANSI_8: return "8";
+ case kVK_ANSI_9: return "9";
+ case kVK_ANSI_0: return "0";
+ case kVK_ANSI_Equal: return "Equal";
+ case kVK_ANSI_Minus: return "Minus";
+ case kVK_ANSI_RightBracket: return "RightBracket";
+ case kVK_ANSI_LeftBracket: return "LeftBracket";
+ case kVK_ANSI_Quote: return "Quote";
+ case kVK_ANSI_Semicolon: return "Semicolon";
+ case kVK_ANSI_Backslash: return "Backslash";
+ case kVK_ANSI_Comma: return "Comma";
+ case kVK_ANSI_Slash: return "Slash";
+ case kVK_ANSI_Period: return "Period";
+ case kVK_ANSI_Grave: return "Grave";
+
+ default: return "undefined";
+ }
+}
+
+static const char*
+GetCharacters(const NSString* aString)
+{
+ nsAutoString str;
+ nsCocoaUtils::GetStringForNSString(aString, str);
+ if (str.IsEmpty()) {
+ return "";
+ }
+
+ nsAutoString escapedStr;
+ for (uint32_t i = 0; i < str.Length(); i++) {
+ char16_t ch = str[i];
+ if (ch < 0x20) {
+ nsPrintfCString utf8str("(U+%04X)", ch);
+ escapedStr += NS_ConvertUTF8toUTF16(utf8str);
+ } else if (ch <= 0x7E) {
+ escapedStr += ch;
+ } else {
+ nsPrintfCString utf8str("(U+%04X)", ch);
+ escapedStr += ch;
+ escapedStr += NS_ConvertUTF8toUTF16(utf8str);
+ }
+ }
+
+ // the result will be freed automatically by cocoa.
+ NSString* result = nsCocoaUtils::ToNSString(escapedStr);
+ return [result UTF8String];
+}
+
+static const char*
+GetCharacters(const CFStringRef aString)
+{
+ const NSString* str = reinterpret_cast<const NSString*>(aString);
+ return GetCharacters(str);
+}
+
+static const char*
+GetNativeKeyEventType(NSEvent* aNativeEvent)
+{
+ switch ([aNativeEvent type]) {
+ case NSKeyDown: return "NSKeyDown";
+ case NSKeyUp: return "NSKeyUp";
+ case NSFlagsChanged: return "NSFlagsChanged";
+ default: return "not key event";
+ }
+}
+
+static const char*
+GetGeckoKeyEventType(const WidgetEvent& aEvent)
+{
+ switch (aEvent.mMessage) {
+ case eKeyDown: return "eKeyDown";
+ case eKeyUp: return "eKeyUp";
+ case eKeyPress: return "eKeyPress";
+ default: return "not key event";
+ }
+}
+
+static const char*
+GetWindowLevelName(NSInteger aWindowLevel)
+{
+ switch (aWindowLevel) {
+ case kCGBaseWindowLevelKey:
+ return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
+ case kCGMinimumWindowLevelKey:
+ return "kCGMinimumWindowLevelKey";
+ case kCGDesktopWindowLevelKey:
+ return "kCGDesktopWindowLevelKey";
+ case kCGBackstopMenuLevelKey:
+ return "kCGBackstopMenuLevelKey";
+ case kCGNormalWindowLevelKey:
+ return "kCGNormalWindowLevelKey";
+ case kCGFloatingWindowLevelKey:
+ return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
+ case kCGTornOffMenuWindowLevelKey:
+ return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
+ case kCGDockWindowLevelKey:
+ return "kCGDockWindowLevelKey (NSDockWindowLevel)";
+ case kCGMainMenuWindowLevelKey:
+ return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
+ case kCGStatusWindowLevelKey:
+ return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
+ case kCGModalPanelWindowLevelKey:
+ return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
+ case kCGPopUpMenuWindowLevelKey:
+ return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
+ case kCGDraggingWindowLevelKey:
+ return "kCGDraggingWindowLevelKey";
+ case kCGScreenSaverWindowLevelKey:
+ return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
+ case kCGMaximumWindowLevelKey:
+ return "kCGMaximumWindowLevelKey";
+ case kCGOverlayWindowLevelKey:
+ return "kCGOverlayWindowLevelKey";
+ case kCGHelpWindowLevelKey:
+ return "kCGHelpWindowLevelKey";
+ case kCGUtilityWindowLevelKey:
+ return "kCGUtilityWindowLevelKey";
+ case kCGDesktopIconWindowLevelKey:
+ return "kCGDesktopIconWindowLevelKey";
+ case kCGCursorWindowLevelKey:
+ return "kCGCursorWindowLevelKey";
+ case kCGNumberOfWindowLevelKeys:
+ return "kCGNumberOfWindowLevelKeys";
+ default:
+ return "unknown window level";
+ }
+}
+
+static bool
+IsControlChar(uint32_t aCharCode)
+{
+ return aCharCode < ' ' || aCharCode == 0x7F;
+}
+
+static uint32_t gHandlerInstanceCount = 0;
+
+static void
+EnsureToLogAllKeyboardLayoutsAndIMEs()
+{
+ static bool sDone = false;
+ if (!sDone) {
+ sDone = true;
+ TextInputHandler::DebugPrintAllKeyboardLayouts();
+ IMEInputHandler::DebugPrintAllIMEModes();
+ }
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TISInputSourceWrapper implementation
+ *
+ ******************************************************************************/
+
+TISInputSourceWrapper* TISInputSourceWrapper::sCurrentInputSource = nullptr;
+
+// static
+TISInputSourceWrapper&
+TISInputSourceWrapper::CurrentInputSource()
+{
+ if (!sCurrentInputSource) {
+ sCurrentInputSource = new TISInputSourceWrapper();
+ }
+ if (!sCurrentInputSource->IsInitializedByCurrentInputSource()) {
+ sCurrentInputSource->InitByCurrentInputSource();
+ }
+ return *sCurrentInputSource;
+}
+
+// static
+void
+TISInputSourceWrapper::Shutdown()
+{
+ if (!sCurrentInputSource) {
+ return;
+ }
+ sCurrentInputSource->Clear();
+ delete sCurrentInputSource;
+ sCurrentInputSource = nullptr;
+}
+
+bool
+TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
+ UInt32 aKbType, nsAString &aStr)
+{
+ aStr.Truncate();
+
+ const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
+ "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
+ "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
+ this, aKeyCode, aModifiers, aKbType, UCKey,
+ OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
+ OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
+ OnOrOff(aModifiers & alphaLock),
+ OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
+
+ NS_ENSURE_TRUE(UCKey, false);
+
+ UInt32 deadKeyState = 0;
+ UniCharCount len;
+ UniChar chars[5];
+ OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode,
+ kUCKeyActionDown, aModifiers >> 8,
+ aKbType, kUCKeyTranslateNoDeadKeysMask,
+ &deadKeyState, 5, &len, chars);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu",
+ this, err, len));
+
+ NS_ENSURE_TRUE(err == noErr, false);
+ if (len == 0) {
+ return true;
+ }
+ NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false);
+ NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
+ "size of char16_t and size of UniChar are different");
+ memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"",
+ this, NS_ConvertUTF16toUTF8(aStr).get()));
+
+ return true;
+}
+
+uint32_t
+TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
+ UInt32 aKbType)
+{
+ nsAutoString str;
+ if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
+ str.Length() != 1) {
+ return 0;
+ }
+ return static_cast<uint32_t>(str.CharAt(0));
+}
+
+void
+TISInputSourceWrapper::InitByInputSourceID(const char* aID)
+{
+ Clear();
+ if (!aID)
+ return;
+
+ CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
+ kCFStringEncodingASCII);
+ InitByInputSourceID(idstr);
+ ::CFRelease(idstr);
+}
+
+void
+TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID)
+{
+ Clear();
+ if (aID.IsEmpty())
+ return;
+ CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
+ reinterpret_cast<const UniChar*>(aID.get()),
+ aID.Length());
+ InitByInputSourceID(idstr);
+ ::CFRelease(idstr);
+}
+
+void
+TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID)
+{
+ Clear();
+ if (!aID)
+ return;
+ const void* keys[] = { kTISPropertyInputSourceID };
+ const void* values[] = { aID };
+ CFDictionaryRef filter =
+ ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
+ NS_ASSERTION(filter, "failed to create the filter");
+ mInputSourceList = ::TISCreateInputSourceList(filter, true);
+ ::CFRelease(filter);
+ if (::CFArrayGetCount(mInputSourceList) > 0) {
+ mInputSource = static_cast<TISInputSourceRef>(
+ const_cast<void *>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
+ if (IsKeyboardLayout()) {
+ mKeyboardLayout = mInputSource;
+ }
+ }
+}
+
+void
+TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID,
+ bool aOverrideKeyboard)
+{
+ // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
+ switch (aLayoutID) {
+ case 0:
+ InitByInputSourceID("com.apple.keylayout.US");
+ break;
+ case 1:
+ InitByInputSourceID("com.apple.keylayout.Greek");
+ break;
+ case 2:
+ InitByInputSourceID("com.apple.keylayout.German");
+ break;
+ case 3:
+ InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
+ break;
+ case 4:
+ InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
+ break;
+ case 5:
+ InitByInputSourceID("com.apple.keylayout.Thai");
+ break;
+ case 6:
+ InitByInputSourceID("com.apple.keylayout.Arabic");
+ break;
+ case 7:
+ InitByInputSourceID("com.apple.keylayout.ArabicPC");
+ break;
+ case 8:
+ InitByInputSourceID("com.apple.keylayout.French");
+ break;
+ case 9:
+ InitByInputSourceID("com.apple.keylayout.Hebrew");
+ break;
+ case 10:
+ InitByInputSourceID("com.apple.keylayout.Lithuanian");
+ break;
+ case 11:
+ InitByInputSourceID("com.apple.keylayout.Norwegian");
+ break;
+ case 12:
+ InitByInputSourceID("com.apple.keylayout.Spanish");
+ break;
+ default:
+ Clear();
+ break;
+ }
+ mOverrideKeyboard = aOverrideKeyboard;
+}
+
+void
+TISInputSourceWrapper::InitByCurrentInputSource()
+{
+ Clear();
+ mInputSource = ::TISCopyCurrentKeyboardInputSource();
+ mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
+ if (!mKeyboardLayout) {
+ mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
+ }
+ // If this causes composition, the current keyboard layout may input non-ASCII
+ // characters such as Japanese Kana characters or Hangul characters.
+ // However, we need to set ASCII characters to DOM key events for consistency
+ // with other platforms.
+ if (IsOpenedIMEMode()) {
+ TISInputSourceWrapper tis(mKeyboardLayout);
+ if (!tis.IsASCIICapable()) {
+ mKeyboardLayout =
+ ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
+ }
+ }
+}
+
+void
+TISInputSourceWrapper::InitByCurrentKeyboardLayout()
+{
+ Clear();
+ mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
+ mKeyboardLayout = mInputSource;
+}
+
+void
+TISInputSourceWrapper::InitByCurrentASCIICapableInputSource()
+{
+ Clear();
+ mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
+ mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
+ if (mKeyboardLayout) {
+ TISInputSourceWrapper tis(mKeyboardLayout);
+ if (!tis.IsASCIICapable()) {
+ mKeyboardLayout = nullptr;
+ }
+ }
+ if (!mKeyboardLayout) {
+ mKeyboardLayout =
+ ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
+ }
+}
+
+void
+TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout()
+{
+ Clear();
+ mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
+ mKeyboardLayout = mInputSource;
+}
+
+void
+TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride()
+{
+ Clear();
+ mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
+ mKeyboardLayout = mInputSource;
+}
+
+void
+TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource)
+{
+ Clear();
+ mInputSource = aInputSource;
+ if (IsKeyboardLayout()) {
+ mKeyboardLayout = mInputSource;
+ }
+}
+
+void
+TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage)
+{
+ Clear();
+ mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
+ if (IsKeyboardLayout()) {
+ mKeyboardLayout = mInputSource;
+ }
+}
+
+const UCKeyboardLayout*
+TISInputSourceWrapper::GetUCKeyboardLayout()
+{
+ NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
+ if (mUCKeyboardLayout) {
+ return mUCKeyboardLayout;
+ }
+ CFDataRef uchr = static_cast<CFDataRef>(
+ ::TISGetInputSourceProperty(mKeyboardLayout,
+ kTISPropertyUnicodeKeyLayoutData));
+
+ // We should be always able to get the layout here.
+ NS_ENSURE_TRUE(uchr, nullptr);
+ mUCKeyboardLayout =
+ reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
+ return mUCKeyboardLayout;
+}
+
+bool
+TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey)
+{
+ CFBooleanRef ret = static_cast<CFBooleanRef>(
+ ::TISGetInputSourceProperty(mInputSource, aKey));
+ return ::CFBooleanGetValue(ret);
+}
+
+bool
+TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
+ CFStringRef &aStr)
+{
+ aStr = static_cast<CFStringRef>(
+ ::TISGetInputSourceProperty(mInputSource, aKey));
+ return aStr != nullptr;
+}
+
+bool
+TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
+ nsAString &aStr)
+{
+ CFStringRef str;
+ GetStringProperty(aKey, str);
+ nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
+ return !aStr.IsEmpty();
+}
+
+bool
+TISInputSourceWrapper::IsOpenedIMEMode()
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ if (!IsIMEMode())
+ return false;
+ return !IsASCIICapable();
+}
+
+bool
+TISInputSourceWrapper::IsIMEMode()
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFStringRef str;
+ GetInputSourceType(str);
+ NS_ENSURE_TRUE(str, false);
+ return ::CFStringCompare(kTISTypeKeyboardInputMode,
+ str, 0) == kCFCompareEqualTo;
+}
+
+bool
+TISInputSourceWrapper::IsKeyboardLayout()
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFStringRef str;
+ GetInputSourceType(str);
+ NS_ENSURE_TRUE(str, false);
+ return ::CFStringCompare(kTISTypeKeyboardLayout,
+ str, 0) == kCFCompareEqualTo;
+}
+
+bool
+TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList)
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ aLanguageList = static_cast<CFArrayRef>(
+ ::TISGetInputSourceProperty(mInputSource,
+ kTISPropertyInputSourceLanguages));
+ return aLanguageList != nullptr;
+}
+
+bool
+TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage)
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFArrayRef langList;
+ NS_ENSURE_TRUE(GetLanguageList(langList), false);
+ if (::CFArrayGetCount(langList) == 0)
+ return false;
+ aPrimaryLanguage =
+ static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
+ return aPrimaryLanguage != nullptr;
+}
+
+bool
+TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage)
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFStringRef primaryLanguage;
+ NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
+ nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage,
+ aPrimaryLanguage);
+ return !aPrimaryLanguage.IsEmpty();
+}
+
+bool
+TISInputSourceWrapper::IsForRTLLanguage()
+{
+ if (mIsRTL < 0) {
+ // Get the input character of the 'A' key of ANSI keyboard layout.
+ nsAutoString str;
+ bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
+ NS_ENSURE_TRUE(ret, ret);
+ char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
+ mIsRTL = UCS2_CHAR_IS_BIDI(ch);
+ }
+ return mIsRTL != 0;
+}
+
+bool
+TISInputSourceWrapper::IsInitializedByCurrentInputSource()
+{
+ return mInputSource == ::TISCopyCurrentKeyboardInputSource();
+}
+
+void
+TISInputSourceWrapper::Select()
+{
+ if (!mInputSource)
+ return;
+ ::TISSelectInputSource(mInputSource);
+}
+
+void
+TISInputSourceWrapper::Clear()
+{
+ // Clear() is always called when TISInputSourceWrappper is created.
+ EnsureToLogAllKeyboardLayoutsAndIMEs();
+
+ if (mInputSourceList) {
+ ::CFRelease(mInputSourceList);
+ }
+ mInputSourceList = nullptr;
+ mInputSource = nullptr;
+ mKeyboardLayout = nullptr;
+ mIsRTL = -1;
+ mUCKeyboardLayout = nullptr;
+ mOverrideKeyboard = false;
+}
+
+bool
+TISInputSourceWrapper::IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const
+{
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+
+ bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
+ if (isPrintableKey &&
+ [aNativeKeyEvent type] != NSKeyDown &&
+ [aNativeKeyEvent type] != NSKeyUp) {
+ NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?");
+ isPrintableKey = false;
+ }
+ return isPrintableKey;
+}
+
+UInt32
+TISInputSourceWrapper::GetKbdType() const
+{
+ // If a keyboard layout override is set, we also need to force the keyboard
+ // type to something ANSI to avoid test failures on machines with JIS
+ // keyboards (since the pair of keyboard layout and physical keyboard type
+ // form the actual key layout). This assumes that the test setting the
+ // override was written assuming an ANSI keyboard.
+ return mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
+}
+
+void
+TISInputSourceWrapper::ComputeInsertStringForCharCode(
+ NSEvent* aNativeKeyEvent,
+ const WidgetKeyboardEvent& aKeyEvent,
+ const nsAString* aInsertString,
+ nsAString& aResult)
+{
+ if (aInsertString) {
+ // If the caller expects that the aInsertString will be input, we shouldn't
+ // change it.
+ aResult = *aInsertString;
+ } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
+ // If IME is open, [aNativeKeyEvent characters] may be a character
+ // which will be appended to the composition string. However, especially,
+ // while IME is disabled, most users and developers expect the key event
+ // works as IME closed. So, we should compute the aResult with
+ // the ASCII capable keyboard layout.
+ // NOTE: Such keyboard layouts typically change the layout to its ASCII
+ // capable layout when Command key is pressed. And we don't worry
+ // when Control key is pressed too because it causes inputting
+ // control characters.
+ // Additionally, if the key event doesn't input any text, the event may be
+ // dead key event. In this case, the charCode value should be the dead
+ // character.
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+ if ((!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) ||
+ ![[aNativeKeyEvent characters] length]) {
+ UInt32 state =
+ nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
+ uint32_t ch = TranslateToChar(nativeKeyCode, state, GetKbdType());
+ if (ch) {
+ aResult = ch;
+ }
+ } else {
+ // If the caller isn't sure what string will be input, let's use
+ // characters of NSEvent.
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aResult);
+ }
+
+ // If control key is pressed and the eventChars is a non-printable control
+ // character, we should convert it to ASCII alphabet.
+ if (aKeyEvent.IsControl() &&
+ !aResult.IsEmpty() && aResult[0] <= char16_t(26)) {
+ aResult = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked()) ?
+ static_cast<char16_t>(aResult[0] + ('A' - 1)) :
+ static_cast<char16_t>(aResult[0] + ('a' - 1));
+ }
+ // If Meta key is pressed, it may cause to switch the keyboard layout like
+ // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
+ else if (aKeyEvent.IsMeta() &&
+ !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
+ UInt32 kbType = GetKbdType();
+ UInt32 numLockState =
+ aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
+ UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
+ UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
+ uint32_t uncmdedChar =
+ TranslateToChar(nativeKeyCode, numLockState, kbType);
+ uint32_t cmdedChar =
+ TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
+ // If we can make a good guess at the characters that the user would
+ // expect this key combination to produce (with and without Shift) then
+ // use those characters. This also corrects for CapsLock.
+ uint32_t ch = 0;
+ if (uncmdedChar == cmdedChar) {
+ // The characters produced with Command seem similar to those without
+ // Command.
+ ch = TranslateToChar(nativeKeyCode,
+ shiftState | capsLockState | numLockState, kbType);
+ } else {
+ TISInputSourceWrapper USLayout("com.apple.keylayout.US");
+ uint32_t uncmdedUSChar =
+ USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
+ // If it looks like characters from US keyboard layout when Command key
+ // is pressed, we should compute a character in the layout.
+ if (uncmdedUSChar == cmdedChar) {
+ ch = USLayout.TranslateToChar(nativeKeyCode,
+ shiftState | capsLockState | numLockState, kbType);
+ }
+ }
+
+ // If there is a more preferred character for the commanded key event,
+ // we should use it.
+ if (ch) {
+ aResult = ch;
+ }
+ }
+ }
+
+ // Remove control characters which shouldn't be inputted on editor.
+ // XXX Currently, we don't find any cases inserting control characters with
+ // printable character. So, just checking first character is enough.
+ if (!aResult.IsEmpty() && IsControlChar(aResult[0])) {
+ aResult.Truncate();
+ }
+}
+
+void
+TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
+ WidgetKeyboardEvent& aKeyEvent,
+ const nsAString *aInsertString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
+ "aKeyEvent.mMessage=%s, aInsertString=%p, IsOpenedIMEMode()=%s",
+ this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString,
+ TrueOrFalse(IsOpenedIMEMode())));
+
+ NS_ENSURE_TRUE(aNativeKeyEvent, );
+
+ nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
+
+ // This is used only while dispatching the event (which is a synchronous
+ // call), so there is no need to retain and release this data.
+ aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
+
+ // Fill in fields used for Cocoa NPAPI plugins
+ if ([aNativeKeyEvent type] == NSKeyDown ||
+ [aNativeKeyEvent type] == NSKeyUp) {
+ aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
+ aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
+ nsAutoString nativeChars;
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], nativeChars);
+ aKeyEvent.mNativeCharacters.Assign(nativeChars);
+ nsAutoString nativeCharsIgnoringModifiers;
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers], nativeCharsIgnoringModifiers);
+ aKeyEvent.mNativeCharactersIgnoringModifiers.Assign(nativeCharsIgnoringModifiers);
+ } else if ([aNativeKeyEvent type] == NSFlagsChanged) {
+ aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
+ aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
+ }
+
+ aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ aKeyEvent.mIsChar = false; // XXX not used in XP level
+
+ UInt32 kbType = GetKbdType();
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+
+ aKeyEvent.mKeyCode =
+ ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
+
+ switch (nativeKeyCode) {
+ case kVK_Command:
+ case kVK_Shift:
+ case kVK_Option:
+ case kVK_Control:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+ break;
+
+ case kVK_RightCommand:
+ case kVK_RightShift:
+ case kVK_RightOption:
+ case kVK_RightControl:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+ break;
+
+ case kVK_ANSI_Keypad0:
+ case kVK_ANSI_Keypad1:
+ case kVK_ANSI_Keypad2:
+ case kVK_ANSI_Keypad3:
+ case kVK_ANSI_Keypad4:
+ case kVK_ANSI_Keypad5:
+ case kVK_ANSI_Keypad6:
+ case kVK_ANSI_Keypad7:
+ case kVK_ANSI_Keypad8:
+ case kVK_ANSI_Keypad9:
+ case kVK_ANSI_KeypadMultiply:
+ case kVK_ANSI_KeypadPlus:
+ case kVK_ANSI_KeypadMinus:
+ case kVK_ANSI_KeypadDecimal:
+ case kVK_ANSI_KeypadDivide:
+ case kVK_ANSI_KeypadEquals:
+ case kVK_ANSI_KeypadEnter:
+ case kVK_JIS_KeypadComma:
+ case kVK_Powerbook_KeypadEnter:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+ break;
+
+ default:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ break;
+ }
+
+ aKeyEvent.mIsRepeat =
+ ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::InitKeyEvent, "
+ "shift=%s, ctrl=%s, alt=%s, meta=%s",
+ this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
+ OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
+
+ if (IsPrintableKeyEvent(aNativeKeyEvent)) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ // If insertText calls this method, let's use the string.
+ if (aInsertString && !aInsertString->IsEmpty() &&
+ !IsControlChar((*aInsertString)[0])) {
+ aKeyEvent.mKeyValue = *aInsertString;
+ }
+ // If meta key is pressed, the printable key layout may be switched from
+ // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
+ // KeyboardEvent.key value should be the switched layout's character.
+ else if (aKeyEvent.IsMeta()) {
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
+ aKeyEvent.mKeyValue);
+ }
+ // If control key is pressed, some keys may produce printable character via
+ // [aNativeKeyEvent characters]. Otherwise, translate input character of
+ // the key without control key.
+ else if (aKeyEvent.IsControl()) {
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
+ aKeyEvent.mKeyValue);
+ if (aKeyEvent.mKeyValue.IsEmpty() ||
+ IsControlChar(aKeyEvent.mKeyValue[0])) {
+ NSUInteger cocoaState =
+ [aNativeKeyEvent modifierFlags] & ~NSControlKeyMask;
+ UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
+ aKeyEvent.mKeyValue =
+ TranslateToChar(nativeKeyCode, carbonState, kbType);
+ }
+ }
+ // Otherwise, KeyboardEvent.key expose
+ // [aNativeKeyEvent characters] value. However, if IME is open and the
+ // keyboard layout isn't ASCII capable, exposing the non-ASCII character
+ // doesn't match with other platform's behavior. For the compatibility
+ // with other platform's Gecko, we need to set a translated character.
+ else if (IsOpenedIMEMode()) {
+ UInt32 state =
+ nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
+ aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
+ } else {
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
+ aKeyEvent.mKeyValue);
+ // If the key value is empty, the event may be a dead key event.
+ // If TranslateToChar() returns non-zero value, that means that
+ // the key may input a character with different dead key state.
+ if (aKeyEvent.mKeyValue.IsEmpty()) {
+ NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
+ UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
+ if (TranslateToChar(nativeKeyCode, carbonState, kbType)) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
+ }
+ }
+ }
+
+ // Last resort. If .key value becomes empty string, we should use
+ // charactersIgnoringModifiers, if it's available.
+ if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+ (aKeyEvent.mKeyValue.IsEmpty() ||
+ IsControlChar(aKeyEvent.mKeyValue[0]))) {
+ nsCocoaUtils::GetStringForNSString(
+ [aNativeKeyEvent charactersIgnoringModifiers], aKeyEvent.mKeyValue);
+ // But don't expose it if it's a control character.
+ if (!aKeyEvent.mKeyValue.IsEmpty() &&
+ IsControlChar(aKeyEvent.mKeyValue[0])) {
+ aKeyEvent.mKeyValue.Truncate();
+ }
+ }
+ } else {
+ // Compute the key for non-printable keys and some special printable keys.
+ aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
+ }
+
+ aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode);
+ MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+void
+TISInputSourceWrapper::WillDispatchKeyboardEvent(
+ NSEvent* aNativeKeyEvent,
+ const nsAString* aInsertString,
+ WidgetKeyboardEvent& aKeyEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Nothing to do here if the native key event is neither NSKeyDown nor
+ // NSKeyUp because accessing [aNativeKeyEvent characters] causes throwing
+ // an exception.
+ if ([aNativeKeyEvent type] != NSKeyDown &&
+ [aNativeKeyEvent type] != NSKeyUp) {
+ return;
+ }
+
+ UInt32 kbType = GetKbdType();
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ nsAutoString chars;
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
+ NS_ConvertUTF16toUTF8 utf8Chars(chars);
+ char16_t uniChar = static_cast<char16_t>(aKeyEvent.mCharCode);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "aNativeKeyEvent=%p, [aNativeKeyEvent characters]=\"%s\", "
+ "aKeyEvent={ mMessage=%s, mCharCode=0x%X(%s) }, kbType=0x%X, "
+ "IsOpenedIMEMode()=%s",
+ this, aNativeKeyEvent, utf8Chars.get(),
+ GetGeckoKeyEventType(aKeyEvent), aKeyEvent.mCharCode,
+ uniChar ? NS_ConvertUTF16toUTF8(&uniChar, 1).get() : "",
+ kbType, TrueOrFalse(IsOpenedIMEMode())));
+ }
+
+ nsAutoString insertStringForCharCode;
+ ComputeInsertStringForCharCode(aNativeKeyEvent, aKeyEvent, aInsertString,
+ insertStringForCharCode);
+
+ // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
+ // is pressed, its value should indicate an ASCII character for backward
+ // compatibility rather than inputting character without the modifiers.
+ // Therefore, we need to modify mCharCode value here.
+ uint32_t charCode =
+ insertStringForCharCode.IsEmpty() ? 0 : insertStringForCharCode[0];
+ aKeyEvent.SetCharCode(charCode);
+ // this is not a special key XXX not used in XP
+ aKeyEvent.mIsChar = (aKeyEvent.mMessage == eKeyPress);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "aKeyEvent.mKeyCode=0x%X, aKeyEvent.mCharCode=0x%X",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
+
+ TISInputSourceWrapper USLayout("com.apple.keylayout.US");
+ bool isRomanKeyboardLayout = IsASCIICapable();
+
+ UInt32 key = [aNativeKeyEvent keyCode];
+
+ // Caps lock and num lock modifier state:
+ UInt32 lockState = 0;
+ if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) {
+ lockState |= alphaLock;
+ }
+ if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) {
+ lockState |= kEventKeyModifierNumLockMask;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "isRomanKeyboardLayout=%s, key=0x%X",
+ this, TrueOrFalse(isRomanKeyboardLayout), kbType, key));
+
+ nsString str;
+
+ // normal chars
+ uint32_t unshiftedChar = TranslateToChar(key, lockState, kbType);
+ UInt32 shiftLockMod = shiftKey | lockState;
+ uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, kbType);
+
+ // characters generated with Cmd key
+ // XXX we should remove CapsLock state, which changes characters from
+ // Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
+ // is pressed.
+ UInt32 numState = (lockState & ~alphaLock); // only num lock state
+ uint32_t uncmdedChar = TranslateToChar(key, numState, kbType);
+ UInt32 shiftNumMod = numState | shiftKey;
+ uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType);
+ uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType);
+ UInt32 cmdNumMod = cmdKey | numState;
+ uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, kbType);
+ UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
+ uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType);
+
+ // Is the keyboard layout changed by Cmd key?
+ // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
+ bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
+ // Is the keyboard layout for Latin, but Cmd key switches the layout?
+ // I.e., Dvorak-QWERTY
+ bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
+
+ // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
+ // we should append unshiftedChar and shiftedChar for handling the
+ // normal characters. These are the characters that the user is most
+ // likely to associate with this key.
+ if ((unshiftedChar || shiftedChar) &&
+ (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
+ AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
+ "unshiftedChar=U+%X, shiftedChar=U+%X",
+ this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY),
+ unshiftedChar, shiftedChar));
+
+ // Most keyboard layouts provide the same characters in the NSEvents
+ // with Command+Shift as with Command. However, with Command+Shift we
+ // want the character on the second level. e.g. With a US QWERTY
+ // layout, we want "?" when the "/","?" key is pressed with
+ // Command+Shift.
+
+ // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
+ // even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems
+ // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
+ // event on a US keyboard. The user thinks they are typing Cmd+"?", so
+ // we'll prefer the "?" character, replacing mCharCode with shiftedChar
+ // when Shift is pressed. However, in case there is a layout where the
+ // character unique to Cmd+Shift is the character that the user expects,
+ // we'll send it as an alternative char.
+ bool hasCmdShiftOnlyChar =
+ cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
+ uint32_t originalCmdedShiftChar = cmdedShiftChar;
+
+ // If we can make a good guess at the characters that the user would
+ // expect this key combination to produce (with and without Shift) then
+ // use those characters. This also corrects for CapsLock, which was
+ // ignored above.
+ if (!isCmdSwitchLayout) {
+ // The characters produced with Command seem similar to those without
+ // Command.
+ if (unshiftedChar) {
+ cmdedChar = unshiftedChar;
+ }
+ if (shiftedChar) {
+ cmdedShiftChar = shiftedChar;
+ }
+ } else if (uncmdedUSChar == cmdedChar) {
+ // It looks like characters from a US layout are provided when Command
+ // is down.
+ uint32_t ch = USLayout.TranslateToChar(key, lockState, kbType);
+ if (ch) {
+ cmdedChar = ch;
+ }
+ ch = USLayout.TranslateToChar(key, shiftLockMod, kbType);
+ if (ch) {
+ cmdedShiftChar = ch;
+ }
+ }
+
+ // If the current keyboard layout is switched by the Cmd key,
+ // we should append cmdedChar and shiftedCmdChar that are
+ // Latin char for the key.
+ // If the keyboard layout is Dvorak-QWERTY, we should append them only when
+ // command key is pressed because when command key isn't pressed, uncmded
+ // chars have been appended already.
+ if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
+ (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
+ AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
+ "cmdedChar=U+%X, cmdedShiftChar=U+%X",
+ this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
+ TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
+ // Special case for 'SS' key of German layout. See the comment of
+ // hasCmdShiftOnlyChar definition for the detail.
+ if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
+ AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
+ this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+uint32_t
+TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode,
+ UInt32 aKbType,
+ bool aCmdIsPressed)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
+ "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
+ "IsASCIICapable()=%s",
+ this, aNativeKeyCode, aKbType, TrueOrFalse(aCmdIsPressed),
+ TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
+
+ switch (aNativeKeyCode) {
+ case kVK_Space: return NS_VK_SPACE;
+ case kVK_Escape: return NS_VK_ESCAPE;
+
+ // modifiers
+ case kVK_RightCommand:
+ case kVK_Command: return NS_VK_META;
+ case kVK_RightShift:
+ case kVK_Shift: return NS_VK_SHIFT;
+ case kVK_CapsLock: return NS_VK_CAPS_LOCK;
+ case kVK_RightControl:
+ case kVK_Control: return NS_VK_CONTROL;
+ case kVK_RightOption:
+ case kVK_Option: return NS_VK_ALT;
+
+ case kVK_ANSI_KeypadClear: return NS_VK_CLEAR;
+
+ // function keys
+ case kVK_F1: return NS_VK_F1;
+ case kVK_F2: return NS_VK_F2;
+ case kVK_F3: return NS_VK_F3;
+ case kVK_F4: return NS_VK_F4;
+ case kVK_F5: return NS_VK_F5;
+ case kVK_F6: return NS_VK_F6;
+ case kVK_F7: return NS_VK_F7;
+ case kVK_F8: return NS_VK_F8;
+ case kVK_F9: return NS_VK_F9;
+ case kVK_F10: return NS_VK_F10;
+ case kVK_F11: return NS_VK_F11;
+ case kVK_F12: return NS_VK_F12;
+ // case kVK_F13: return NS_VK_F13; // clash with the 3 below
+ // case kVK_F14: return NS_VK_F14;
+ // case kVK_F15: return NS_VK_F15;
+ case kVK_F16: return NS_VK_F16;
+ case kVK_F17: return NS_VK_F17;
+ case kVK_F18: return NS_VK_F18;
+ case kVK_F19: return NS_VK_F19;
+
+ case kVK_PC_Pause: return NS_VK_PAUSE;
+ case kVK_PC_ScrollLock: return NS_VK_SCROLL_LOCK;
+ case kVK_PC_PrintScreen: return NS_VK_PRINTSCREEN;
+
+ // keypad
+ case kVK_ANSI_Keypad0: return NS_VK_NUMPAD0;
+ case kVK_ANSI_Keypad1: return NS_VK_NUMPAD1;
+ case kVK_ANSI_Keypad2: return NS_VK_NUMPAD2;
+ case kVK_ANSI_Keypad3: return NS_VK_NUMPAD3;
+ case kVK_ANSI_Keypad4: return NS_VK_NUMPAD4;
+ case kVK_ANSI_Keypad5: return NS_VK_NUMPAD5;
+ case kVK_ANSI_Keypad6: return NS_VK_NUMPAD6;
+ case kVK_ANSI_Keypad7: return NS_VK_NUMPAD7;
+ case kVK_ANSI_Keypad8: return NS_VK_NUMPAD8;
+ case kVK_ANSI_Keypad9: return NS_VK_NUMPAD9;
+
+ case kVK_ANSI_KeypadMultiply: return NS_VK_MULTIPLY;
+ case kVK_ANSI_KeypadPlus: return NS_VK_ADD;
+ case kVK_ANSI_KeypadMinus: return NS_VK_SUBTRACT;
+ case kVK_ANSI_KeypadDecimal: return NS_VK_DECIMAL;
+ case kVK_ANSI_KeypadDivide: return NS_VK_DIVIDE;
+
+ case kVK_JIS_KeypadComma: return NS_VK_SEPARATOR;
+
+ // IME keys
+ case kVK_JIS_Eisu: return NS_VK_EISU;
+ case kVK_JIS_Kana: return NS_VK_KANA;
+
+ // these may clash with forward delete and help
+ case kVK_PC_Insert: return NS_VK_INSERT;
+ case kVK_PC_Delete: return NS_VK_DELETE;
+
+ case kVK_PC_Backspace: return NS_VK_BACK;
+ case kVK_Tab: return NS_VK_TAB;
+
+ case kVK_Home: return NS_VK_HOME;
+ case kVK_End: return NS_VK_END;
+
+ case kVK_PageUp: return NS_VK_PAGE_UP;
+ case kVK_PageDown: return NS_VK_PAGE_DOWN;
+
+ case kVK_LeftArrow: return NS_VK_LEFT;
+ case kVK_RightArrow: return NS_VK_RIGHT;
+ case kVK_UpArrow: return NS_VK_UP;
+ case kVK_DownArrow: return NS_VK_DOWN;
+
+ case kVK_PC_ContextMenu: return NS_VK_CONTEXT_MENU;
+
+ case kVK_ANSI_1: return NS_VK_1;
+ case kVK_ANSI_2: return NS_VK_2;
+ case kVK_ANSI_3: return NS_VK_3;
+ case kVK_ANSI_4: return NS_VK_4;
+ case kVK_ANSI_5: return NS_VK_5;
+ case kVK_ANSI_6: return NS_VK_6;
+ case kVK_ANSI_7: return NS_VK_7;
+ case kVK_ANSI_8: return NS_VK_8;
+ case kVK_ANSI_9: return NS_VK_9;
+ case kVK_ANSI_0: return NS_VK_0;
+
+ case kVK_ANSI_KeypadEnter:
+ case kVK_Return:
+ case kVK_Powerbook_KeypadEnter: return NS_VK_RETURN;
+ }
+
+ // If Cmd key is pressed, that causes switching keyboard layout temporarily.
+ // E.g., Dvorak-QWERTY. Therefore, if Cmd key is pressed, we should honor it.
+ UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
+
+ uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
+
+ // Special case for Mac. Mac inputs Yen sign (U+00A5) directly instead of
+ // Back slash (U+005C). We should return NS_VK_BACK_SLASH for compatibility
+ // with other platforms.
+ // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
+ if (charCode == 0x00A5) {
+ return NS_VK_BACK_SLASH;
+ }
+
+ uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
+ if (keyCode) {
+ return keyCode;
+ }
+
+ // If the unshifed char isn't an ASCII character, use shifted char.
+ charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
+ keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
+ if (keyCode) {
+ return keyCode;
+ }
+
+ // If this is ASCII capable, give up to compute it.
+ if (IsASCIICapable()) {
+ return 0;
+ }
+
+ // Retry with ASCII capable keyboard layout.
+ TISInputSourceWrapper currentKeyboardLayout;
+ currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
+ NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
+ keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType,
+ aCmdIsPressed);
+
+ // However, if keyCode isn't for an alphabet keys or a numeric key, we should
+ // ignore it. For example, comma key of Thai layout is same as close-square-
+ // bracket key of US layout and an unicode character key of Thai layout is
+ // same as comma key of US layout. If we return NS_VK_COMMA for latter key,
+ // web application developers cannot distinguish with the former key.
+ return ((keyCode >= NS_VK_A && keyCode <= NS_VK_Z) ||
+ (keyCode >= NS_VK_0 && keyCode <= NS_VK_9)) ? keyCode : 0;
+}
+
+// static
+KeyNameIndex
+TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode)
+{
+ // NOTE:
+ // When unsupported keys like Convert, Nonconvert of Japanese keyboard is
+ // pressed:
+ // on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
+ // on 10.7.x, Nothing happens.
+ // on 10.8.x, Nothing happens.
+ // on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
+ switch (aNativeKeyCode) {
+
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+// static
+CodeNameIndex
+TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode)
+{
+ switch (aNativeKeyCode) {
+
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandler implementation (static methods)
+ *
+ ******************************************************************************/
+
+NSUInteger TextInputHandler::sLastModifierState = 0;
+
+// static
+CFArrayRef
+TextInputHandler::CreateAllKeyboardLayoutList()
+{
+ const void* keys[] = { kTISPropertyInputSourceType };
+ const void* values[] = { kTISTypeKeyboardLayout };
+ CFDictionaryRef filter =
+ ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
+ NS_ASSERTION(filter, "failed to create the filter");
+ CFArrayRef list = ::TISCreateInputSourceList(filter, true);
+ ::CFRelease(filter);
+ return list;
+}
+
+// static
+void
+TextInputHandler::DebugPrintAllKeyboardLayouts()
+{
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ CFArrayRef list = CreateAllKeyboardLayoutList();
+ MOZ_LOG(gLog, LogLevel::Info, ("Keyboard layout configuration:"));
+ CFIndex idx = ::CFArrayGetCount(list);
+ TISInputSourceWrapper tis;
+ for (CFIndex i = 0; i < idx; ++i) {
+ TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
+ const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
+ tis.InitByTISInputSourceRef(inputSource);
+ nsAutoString name, isid;
+ tis.GetLocalizedName(name);
+ tis.GetInputSourceID(isid);
+ MOZ_LOG(gLog, LogLevel::Info,
+ (" %s\t<%s>%s%s\n",
+ NS_ConvertUTF16toUTF8(name).get(),
+ NS_ConvertUTF16toUTF8(isid).get(),
+ tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
+ tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ?
+ "" : "\t(uchr is NOT AVAILABLE)"));
+ }
+ ::CFRelease(list);
+ }
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandler implementation
+ *
+ ******************************************************************************/
+
+TextInputHandler::TextInputHandler(nsChildView* aWidget,
+ NSView<mozView> *aNativeView) :
+ IMEInputHandler(aWidget, aNativeView)
+{
+ EnsureToLogAllKeyboardLayoutsAndIMEs();
+ [mView installTextInputHandler:this];
+}
+
+TextInputHandler::~TextInputHandler()
+{
+ [mView uninstallTextInputHandler];
+}
+
+bool
+TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "widget has been already destroyed", this));
+ return false;
+ }
+
+ // Insert empty line to the log for easier to read.
+ MOZ_LOG(gLog, LogLevel::Info, (""));
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
+ "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
+ "charactersIgnoringModifiers=\"%s\"",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ [aNativeEvent keyCode], [aNativeEvent keyCode],
+ [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
+ GetCharacters([aNativeEvent charactersIgnoringModifiers])));
+
+ // Except when Command key is pressed, we should hide mouse cursor until
+ // next mousemove. Handling here means that:
+ // - Don't hide mouse cursor at pressing modifier key
+ // - Hide mouse cursor even if the key event will be handled by IME (i.e.,
+ // even without dispatching eKeyPress events)
+ // - Hide mouse cursor even when a plugin has focus
+ if (!([aNativeEvent modifierFlags] & NSCommandKeyMask)) {
+ [NSCursor setHiddenUntilMouseMoves:YES];
+ }
+
+ RefPtr<nsChildView> widget(mWidget);
+
+ KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
+ AutoKeyEventStateCleaner remover(this);
+
+ ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+ if (ctiPanel && ctiPanel->IsInComposition()) {
+ nsAutoString committed;
+ ctiPanel->InterpretKeyEvent(aNativeEvent, committed);
+ if (!committed.IsEmpty()) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyDownEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keydown for ComplexTextInputPanel", this));
+ return false;
+ }
+
+ WidgetKeyboardEvent imeEvent(true, eKeyDown, widget);
+ currentKeyEvent->InitKeyEvent(this, imeEvent);
+ imeEvent.mPluginTextEventString.Assign(committed);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(eKeyDown, imeEvent, status,
+ currentKeyEvent);
+ }
+ return true;
+ }
+
+ NSResponder* firstResponder = [[mView window] firstResponder];
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyDownEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keydown for ordinal cases", this));
+ return false;
+ }
+
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
+ currentKeyEvent->InitKeyEvent(this, keydownEvent);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status,
+ currentKeyEvent);
+ currentKeyEvent->mKeyDownHandled =
+ (status == nsEventStatus_eConsumeNoDefault);
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "widget was destroyed by keydown event", this));
+ return currentKeyEvent->IsDefaultPrevented();
+ }
+
+ // The key down event may have shifted the focus, in which
+ // case we should not fire the key press.
+ // XXX This is a special code only on Cocoa widget, why is this needed?
+ if (firstResponder != [[mView window] firstResponder]) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "view lost focus by keydown event", this));
+ return currentKeyEvent->IsDefaultPrevented();
+ }
+
+ if (currentKeyEvent->IsDefaultPrevented()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "keydown event's default is prevented", this));
+ return true;
+ }
+
+ // Let Cocoa interpret the key events, caching IsIMEComposing first.
+ bool wasComposing = IsIMEComposing();
+ bool interpretKeyEventsCalled = false;
+ // Don't call interpretKeyEvents when a plugin has focus. If we call it,
+ // for example, a character is inputted twice during a composition in e10s
+ // mode.
+ if (!widget->IsPluginFocused() && (IsIMEEnabled() || IsASCIICapableOnly())) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents",
+ this));
+ [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
+ interpretKeyEventsCalled = true;
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
+ this));
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed",
+ this));
+ return currentKeyEvent->IsDefaultPrevented();
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
+ "IsIMEComposing()=%s",
+ this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
+
+ if (currentKeyEvent->CanDispatchKeyPressEvent() &&
+ !wasComposing && !IsIMEComposing()) {
+ rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyDownEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keypress", this));
+ return false;
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ currentKeyEvent->InitKeyEvent(this, keypressEvent);
+
+ // If we called interpretKeyEvents and this isn't normal character input
+ // then IME probably ate the event for some reason. We do not want to
+ // send a key press event in that case.
+ // TODO:
+ // There are some other cases which IME eats the current event.
+ // 1. If key events were nested during calling interpretKeyEvents, it means
+ // that IME did something. Then, we should do nothing.
+ // 2. If one or more commands are called like "deleteBackward", we should
+ // dispatch keypress event at that time. Note that the command may have
+ // been a converted or generated action by IME. Then, we shouldn't do
+ // our default action for this key.
+ if (!(interpretKeyEventsCalled &&
+ IsNormalCharInputtingEvent(keypressEvent))) {
+ currentKeyEvent->mKeyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ currentKeyEvent);
+ currentKeyEvent->mKeyPressHandled =
+ (status == nsEventStatus_eConsumeNoDefault);
+ currentKeyEvent->mKeyPressDispatched = true;
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
+ this));
+ }
+ }
+
+ // Note: mWidget might have become null here. Don't count on it from here on.
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s, "
+ "compositionDispatched=%s",
+ this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
+ TrueOrFalse(currentKeyEvent->mKeyPressHandled),
+ TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents),
+ TrueOrFalse(currentKeyEvent->mCompositionDispatched)));
+ // Insert empty line to the log for easier to read.
+ MOZ_LOG(gLog, LogLevel::Info, (""));
+ return currentKeyEvent->IsDefaultPrevented();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+void
+TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
+ "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
+ "charactersIgnoringModifiers=\"%s\", "
+ "IsIMEComposing()=%s",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ [aNativeEvent keyCode], [aNativeEvent keyCode],
+ [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
+ GetCharacters([aNativeEvent charactersIgnoringModifiers]),
+ TrueOrFalse(IsIMEComposing())));
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyUpEvent, "
+ "widget has been already destroyed", this));
+ return;
+ }
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyUpEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return;
+ }
+
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
+ InitKeyEvent(aNativeEvent, keyupEvent);
+
+ KeyEventState currentKeyEvent(aNativeEvent);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status,
+ &currentKeyEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleFlagsChanged, "
+ "widget has been already destroyed", this));
+ return;
+ }
+
+ RefPtr<nsChildView> kungFuDeathGrip(mWidget);
+ mozilla::Unused << kungFuDeathGrip; // Not referenced within this function
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
+ "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08X, "
+ "sLastModifierState=0x%08X, IsIMEComposing()=%s",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
+ [aNativeEvent modifierFlags], sLastModifierState,
+ TrueOrFalse(IsIMEComposing())));
+
+ MOZ_ASSERT([aNativeEvent type] == NSFlagsChanged);
+
+ NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
+ // Device dependent flags for left-control key, both shift keys, both command
+ // keys and both option keys have been defined in Next's SDK. But we
+ // shouldn't use it directly as far as possible since Cocoa SDK doesn't
+ // define them. Fortunately, we need them only when we dispatch keyup
+ // events. So, we can usually know the actual relation between keyCode and
+ // device dependent flags. However, we need to remove following flags first
+ // since the differences don't indicate modifier key state.
+ // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
+ // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
+ // Quartz Event Services.
+ diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
+
+ switch ([aNativeEvent keyCode]) {
+ // CapsLock state and other modifier states are different:
+ // CapsLock state does not revert when the CapsLock key goes up, as the
+ // modifier state does for other modifier keys on key up.
+ case kVK_CapsLock: {
+ // Fire key down event for caps lock.
+ DispatchKeyEventForFlagsChanged(aNativeEvent, true);
+ // XXX should we fire keyup event too? The keyup event for CapsLock key
+ // is never dispatched on Gecko.
+ // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
+ // keyup event. If we do so, we cannot keep the consistency with other
+ // platform's behavior...
+ break;
+ }
+
+ // If the event is caused by pressing or releasing a modifier key, just
+ // dispatch the key's event.
+ case kVK_Shift:
+ case kVK_RightShift:
+ case kVK_Command:
+ case kVK_RightCommand:
+ case kVK_Control:
+ case kVK_RightControl:
+ case kVK_Option:
+ case kVK_RightOption:
+ case kVK_Help: {
+ // We assume that at most one modifier is changed per event if the event
+ // is caused by pressing or releasing a modifier key.
+ bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
+ DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
+ // XXX Some applications might send the event with incorrect device-
+ // dependent flags.
+ if (isKeyDown && ((diff & ~NSDeviceIndependentModifierFlagsMask) != 0)) {
+ unsigned short keyCode = [aNativeEvent keyCode];
+ const ModifierKey* modifierKey =
+ GetModifierKeyForDeviceDependentFlags(diff);
+ if (modifierKey && modifierKey->keyCode != keyCode) {
+ // Although, we're not sure the actual cause of this case, the stored
+ // modifier information and the latest key event information may be
+ // mismatched. Then, let's reset the stored information.
+ // NOTE: If this happens, it may fail to handle NSFlagsChanged event
+ // in the default case (below). However, it's the rare case handler
+ // and this case occurs rarely. So, we can ignore the edge case bug.
+ NS_WARNING("Resetting stored modifier key information");
+ mModifierKeys.Clear();
+ modifierKey = nullptr;
+ }
+ if (!modifierKey) {
+ mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
+ }
+ }
+ break;
+ }
+
+ // Currently we don't support Fn key since other browsers don't dispatch
+ // events for it and we don't have keyCode for this key.
+ // It should be supported when we implement .key and .char.
+ case kVK_Function:
+ break;
+
+ // If the event is caused by something else than pressing or releasing a
+ // single modifier key (for example by the app having been deactivated
+ // using command-tab), use the modifiers themselves to determine which
+ // key's event to dispatch, and whether it's a keyup or keydown event.
+ // In all cases we assume one or more modifiers are being deactivated
+ // (never activated) -- otherwise we'd have received one or more events
+ // corresponding to a single modifier key being pressed.
+ default: {
+ NSUInteger modifiers = sLastModifierState;
+ for (int32_t bit = 0; bit < 32; ++bit) {
+ NSUInteger flag = 1 << bit;
+ if (!(diff & flag)) {
+ continue;
+ }
+
+ // Given correct information from the application, a flag change here
+ // will normally be a deactivation (except for some lockable modifiers
+ // such as CapsLock). But some applications (like VNC) can send an
+ // activating event with a zero keyCode. So we need to check for that
+ // here.
+ bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
+
+ unsigned short keyCode = 0;
+ if (flag & NSDeviceIndependentModifierFlagsMask) {
+ switch (flag) {
+ case NSAlphaShiftKeyMask:
+ keyCode = kVK_CapsLock;
+ dispatchKeyDown = true;
+ break;
+
+ case NSNumericPadKeyMask:
+ // NSNumericPadKeyMask is fired by VNC a lot. But not all of
+ // these events can really be Clear key events, so we just ignore
+ // them.
+ continue;
+
+ case NSHelpKeyMask:
+ keyCode = kVK_Help;
+ break;
+
+ case NSFunctionKeyMask:
+ // An NSFunctionKeyMask change here will normally be a
+ // deactivation. But sometimes it will be an activation send (by
+ // VNC for example) with a zero keyCode.
+ continue;
+
+ // These cases (NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
+ // and NSCommandKeyMask) should be handled by the other branch of
+ // the if statement, below (which handles device dependent flags).
+ // However, some applications (like VNC) can send key events without
+ // any device dependent flags, so we handle them here instead.
+ case NSShiftKeyMask:
+ keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
+ break;
+ case NSControlKeyMask:
+ keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
+ break;
+ case NSAlternateKeyMask:
+ keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
+ break;
+ case NSCommandKeyMask:
+ keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
+ break;
+
+ default:
+ continue;
+ }
+ } else {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForDeviceDependentFlags(flag);
+ if (!modifierKey) {
+ // See the note above (in the other branch of the if statement)
+ // about the NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
+ // and NSCommandKeyMask cases.
+ continue;
+ }
+ keyCode = modifierKey->keyCode;
+ }
+
+ // Remove flags
+ modifiers &= ~flag;
+ switch (keyCode) {
+ case kVK_Shift: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_RightShift);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSShiftKeyMask;
+ }
+ break;
+ }
+ case kVK_RightShift: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_Shift);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSShiftKeyMask;
+ }
+ break;
+ }
+ case kVK_Command: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_RightCommand);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSCommandKeyMask;
+ }
+ break;
+ }
+ case kVK_RightCommand: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_Command);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSCommandKeyMask;
+ }
+ break;
+ }
+ case kVK_Control: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_RightControl);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSControlKeyMask;
+ }
+ break;
+ }
+ case kVK_RightControl: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_Control);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSControlKeyMask;
+ }
+ break;
+ }
+ case kVK_Option: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_RightOption);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSAlternateKeyMask;
+ }
+ break;
+ }
+ case kVK_RightOption: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_Option);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSAlternateKeyMask;
+ }
+ break;
+ }
+ case kVK_Help:
+ modifiers &= ~NSHelpKeyMask;
+ break;
+ default:
+ break;
+ }
+
+ NSEvent* event =
+ [NSEvent keyEventWithType:NSFlagsChanged
+ location:[aNativeEvent locationInWindow]
+ modifierFlags:modifiers
+ timestamp:[aNativeEvent timestamp]
+ windowNumber:[aNativeEvent windowNumber]
+ context:[aNativeEvent context]
+ characters:@""
+ charactersIgnoringModifiers:@""
+ isARepeat:NO
+ keyCode:keyCode];
+ DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
+ if (Destroyed()) {
+ break;
+ }
+
+ // Stop if focus has changed.
+ // Check to see if mView is still the first responder.
+ if (![mView isFirstResponder]) {
+ break;
+ }
+
+ }
+ break;
+ }
+ }
+
+ // Be aware, the widget may have been destroyed.
+ sLastModifierState = [aNativeEvent modifierFlags];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+const TextInputHandler::ModifierKey*
+TextInputHandler::GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const
+{
+ for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
+ if (mModifierKeys[i].keyCode == aKeyCode) {
+ return &((ModifierKey&)mModifierKeys[i]);
+ }
+ }
+ return nullptr;
+}
+
+const TextInputHandler::ModifierKey*
+TextInputHandler::GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const
+{
+ for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
+ if (mModifierKeys[i].GetDeviceDependentFlags() ==
+ (aFlags & ~NSDeviceIndependentModifierFlagsMask)) {
+ return &((ModifierKey&)mModifierKeys[i]);
+ }
+ }
+ return nullptr;
+}
+
+void
+TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
+ bool aDispatchKeyDown)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ return;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
+ "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
+ TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
+
+ if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) {
+ return;
+ }
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchKeyEventForFlagsChanged, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return;
+ }
+
+ EventMessage message = aDispatchKeyDown ? eKeyDown : eKeyUp;
+
+ // Fire a key event.
+ WidgetKeyboardEvent keyEvent(true, message, mWidget);
+ InitKeyEvent(aNativeEvent, keyEvent);
+
+ // Attach a plugin event, in case keyEvent gets dispatched to a plugin. Only
+ // one field is needed -- the type. The other fields can be constructed as
+ // the need arises. But Gecko doesn't have anything equivalent to the
+ // NPCocoaEventFlagsChanged type, and this needs to be passed accurately to
+ // any plugin to which this event is sent.
+ NPCocoaEvent cocoaEvent;
+ nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent);
+ cocoaEvent.type = NPCocoaEventFlagsChanged;
+ keyEvent.mPluginEvent.Copy(cocoaEvent);
+
+ KeyEventState currentKeyEvent(aNativeEvent);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(message, keyEvent, status,
+ &currentKeyEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+TextInputHandler::InsertText(NSAttributedString* aAttrString,
+ NSRange* aReplacementRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ return;
+ }
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
+ "aReplacementRange=%p { location=%llu, length=%llu }, "
+ "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
+ "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
+ "causedOtherKeyEvents=%s, compositionDispatched=%s",
+ this, GetCharacters([aAttrString string]), aReplacementRange,
+ aReplacementRange ? aReplacementRange->location : 0,
+ aReplacementRange ? aReplacementRange->length : 0,
+ TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()),
+ currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
+
+ if (IgnoreIMEComposition()) {
+ return;
+ }
+
+ InputContext context = mWidget->GetInputContext();
+ bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED ||
+ context.mIMEState.mEnabled == IMEState::PASSWORD);
+ NSRange selectedRange = SelectedRange();
+
+ nsAutoString str;
+ nsCocoaUtils::GetStringForNSString([aAttrString string], str);
+
+ AutoInsertStringClearer clearer(currentKeyEvent);
+ if (currentKeyEvent) {
+ currentKeyEvent->mInsertString = &str;
+ }
+
+ if (!IsIMEComposing() && str.IsEmpty()) {
+ // nothing to do if there is no content which can be removed.
+ if (!isEditable) {
+ return;
+ }
+ // If replacement range is specified, we need to remove the range.
+ // Otherwise, we need to remove the selected range if it's not collapsed.
+ if (aReplacementRange && aReplacementRange->location != NSNotFound) {
+ // nothing to do since the range is collapsed.
+ if (aReplacementRange->length == 0) {
+ return;
+ }
+ // If the replacement range is different from current selected range,
+ // select the range.
+ if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
+ NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
+ }
+ selectedRange = SelectedRange();
+ }
+ NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
+ if (selectedRange.length == 0) {
+ return; // nothing to do
+ }
+ // If this is caused by a key input, the keypress event which will be
+ // dispatched later should cause the delete. Therefore, nothing to do here.
+ // Although, we're not sure if such case is actually possible.
+ if (!currentKeyEvent) {
+ return;
+ }
+ // Delete the selected range.
+ RefPtr<TextInputHandler> kungFuDeathGrip(this);
+ WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete,
+ mWidget);
+ DispatchEvent(deleteCommandEvent);
+ NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
+ // Be aware! The widget might be destroyed here.
+ return;
+ }
+
+ bool isReplacingSpecifiedRange =
+ isEditable && aReplacementRange &&
+ aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(selectedRange, *aReplacementRange);
+
+ // If this is not caused by pressing a key, there is a composition or
+ // replacing a range which is different from current selection, let's
+ // insert the text as committing a composition.
+ // If InsertText() is called two or more times, we should insert all
+ // text with composition events.
+ // XXX When InsertText() is called multiple times, Chromium dispatches
+ // only one composition event. So, we need to store InsertText()
+ // calls and flush later.
+ if (!currentKeyEvent || currentKeyEvent->mCompositionDispatched ||
+ IsIMEComposing() || isReplacingSpecifiedRange) {
+ InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
+ if (currentKeyEvent) {
+ currentKeyEvent->mCompositionDispatched = true;
+ }
+ return;
+ }
+
+ // Don't let the same event be fired twice when hitting
+ // enter/return for Bug 420502. However, Korean IME (or some other
+ // simple IME) may work without marked text. For example, composing
+ // character may be inserted as committed text and it's modified with
+ // aReplacementRange. When a keydown starts new composition with
+ // committing previous character, InsertText() may be called twice,
+ // one is for committing previous character and then, inserting new
+ // composing character as committed character. In the latter case,
+ // |CanDispatchKeyPressEvent()| returns true but we need to dispatch
+ // keypress event for the new character. So, when IME tries to insert
+ // printable characters, we should ignore current key event state even
+ // after the keydown has already caused dispatching composition event.
+ // XXX Anyway, we should sort out around this at fixing bug 1338460.
+ if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent() &&
+ (str.IsEmpty() || (str.Length() == 1 && !IsPrintableChar(str[0])))) {
+ return;
+ }
+
+ // XXX Shouldn't we hold mDispatcher instead of mWidget?
+ RefPtr<nsChildView> widget(mWidget);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyUpEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return;
+ }
+
+ // Dispatch keypress event with char instead of compositionchange event
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ // XXX Why do we need to dispatch keypress event for not inputting any
+ // string? If it wants to delete the specified range, should we
+ // dispatch an eContentCommandDelete event instead? Because this
+ // must not be caused by a key operation, a part of IME's processing.
+ keypressEvent.mIsChar = IsPrintableChar(str.CharAt(0));
+
+ // Don't set other modifiers from the current event, because here in
+ // -insertText: they've already been taken into account in creating
+ // the input string.
+
+ if (currentKeyEvent) {
+ currentKeyEvent->InitKeyEvent(this, keypressEvent);
+ } else {
+ nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ keypressEvent.mKeyValue = str;
+ // FYI: TextEventDispatcher will set mKeyCode to 0 for printable key's
+ // keypress events even if they don't cause inputting non-empty string.
+ }
+
+ // Remove basic modifiers from keypress event because if they are included,
+ // nsPlaintextEditor ignores the event.
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+ MODIFIER_ALT |
+ MODIFIER_META);
+
+ // TODO:
+ // If mCurrentKeyEvent.mKeyEvent is null, the text should be inputted as
+ // composition events.
+ nsEventStatus status = nsEventStatus_eIgnore;
+ bool keyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ currentKeyEvent);
+ bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
+
+ // Note: mWidget might have become null here. Don't count on it from here on.
+
+ if (currentKeyEvent) {
+ currentKeyEvent->mKeyPressHandled = keyPressHandled;
+ currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool
+TextInputHandler::DoCommandBySelector(const char* aSelector)
+{
+ RefPtr<nsChildView> widget(mWidget);
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
+ "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, "
+ "causedOtherKeyEvents=%s",
+ this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
+
+ if (currentKeyEvent && currentKeyEvent->CanDispatchKeyPressEvent()) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DoCommandBySelector, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keypress", this));
+ return false;
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ currentKeyEvent->InitKeyEvent(this, keypressEvent);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ currentKeyEvent->mKeyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ currentKeyEvent);
+ currentKeyEvent->mKeyPressHandled =
+ (status == nsEventStatus_eConsumeNoDefault);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::DoCommandBySelector, keypress event "
+ "dispatched, Destroyed()=%s, keypressHandled=%s",
+ this, TrueOrFalse(Destroyed()),
+ TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
+ }
+
+ return (!Destroyed() && currentKeyEvent &&
+ currentKeyEvent->IsDefaultPrevented());
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation (static methods)
+ *
+ ******************************************************************************/
+
+bool IMEInputHandler::sStaticMembersInitialized = false;
+bool IMEInputHandler::sCachedIsForRTLLangage = false;
+CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
+IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
+
+// static
+void
+IMEInputHandler::InitStaticMembers()
+{
+ if (sStaticMembersInitialized)
+ return;
+ sStaticMembersInitialized = true;
+ // We need to check the keyboard layout changes on all applications.
+ CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
+ // XXX Don't we need to remove the observer at shut down?
+ // Mac Dev Center's document doesn't say how to remove the observer if
+ // the second parameter is NULL.
+ ::CFNotificationCenterAddObserver(center, NULL,
+ OnCurrentTextInputSourceChange,
+ kTISNotifySelectedKeyboardInputSourceChanged, NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+ // Initiailize with the current keyboard layout
+ OnCurrentTextInputSourceChange(NULL, NULL,
+ kTISNotifySelectedKeyboardInputSourceChanged,
+ NULL, NULL);
+}
+
+// static
+void
+IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
+ void* aObserver,
+ CFStringRef aName,
+ const void* aObject,
+ CFDictionaryRef aUserInfo)
+{
+ // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentInputSource();
+ if (tis.IsOpenedIMEMode()) {
+ tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
+ }
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ static CFStringRef sLastTIS = nullptr;
+ CFStringRef newTIS;
+ tis.GetInputSourceID(newTIS);
+ if (!sLastTIS ||
+ ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
+ TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
+ tis1.InitByCurrentKeyboardLayout();
+ tis2.InitByCurrentASCIICapableInputSource();
+ tis3.InitByCurrentASCIICapableKeyboardLayout();
+ tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
+ tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
+ CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr,
+ is4 = nullptr, is5 = nullptr, type0 = nullptr,
+ lang0 = nullptr, bundleID0 = nullptr;
+ tis.GetInputSourceID(is0);
+ tis1.GetInputSourceID(is1);
+ tis2.GetInputSourceID(is2);
+ tis3.GetInputSourceID(is3);
+ tis4.GetInputSourceID(is4);
+ tis5.GetInputSourceID(is5);
+ tis.GetInputSourceType(type0);
+ tis.GetPrimaryLanguage(lang0);
+ tis.GetBundleID(bundleID0);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
+ " Current Input Source is changed to:\n"
+ " currentInputContext=%p\n"
+ " %s\n"
+ " type=%s %s\n"
+ " overridden keyboard layout=%s\n"
+ " used keyboard layout for translation=%s\n"
+ " primary language=%s\n"
+ " bundle ID=%s\n"
+ " current ASCII capable Input Source=%s\n"
+ " current Keyboard Layout=%s\n"
+ " current ASCII capable Keyboard Layout=%s",
+ [NSTextInputContext currentInputContext], GetCharacters(is0),
+ GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "",
+ GetCharacters(is4), GetCharacters(is5),
+ GetCharacters(lang0), GetCharacters(bundleID0),
+ GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
+ }
+ sLastTIS = newTIS;
+ }
+
+ /**
+ * When the direction is changed, all the children are notified.
+ * No need to treat the initial case separately because it is covered
+ * by the general case (sCachedIsForRTLLangage is initially false)
+ */
+ if (sCachedIsForRTLLangage != tis.IsForRTLLanguage()) {
+ WidgetUtils::SendBidiKeyboardInfoToContent();
+ sCachedIsForRTLLangage = tis.IsForRTLLanguage();
+ }
+}
+
+// static
+void
+IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure)
+{
+ NS_ASSERTION(aClosure, "aClosure is null");
+ static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
+}
+
+// static
+CFArrayRef
+IMEInputHandler::CreateAllIMEModeList()
+{
+ const void* keys[] = { kTISPropertyInputSourceType };
+ const void* values[] = { kTISTypeKeyboardInputMode };
+ CFDictionaryRef filter =
+ ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
+ NS_ASSERTION(filter, "failed to create the filter");
+ CFArrayRef list = ::TISCreateInputSourceList(filter, true);
+ ::CFRelease(filter);
+ return list;
+}
+
+// static
+void
+IMEInputHandler::DebugPrintAllIMEModes()
+{
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ CFArrayRef list = CreateAllIMEModeList();
+ MOZ_LOG(gLog, LogLevel::Info, ("IME mode configuration:"));
+ CFIndex idx = ::CFArrayGetCount(list);
+ TISInputSourceWrapper tis;
+ for (CFIndex i = 0; i < idx; ++i) {
+ TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
+ const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
+ tis.InitByTISInputSourceRef(inputSource);
+ nsAutoString name, isid;
+ tis.GetLocalizedName(name);
+ tis.GetInputSourceID(isid);
+ MOZ_LOG(gLog, LogLevel::Info,
+ (" %s\t<%s>%s%s\n",
+ NS_ConvertUTF16toUTF8(name).get(),
+ NS_ConvertUTF16toUTF8(isid).get(),
+ tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
+ tis.IsEnabled() ? "" : "\t(Isn't Enabled)"));
+ }
+ ::CFRelease(list);
+ }
+}
+
+//static
+TSMDocumentID
+IMEInputHandler::GetCurrentTSMDocumentID()
+{
+ // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
+ // The result of ::TSMGetActiveDocument() isn't modified for new active text
+ // input context until [NSTextInputContext currentInputContext] is called.
+ // Therefore, we need to call it here.
+ [NSTextInputContext currentInputContext];
+ return ::TSMGetActiveDocument();
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation #1
+ * The methods are releated to the pending methods. Some jobs should be
+ * run after the stack is finished, e.g, some methods cannot run the jobs
+ * during processing the focus event. And also some other jobs should be
+ * run at the next focus event is processed.
+ * The pending methods are recorded in mPendingMethods. They are executed
+ * by ExecutePendingMethods via FlushPendingMethods.
+ *
+ ******************************************************************************/
+
+NS_IMETHODIMP
+IMEInputHandler::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification)
+{
+ switch (aNotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ CommitIMEComposition();
+ return NS_OK;
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ CancelIMEComposition();
+ return NS_OK;
+ case NOTIFY_IME_OF_FOCUS:
+ if (IsFocused()) {
+ nsIWidget* widget = aTextEventDispatcher->GetWidget();
+ if (widget && widget->GetInputContext().IsPasswordEditor()) {
+ EnableSecureEventInput();
+ } else {
+ EnsureSecureEventInputDisabled();
+ }
+ }
+ OnFocusChangeInGecko(true);
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ OnFocusChangeInGecko(false);
+ return NS_OK;
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ OnSelectionChange(aNotification);
+ return NS_OK;
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+NS_IMETHODIMP_(void)
+IMEInputHandler::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
+{
+ // XXX When input transaction is being stolen by add-on, what should we do?
+}
+
+NS_IMETHODIMP_(void)
+IMEInputHandler::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress,
+ void* aData)
+{
+ // If the keyboard event is not caused by a native key event, we can do
+ // nothing here.
+ if (!aData) {
+ return;
+ }
+
+ KeyEventState* currentKeyEvent = static_cast<KeyEventState*>(aData);
+ NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
+ nsAString* insertString = currentKeyEvent->mInsertString;
+ if (KeyboardLayoutOverrideRef().mOverrideEnabled) {
+ TISInputSourceWrapper tis;
+ tis.InitByLayoutID(KeyboardLayoutOverrideRef().mKeyboardLayout, true);
+ tis.WillDispatchKeyboardEvent(nativeEvent, insertString, aKeyboardEvent);
+ return;
+ }
+ TISInputSourceWrapper::CurrentInputSource().
+ WillDispatchKeyboardEvent(nativeEvent, insertString, aKeyboardEvent);
+}
+
+void
+IMEInputHandler::NotifyIMEOfFocusChangeInGecko()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
+ "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
+ this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
+ mView ? [mView inputContext] : nullptr));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (!IsFocused()) {
+ // retry at next focus event
+ mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
+ return;
+ }
+
+ MOZ_ASSERT(mView);
+ NSTextInputContext* inputContext = [mView inputContext];
+ NS_ENSURE_TRUE_VOID(inputContext);
+
+ // When an <input> element on a XUL <panel> element gets focus from an <input>
+ // element on the opener window of the <panel> element, the owner window
+ // still has native focus. Therefore, IMEs may store the opener window's
+ // level at this time because they don't know the actual focus is moved to
+ // different window. If IMEs try to get the newest window level after the
+ // focus change, we return the window level of the XUL <panel>'s widget.
+ // Therefore, let's emulate the native focus change. Then, IMEs can refresh
+ // the stored window level.
+ [inputContext deactivate];
+ [inputContext activate];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::DiscardIMEComposition()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DiscardIMEComposition, "
+ "Destroyed()=%s, IsFocused()=%s, mView=%p, inputContext=%p",
+ this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
+ mView, mView ? [mView inputContext] : nullptr));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (!IsFocused()) {
+ // retry at next focus event
+ mPendingMethods |= kDiscardIMEComposition;
+ return;
+ }
+
+ NS_ENSURE_TRUE_VOID(mView);
+ NSTextInputContext* inputContext = [mView inputContext];
+ NS_ENSURE_TRUE_VOID(inputContext);
+ mIgnoreIMECommit = true;
+ [inputContext discardMarkedText];
+ mIgnoreIMECommit = false;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+void
+IMEInputHandler::SyncASCIICapableOnly()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SyncASCIICapableOnly, "
+ "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
+ "GetCurrentTSMDocumentID()=%p",
+ this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
+ TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (!IsFocused()) {
+ // retry at next focus event
+ mPendingMethods |= kSyncASCIICapableOnly;
+ return;
+ }
+
+ TSMDocumentID doc = GetCurrentTSMDocumentID();
+ if (!doc) {
+ // retry
+ mPendingMethods |= kSyncASCIICapableOnly;
+ NS_WARNING("Application is active but there is no active document");
+ ResetTimer();
+ return;
+ }
+
+ if (mIsASCIICapableOnly) {
+ CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
+ ::TSMSetDocumentProperty(doc,
+ kTSMDocumentEnabledInputSourcesPropertyTag,
+ sizeof(CFArrayRef),
+ &ASCIICapableTISList);
+ ::CFRelease(ASCIICapableTISList);
+ } else {
+ ::TSMRemoveDocumentProperty(doc,
+ kTSMDocumentEnabledInputSourcesPropertyTag);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::ResetTimer()
+{
+ NS_ASSERTION(mPendingMethods != 0,
+ "There are not pending methods, why this is called?");
+ if (mTimer) {
+ mTimer->Cancel();
+ } else {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ NS_ENSURE_TRUE(mTimer, );
+ }
+ mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+IMEInputHandler::ExecutePendingMethods()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ if (![[NSApplication sharedApplication] isActive]) {
+ mIsInFocusProcessing = false;
+ // If we're not active, we should retry at focus event
+ return;
+ }
+
+ uint32_t pendingMethods = mPendingMethods;
+ // First, reset the pending method flags because if each methods cannot
+ // run now, they can reentry to the pending flags by theirselves.
+ mPendingMethods = 0;
+
+ if (pendingMethods & kDiscardIMEComposition)
+ DiscardIMEComposition();
+ if (pendingMethods & kSyncASCIICapableOnly)
+ SyncASCIICapableOnly();
+ if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
+ NotifyIMEOfFocusChangeInGecko();
+ }
+
+ mIsInFocusProcessing = false;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation (native event handlers)
+ *
+ ******************************************************************************/
+
+TextRangeType
+IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
+ NSRange& aSelectedRange)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::ConvertToTextRangeType, "
+ "aUnderlineStyle=%llu, aSelectedRange.length=%llu,",
+ this, aUnderlineStyle, aSelectedRange.length));
+
+ // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
+ // NSUnderlineStyleThick. NSUnderlineStyleThick should indicate a selected
+ // clause. Otherwise, should indicate non-selected clause.
+
+ if (aSelectedRange.length == 0) {
+ switch (aUnderlineStyle) {
+ case NSUnderlineStyleSingle:
+ return TextRangeType::eRawClause;
+ case NSUnderlineStyleThick:
+ return TextRangeType::eSelectedRawClause;
+ default:
+ NS_WARNING("Unexpected line style");
+ return TextRangeType::eSelectedRawClause;
+ }
+ }
+
+ switch (aUnderlineStyle) {
+ case NSUnderlineStyleSingle:
+ return TextRangeType::eConvertedClause;
+ case NSUnderlineStyleThick:
+ return TextRangeType::eSelectedClause;
+ default:
+ NS_WARNING("Unexpected line style");
+ return TextRangeType::eSelectedClause;
+ }
+}
+
+uint32_t
+IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
+ // count the different segments adjusting limitRange as we go.
+ uint32_t count = 0;
+ NSRange effectiveRange;
+ NSRange limitRange = NSMakeRange(0, [aAttrString length]);
+ while (limitRange.length > 0) {
+ [aAttrString attribute:NSUnderlineStyleAttributeName
+ atIndex:limitRange.location
+ longestEffectiveRange:&effectiveRange
+ inRange:limitRange];
+ limitRange =
+ NSMakeRange(NSMaxRange(effectiveRange),
+ NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
+ count++;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu",
+ this, GetCharacters([aAttrString string]), count));
+
+ return count;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+already_AddRefed<mozilla::TextRangeArray>
+IMEInputHandler::CreateTextRangeArray(NSAttributedString *aAttrString,
+ NSRange& aSelectedRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ RefPtr<mozilla::TextRangeArray> textRangeArray =
+ new mozilla::TextRangeArray();
+
+ // Note that we shouldn't append ranges when composition string
+ // is empty because it may cause TextComposition confused.
+ if (![aAttrString length]) {
+ return textRangeArray.forget();
+ }
+
+ // Convert the Cocoa range into the TextRange Array used in Gecko.
+ // Iterate through the attributed string and map the underline attribute to
+ // Gecko IME textrange attributes. We may need to change the code here if
+ // we change the implementation of validAttributesForMarkedText.
+ NSRange limitRange = NSMakeRange(0, [aAttrString length]);
+ uint32_t rangeCount = GetRangeCount(aAttrString);
+ for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
+ NSRange effectiveRange;
+ id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
+ atIndex:limitRange.location
+ longestEffectiveRange:&effectiveRange
+ inRange:limitRange];
+
+ TextRange range;
+ range.mStartOffset = effectiveRange.location;
+ range.mEndOffset = NSMaxRange(effectiveRange);
+ range.mRangeType =
+ ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
+ textRangeArray->AppendElement(range);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CreateTextRangeArray, "
+ "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
+ this, range.mStartOffset, range.mEndOffset,
+ ToChar(range.mRangeType)));
+
+ limitRange =
+ NSMakeRange(NSMaxRange(effectiveRange),
+ NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
+ }
+
+ // Get current caret position.
+ TextRange range;
+ range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
+ range.mEndOffset = range.mStartOffset;
+ range.mRangeType = TextRangeType::eCaret;
+ textRangeArray->AppendElement(range);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CreateTextRangeArray, "
+ "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
+ this, range.mStartOffset, range.mEndOffset,
+ ToChar(range.mRangeType)));
+
+ return textRangeArray.forget();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+bool
+IMEInputHandler::DispatchCompositionStartEvent()
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "mSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, "
+ "mView=%p, mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
+ this, SelectedRange().location, mSelectedRange.length,
+ TrueOrFalse(Destroyed()), mView, mWidget,
+ mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return false;
+ }
+
+ NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
+ mIsIMEComposing = true;
+
+ nsEventStatus status;
+ rv = mDispatcher->StartComposition(status);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "FAILED, due to StartComposition() failure", this));
+ return false;
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "destroyed by compositionstart event", this));
+ return false;
+ }
+
+ // FYI: compositionstart may cause committing composition by the webapp.
+ if (!mIsIMEComposing) {
+ return false;
+ }
+
+ // FYI: The selection range might have been modified by a compositionstart
+ // event handler.
+ mIMECompositionStart = SelectedRange().location;
+ return true;
+}
+
+bool
+IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText,
+ NSAttributedString* aAttrString,
+ NSRange& aSelectedRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "aText=\"%s\", aAttrString=\"%s\", "
+ "aSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, mView=%p, "
+ "mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
+ this, NS_ConvertUTF16toUTF8(aText).get(),
+ GetCharacters([aAttrString string]),
+ aSelectedRange.location, aSelectedRange.length,
+ TrueOrFalse(Destroyed()), mView, mWidget,
+ mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+ NS_ENSURE_TRUE(!Destroyed(), false);
+
+ NS_ASSERTION(mIsIMEComposing, "We're not in composition");
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return false;
+ }
+
+ RefPtr<TextRangeArray> rangeArray =
+ CreateTextRangeArray(aAttrString, aSelectedRange);
+
+ rv = mDispatcher->SetPendingComposition(aText, rangeArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "FAILED, due to SetPendingComposition() failure", this));
+ return false;
+ }
+
+ mSelectedRange.location = mIMECompositionStart + aSelectedRange.location;
+ mSelectedRange.length = aSelectedRange.length;
+
+ if (mIMECompositionString) {
+ [mIMECompositionString release];
+ }
+ mIMECompositionString = [[aAttrString string] retain];
+
+ nsEventStatus status;
+ rv = mDispatcher->FlushPendingComposition(status);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "FAILED, due to FlushPendingComposition() failure", this));
+ return false;
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "destroyed by compositionchange event", this));
+ return false;
+ }
+
+ // FYI: compositionstart may cause committing composition by the webapp.
+ return mIsIMEComposing;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool
+IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "aCommitString=0x%p (\"%s\"), Destroyed()=%s, mView=%p, mWidget=%p, "
+ "inputContext=%p, mIsIMEComposing=%s",
+ this, aCommitString,
+ aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "",
+ TrueOrFalse(Destroyed()), mView, mWidget,
+ mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+ NS_ASSERTION(mIsIMEComposing, "We're not in composition");
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ if (!Destroyed()) {
+ // IME may query selection immediately after this, however, in e10s mode,
+ // OnSelectionChange() will be called asynchronously. Until then, we
+ // should emulate expected selection range if the webapp does nothing.
+ mSelectedRange.location = mIMECompositionStart;
+ if (aCommitString) {
+ mSelectedRange.location += aCommitString->Length();
+ } else if (mIMECompositionString) {
+ nsAutoString commitString;
+ nsCocoaUtils::GetStringForNSString(mIMECompositionString, commitString);
+ mSelectedRange.location += commitString.Length();
+ }
+ mSelectedRange.length = 0;
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ } else {
+ nsEventStatus status;
+ rv = mDispatcher->CommitComposition(status, aCommitString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ }
+ }
+ }
+
+ mIsIMEComposing = false;
+ mIMECompositionStart = UINT32_MAX;
+ if (mIMECompositionString) {
+ [mIMECompositionString release];
+ mIMECompositionString = nullptr;
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "destroyed by compositioncommit event", this));
+ return false;
+ }
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+void
+IMEInputHandler::InsertTextAsCommittingComposition(
+ NSAttributedString* aAttrString,
+ NSRange* aReplacementRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, "
+ "Destroyed()=%s, IsIMEComposing()=%s, "
+ "mMarkedRange={ location=%llu, length=%llu }",
+ this, GetCharacters([aAttrString string]), aReplacementRange,
+ aReplacementRange ? aReplacementRange->location : 0,
+ aReplacementRange ? aReplacementRange->length : 0,
+ TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
+ mMarkedRange.location, mMarkedRange.length));
+
+ if (IgnoreIMECommit()) {
+ MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
+ "be called while canceling the composition");
+ }
+
+ if (Destroyed()) {
+ return;
+ }
+
+ // First, commit current composition with the latest composition string if the
+ // replacement range is different from marked range.
+ if (IsIMEComposing() && aReplacementRange &&
+ aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
+ if (!DispatchCompositionCommitEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "destroyed by commiting composition for setting replacement range",
+ this));
+ return;
+ }
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ nsString str;
+ nsCocoaUtils::GetStringForNSString([aAttrString string], str);
+
+ if (!IsIMEComposing()) {
+ // If there is no selection and replacement range is specified, set the
+ // range as selection.
+ if (aReplacementRange && aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
+ NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
+ }
+
+ if (!DispatchCompositionStartEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "cannot continue handling composition after compositionstart", this));
+ return;
+ }
+ }
+
+ if (!DispatchCompositionCommitEvent(&str)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "destroyed by compositioncommit event", this));
+ return;
+ }
+
+ mMarkedRange = NSMakeRange(NSNotFound, 0);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
+ NSRange& aSelectedRange,
+ NSRange* aReplacementRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, "
+ "aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, "
+ "aReplacementRange=%p { location=%llu, length=%llu }, "
+ "Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, "
+ "mMarkedRange={ location=%llu, length=%llu }, keyevent=%p, "
+ "keydownHandled=%s, keypressDispatched=%s, causedOtherKeyEvents=%s, "
+ "compositionDispatched=%s",
+ this, GetCharacters([aAttrString string]),
+ aSelectedRange.location, aSelectedRange.length, aReplacementRange,
+ aReplacementRange ? aReplacementRange->location : 0,
+ aReplacementRange ? aReplacementRange->length : 0,
+ TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()),
+ TrueOrFalse(IsIMEComposing()),
+ mMarkedRange.location, mMarkedRange.length,
+ currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
+
+ // If SetMarkedText() is called during handling a key press, that means that
+ // the key event caused this composition. So, keypress event shouldn't
+ // be dispatched later, let's mark the key event causing composition event.
+ if (currentKeyEvent) {
+ currentKeyEvent->mCompositionDispatched = true;
+ }
+
+ if (Destroyed() || IgnoreIMEComposition()) {
+ return;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ // First, commit current composition with the latest composition string if the
+ // replacement range is different from marked range.
+ if (IsIMEComposing() && aReplacementRange &&
+ aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
+ AutoRestore<bool> ignoreIMECommit(mIgnoreIMECommit);
+ mIgnoreIMECommit = false;
+ if (!DispatchCompositionCommitEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, "
+ "destroyed by commiting composition for setting replacement range",
+ this));
+ return;
+ }
+ }
+
+ nsString str;
+ nsCocoaUtils::GetStringForNSString([aAttrString string], str);
+
+ mMarkedRange.length = str.Length();
+
+ if (!IsIMEComposing() && !str.IsEmpty()) {
+ // If there is no selection and replacement range is specified, set the
+ // range as selection.
+ if (aReplacementRange && aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
+ NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
+ }
+
+ mMarkedRange.location = SelectedRange().location;
+
+ if (!DispatchCompositionStartEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
+ "composition after dispatching compositionstart", this));
+ return;
+ }
+ }
+
+ if (!str.IsEmpty()) {
+ if (!DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
+ "composition after dispatching compositionchange", this));
+ }
+ return;
+ }
+
+ // If the composition string becomes empty string, we should commit
+ // current composition.
+ if (!DispatchCompositionCommitEvent(&EmptyString())) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, "
+ "destroyed by compositioncommit event", this));
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NSAttributedString*
+IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
+ NSRange* aActualRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+ "aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s",
+ this, aRange.location, aRange.length, aActualRange,
+ TrueOrFalse(Destroyed())));
+
+ if (aActualRange) {
+ *aActualRange = NSMakeRange(NSNotFound, 0);
+ }
+
+ if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
+ return nil;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ // If we're in composing, the queried range may be in the composition string.
+ // In such case, we should use mIMECompositionString since if the composition
+ // string is handled by a remote process, the content cache may be out of
+ // date.
+ // XXX Should we set composition string attributes? Although, Blink claims
+ // that some attributes of marked text are supported, but they return
+ // just marked string without any style. So, let's keep current behavior
+ // at least for now.
+ NSUInteger compositionLength =
+ mIMECompositionString ? [mIMECompositionString length] : 0;
+ if (mIMECompositionStart != UINT32_MAX &&
+ mIMECompositionStart >= aRange.location &&
+ mIMECompositionStart + compositionLength <=
+ aRange.location + aRange.length) {
+ NSRange range =
+ NSMakeRange(aRange.location - mIMECompositionStart, aRange.length);
+ NSString* nsstr = [mIMECompositionString substringWithRange:range];
+ NSMutableAttributedString* result =
+ [[[NSMutableAttributedString alloc] initWithString:nsstr
+ attributes:nil] autorelease];
+ // XXX We cannot return font information in this case. However, this
+ // case must occur only when IME tries to confirm if composing string
+ // is handled as expected.
+ if (aActualRange) {
+ *aActualRange = aRange;
+ }
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ nsAutoString str;
+ nsCocoaUtils::GetStringForNSString(nsstr, str);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+ "computed with mIMECompositionString (result string=\"%s\")",
+ this, NS_ConvertUTF16toUTF8(str).get()));
+ }
+ return result;
+ }
+
+ nsAutoString str;
+ WidgetQueryContentEvent textContent(true, eQueryTextContent, mWidget);
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = aRange.location;
+ if (IsIMEComposing()) {
+ // The composition may be at different offset from the selection start
+ // offset at dispatching compositionstart because start of composition
+ // is fixed when composition string becomes non-empty in the editor.
+ // Therefore, we need to use query event which is relative to insertion
+ // point.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mIMECompositionStart;
+ }
+ textContent.InitForQueryTextContent(startOffset, aRange.length, options);
+ textContent.RequestFontRanges();
+ DispatchEvent(textContent);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+ "textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%u } }",
+ this, TrueOrFalse(textContent.mSucceeded),
+ NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(),
+ textContent.mReply.mOffset));
+
+ if (!textContent.mSucceeded) {
+ return nil;
+ }
+
+ // We don't set vertical information at this point. If required,
+ // OS will calls drawsVerticallyForCharacterAtIndex.
+ NSMutableAttributedString* result =
+ nsCocoaUtils::GetNSMutableAttributedString(textContent.mReply.mString,
+ textContent.mReply.mFontRanges,
+ false,
+ mWidget->BackingScaleFactor());
+ if (aActualRange) {
+ aActualRange->location = textContent.mReply.mOffset;
+ aActualRange->length = textContent.mReply.mString.Length();
+ }
+ return result;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool
+IMEInputHandler::HasMarkedText()
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::HasMarkedText, "
+ "mMarkedRange={ location=%llu, length=%llu }",
+ this, mMarkedRange.location, mMarkedRange.length));
+
+ return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
+}
+
+NSRange
+IMEInputHandler::MarkedRange()
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::MarkedRange, "
+ "mMarkedRange={ location=%llu, length=%llu }",
+ this, mMarkedRange.location, mMarkedRange.length));
+
+ if (!HasMarkedText()) {
+ return NSMakeRange(NSNotFound, 0);
+ }
+ return mMarkedRange;
+}
+
+NSRange
+IMEInputHandler::SelectedRange()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
+ "location=%llu, length=%llu }",
+ this, TrueOrFalse(Destroyed()), mSelectedRange.location,
+ mSelectedRange.length));
+
+ if (Destroyed()) {
+ return mSelectedRange;
+ }
+
+ if (mSelectedRange.location != NSNotFound) {
+ MOZ_ASSERT(mIMEHasFocus);
+ return mSelectedRange;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ WidgetQueryContentEvent selection(true, eQuerySelectedText, mWidget);
+ DispatchEvent(selection);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, "
+ "mReply={ mOffset=%u, mString.Length()=%u } }",
+ this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset,
+ selection.mReply.mString.Length()));
+
+ if (!selection.mSucceeded) {
+ return mSelectedRange;
+ }
+
+ mWritingMode = selection.GetWritingMode();
+ mRangeForWritingMode = NSMakeRange(selection.mReply.mOffset,
+ selection.mReply.mString.Length());
+
+ if (mIMEHasFocus) {
+ mSelectedRange = mRangeForWritingMode;
+ }
+
+ return mRangeForWritingMode;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange);
+}
+
+bool
+IMEInputHandler::DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (Destroyed()) {
+ return false;
+ }
+
+ if (mRangeForWritingMode.location == NSNotFound) {
+ // Update cached writing-mode value for the current selection.
+ SelectedRange();
+ }
+
+ if (aCharIndex < mRangeForWritingMode.location ||
+ aCharIndex > mRangeForWritingMode.location + mRangeForWritingMode.length) {
+ // It's not clear to me whether this ever happens in practice, but if an
+ // IME ever wants to query writing mode at an offset outside the current
+ // selection, the writing-mode value may not be correct for the index.
+ // In that case, use FirstRectForCharacterRange to get a fresh value.
+ // This does more work than strictly necessary (we don't need the rect here),
+ // but should be a rare case.
+ NS_WARNING("DrawsVerticallyForCharacterAtIndex not using cached writing mode");
+ NSRange range = NSMakeRange(aCharIndex, 1);
+ NSRange actualRange;
+ FirstRectForCharacterRange(range, &actualRange);
+ }
+
+ return mWritingMode.IsVertical();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+NSRect
+IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange,
+ NSRange* aActualRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
+ "aRange={ location=%llu, length=%llu }, aActualRange=%p }",
+ this, TrueOrFalse(Destroyed()), aRange.location, aRange.length,
+ aActualRange));
+
+ // XXX this returns first character rect or caret rect, it is limitation of
+ // now. We need more work for returns first line rect. But current
+ // implementation is enough for IMEs.
+
+ NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
+ NSRange actualRange = NSMakeRange(NSNotFound, 0);
+ if (aActualRange) {
+ *aActualRange = actualRange;
+ }
+ if (Destroyed() || aRange.location == NSNotFound) {
+ return rect;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ LayoutDeviceIntRect r;
+ bool useCaretRect = (aRange.length == 0);
+ if (!useCaretRect) {
+ WidgetQueryContentEvent charRect(true, eQueryTextRect, mWidget);
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = aRange.location;
+ if (IsIMEComposing()) {
+ // The composition may be at different offset from the selection start
+ // offset at dispatching compositionstart because start of composition
+ // is fixed when composition string becomes non-empty in the editor.
+ // Therefore, we need to use query event which is relative to insertion
+ // point.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mIMECompositionStart;
+ }
+ charRect.InitForQueryTextRect(startOffset, 1, options);
+ DispatchEvent(charRect);
+ if (charRect.mSucceeded) {
+ r = charRect.mReply.mRect;
+ actualRange.location = charRect.mReply.mOffset;
+ actualRange.length = charRect.mReply.mString.Length();
+ mWritingMode = charRect.GetWritingMode();
+ mRangeForWritingMode = actualRange;
+ } else {
+ useCaretRect = true;
+ }
+ }
+
+ if (useCaretRect) {
+ WidgetQueryContentEvent caretRect(true, eQueryCaretRect, mWidget);
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = aRange.location;
+ if (IsIMEComposing()) {
+ // The composition may be at different offset from the selection start
+ // offset at dispatching compositionstart because start of composition
+ // is fixed when composition string becomes non-empty in the editor.
+ // Therefore, we need to use query event which is relative to insertion
+ // point.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mIMECompositionStart;
+ }
+ caretRect.InitForQueryCaretRect(startOffset, options);
+ DispatchEvent(caretRect);
+ if (!caretRect.mSucceeded) {
+ return rect;
+ }
+ r = caretRect.mReply.mRect;
+ r.width = 0;
+ actualRange.location = caretRect.mReply.mOffset;
+ actualRange.length = 0;
+ }
+
+ nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
+ NSWindow* rootWindow =
+ static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
+ NSView* rootView =
+ static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
+ if (!rootWindow || !rootView) {
+ return rect;
+ }
+ rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
+ rect = [rootView convertRect:rect toView:nil];
+ rect.origin = nsCocoaUtils::ConvertPointToScreen(rootWindow, rect.origin);
+
+ if (aActualRange) {
+ *aActualRange = actualRange;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::FirstRectForCharacterRange, "
+ "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
+ "actualRange={ location=%llu, length=%llu }",
+ this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y,
+ rect.size.width, rect.size.height, actualRange.location,
+ actualRange.length));
+
+ return rect;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
+}
+
+NSUInteger
+IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }",
+ this, aPoint.x, aPoint.y));
+
+ NSWindow* mainWindow = [NSApp mainWindow];
+ if (!mWidget || !mainWindow) {
+ return NSNotFound;
+ }
+
+ WidgetQueryContentEvent charAt(true, eQueryCharacterAtPoint, mWidget);
+ NSPoint ptInWindow = nsCocoaUtils::ConvertPointFromScreen(mainWindow, aPoint);
+ NSPoint ptInView = [mView convertPoint:ptInWindow fromView:nil];
+ charAt.mRefPoint.x =
+ static_cast<int32_t>(ptInView.x) * mWidget->BackingScaleFactor();
+ charAt.mRefPoint.y =
+ static_cast<int32_t>(ptInView.y) * mWidget->BackingScaleFactor();
+ mWidget->DispatchWindowEvent(charAt);
+ if (!charAt.mSucceeded ||
+ charAt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND ||
+ charAt.mReply.mOffset >= static_cast<uint32_t>(NSNotFound)) {
+ return NSNotFound;
+ }
+
+ return charAt.mReply.mOffset;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNotFound);
+}
+
+extern "C" {
+extern NSString *NSTextInputReplacementRangeAttributeName;
+}
+
+NSArray*
+IMEInputHandler::GetValidAttributesForMarkedText()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
+
+ // Return same attributes as Chromium (see render_widget_host_view_mac.mm)
+ // because most IMEs must be tested with Safari (OS default) and Chrome
+ // (having most market share). Therefore, we need to follow their behavior.
+ // XXX It might be better to reuse an array instance for this result because
+ // this may be called a lot. Note that Chromium does so.
+ return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName,
+ NSUnderlineColorAttributeName,
+ NSMarkedClauseSegmentAttributeName,
+ NSTextInputReplacementRangeAttributeName,
+ nil];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation #2
+ *
+ ******************************************************************************/
+
+IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
+ NSView<mozView> *aNativeView)
+ : TextInputHandlerBase(aWidget, aNativeView)
+ , mPendingMethods(0)
+ , mIMECompositionString(nullptr)
+ , mIMECompositionStart(UINT32_MAX)
+ , mIsIMEComposing(false)
+ , mIsIMEEnabled(true)
+ , mIsASCIICapableOnly(false)
+ , mIgnoreIMECommit(false)
+ , mIsInFocusProcessing(false)
+ , mIMEHasFocus(false)
+{
+ InitStaticMembers();
+
+ mMarkedRange.location = NSNotFound;
+ mMarkedRange.length = 0;
+ mSelectedRange.location = NSNotFound;
+ mSelectedRange.length = 0;
+}
+
+IMEInputHandler::~IMEInputHandler()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (sFocusedIMEHandler == this) {
+ sFocusedIMEHandler = nullptr;
+ }
+ if (mIMECompositionString) {
+ [mIMECompositionString release];
+ mIMECompositionString = nullptr;
+ }
+}
+
+void
+IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
+ "sFocusedIMEHandler=%p",
+ this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
+
+ mSelectedRange.location = NSNotFound; // Marking dirty
+ mIMEHasFocus = aFocus;
+
+ // This is called when the native focus is changed and when the native focus
+ // isn't changed but the focus is changed in Gecko.
+ if (!aFocus) {
+ if (sFocusedIMEHandler == this)
+ sFocusedIMEHandler = nullptr;
+ return;
+ }
+
+ sFocusedIMEHandler = this;
+ mIsInFocusProcessing = true;
+
+ // We need to notify IME of focus change in Gecko as native focus change
+ // because the window level of the focused element in Gecko may be changed.
+ mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
+ ResetTimer();
+}
+
+bool
+IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
+ "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
+ this, aDestroyingWidget, sFocusedIMEHandler,
+ TrueOrFalse(IsIMEComposing())));
+
+ // If we're not focused, the focused IMEInputHandler may have been
+ // created by another widget/nsChildView.
+ if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
+ sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
+ }
+
+ if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) {
+ return false;
+ }
+
+ if (IsIMEComposing()) {
+ // If our view is in the composition, we should clean up it.
+ CancelIMEComposition();
+ }
+
+ mSelectedRange.location = NSNotFound; // Marking dirty
+ mIMEHasFocus = false;
+
+ return true;
+}
+
+void
+IMEInputHandler::SendCommittedText(NSString *aString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
+ "inputContext=%p, mIsIMEComposing=%s",
+ this, mView, mWidget, mView ? [mView inputContext] : nullptr,
+ TrueOrFalse(mIsIMEComposing), mWidget));
+
+ NS_ENSURE_TRUE(mWidget, );
+ // XXX We should send the string without mView.
+ if (!mView) {
+ return;
+ }
+
+ NSAttributedString* attrStr =
+ [[NSAttributedString alloc] initWithString:aString];
+ if ([mView conformsToProtocol:@protocol(NSTextInputClient)]) {
+ NSObject<NSTextInputClient>* textInputClient =
+ static_cast<NSObject<NSTextInputClient>*>(mView);
+ [textInputClient insertText:attrStr
+ replacementRange:NSMakeRange(NSNotFound, 0)];
+ }
+
+ // Last resort. If we cannot retrieve NSTextInputProtocol from mView
+ // or blocking to call our InsertText(), we should call InsertText()
+ // directly to commit composition forcibly.
+ if (mIsIMEComposing) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SendCommittedText, trying to insert text directly "
+ "due to IME not calling our InsertText()", this));
+ static_cast<TextInputHandler*>(this)->InsertText(attrStr);
+ MOZ_ASSERT(!mIsIMEComposing);
+ }
+
+ [attrStr release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::KillIMEComposition()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
+ "inputContext=%p, mIsIMEComposing=%s, "
+ "Destroyed()=%s, IsFocused()=%s",
+ this, mView, mWidget, mView ? [mView inputContext] : nullptr,
+ TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()),
+ TrueOrFalse(IsFocused())));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (IsFocused()) {
+ NS_ENSURE_TRUE_VOID(mView);
+ NSTextInputContext* inputContext = [mView inputContext];
+ NS_ENSURE_TRUE_VOID(inputContext);
+ [inputContext discardMarkedText];
+ return;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::KillIMEComposition, Pending...", this));
+
+ // Commit the composition internally.
+ SendCommittedText(mIMECompositionString);
+ NS_ASSERTION(!mIsIMEComposing, "We're still in a composition");
+ // The pending method will be fired by the next focus event.
+ mPendingMethods |= kDiscardIMEComposition;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::CommitIMEComposition()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!IsIMEComposing())
+ return;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s",
+ this, GetCharacters(mIMECompositionString)));
+
+ KillIMEComposition();
+
+ if (!IsIMEComposing())
+ return;
+
+ // If the composition is still there, KillIMEComposition only kills the
+ // composition in TSM. We also need to finish the our composition too.
+ SendCommittedText(mIMECompositionString);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::CancelIMEComposition()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!IsIMEComposing())
+ return;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s",
+ this, GetCharacters(mIMECompositionString)));
+
+ // For canceling the current composing, we need to ignore the param of
+ // insertText. But this code is ugly...
+ mIgnoreIMECommit = true;
+ KillIMEComposition();
+ mIgnoreIMECommit = false;
+
+ if (!IsIMEComposing())
+ return;
+
+ // If the composition is still there, KillIMEComposition only kills the
+ // composition in TSM. We also need to kill the our composition too.
+ SendCommittedText(@"");
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool
+IMEInputHandler::IsFocused()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE(!Destroyed(), false);
+ NSWindow* window = [mView window];
+ NS_ENSURE_TRUE(window, false);
+ return [window firstResponder] == mView &&
+ [window isKeyWindow] &&
+ [[NSApplication sharedApplication] isActive];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool
+IMEInputHandler::IsIMEOpened()
+{
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentInputSource();
+ return tis.IsOpenedIMEMode();
+}
+
+void
+IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly)
+{
+ if (aASCIICapableOnly == mIsASCIICapableOnly)
+ return;
+
+ CommitIMEComposition();
+ mIsASCIICapableOnly = aASCIICapableOnly;
+ SyncASCIICapableOnly();
+}
+
+void
+IMEInputHandler::EnableIME(bool aEnableIME)
+{
+ if (aEnableIME == mIsIMEEnabled)
+ return;
+
+ CommitIMEComposition();
+ mIsIMEEnabled = aEnableIME;
+}
+
+void
+IMEInputHandler::SetIMEOpenState(bool aOpenIME)
+{
+ if (!IsFocused() || IsIMEOpened() == aOpenIME)
+ return;
+
+ if (!aOpenIME) {
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentASCIICapableInputSource();
+ tis.Select();
+ return;
+ }
+
+ // If we know the latest IME opened mode, we should select it.
+ if (sLatestIMEOpenedModeInputSourceID) {
+ TISInputSourceWrapper tis;
+ tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
+ tis.Select();
+ return;
+ }
+
+ // XXX If the current input source is a mode of IME, we should turn on it,
+ // but we haven't found such way...
+
+ // Finally, we should refer the system locale but this is a little expensive,
+ // we shouldn't retry this (if it was succeeded, we already set
+ // sLatestIMEOpenedModeInputSourceID at that time).
+ static bool sIsPrefferredIMESearched = false;
+ if (sIsPrefferredIMESearched)
+ return;
+ sIsPrefferredIMESearched = true;
+ OpenSystemPreferredLanguageIME();
+}
+
+void
+IMEInputHandler::OpenSystemPreferredLanguageIME()
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
+
+ CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
+ if (!langList) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL",
+ this));
+ return;
+ }
+ CFIndex count = ::CFArrayGetCount(langList);
+ for (CFIndex i = 0; i < count; i++) {
+ CFLocaleRef locale =
+ ::CFLocaleCreate(kCFAllocatorDefault,
+ static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
+ if (!locale) {
+ continue;
+ }
+
+ bool changed = false;
+ CFStringRef lang = static_cast<CFStringRef>(
+ ::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
+ NS_ASSERTION(lang, "lang is null");
+ if (lang) {
+ TISInputSourceWrapper tis;
+ tis.InitByLanguage(lang);
+ if (tis.IsOpenedIMEMode()) {
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ CFStringRef foundTIS;
+ tis.GetInputSourceID(foundTIS);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
+ "foundTIS=%s, lang=%s",
+ this, GetCharacters(foundTIS), GetCharacters(lang)));
+ }
+ tis.Select();
+ changed = true;
+ }
+ }
+ ::CFRelease(locale);
+ if (changed) {
+ break;
+ }
+ }
+ ::CFRelease(langList);
+}
+
+void
+IMEInputHandler::OnSelectionChange(const IMENotification& aIMENotification)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OnSelectionChange", this));
+
+ if (aIMENotification.mSelectionChangeData.mOffset == UINT32_MAX) {
+ mSelectedRange.location = NSNotFound;
+ mSelectedRange.length = 0;
+ mRangeForWritingMode.location = NSNotFound;
+ mRangeForWritingMode.length = 0;
+ return;
+ }
+
+ mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
+ mRangeForWritingMode =
+ NSMakeRange(aIMENotification.mSelectionChangeData.mOffset,
+ aIMENotification.mSelectionChangeData.Length());
+ if (mIMEHasFocus) {
+ mSelectedRange = mRangeForWritingMode;
+ }
+}
+
+bool
+IMEInputHandler::OnHandleEvent(NSEvent* aEvent)
+{
+ if (!IsFocused()) {
+ return false;
+ }
+ NSTextInputContext* inputContext = [mView inputContext];
+ return [inputContext handleEvent:aEvent];
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandlerBase implementation
+ *
+ ******************************************************************************/
+
+int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
+
+NS_IMPL_ISUPPORTS(TextInputHandlerBase,
+ TextEventDispatcherListener,
+ nsISupportsWeakReference)
+
+TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget,
+ NSView<mozView> *aNativeView)
+ : mWidget(aWidget)
+ , mDispatcher(aWidget->GetTextEventDispatcher())
+{
+ gHandlerInstanceCount++;
+ mView = [aNativeView retain];
+}
+
+TextInputHandlerBase::~TextInputHandlerBase()
+{
+ [mView release];
+ if (--gHandlerInstanceCount == 0) {
+ TISInputSourceWrapper::Shutdown();
+ }
+}
+
+bool
+TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::OnDestroyWidget, "
+ "aDestroyingWidget=%p, mWidget=%p",
+ this, aDestroyingWidget, mWidget));
+
+ if (aDestroyingWidget != mWidget) {
+ return false;
+ }
+
+ mWidget = nullptr;
+ mDispatcher = nullptr;
+ return true;
+}
+
+bool
+TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent)
+{
+ return mWidget->DispatchWindowEvent(aEvent);
+}
+
+void
+TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent,
+ WidgetKeyboardEvent& aKeyEvent,
+ const nsAString* aInsertString)
+{
+ NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
+
+ if (mKeyboardOverride.mOverrideEnabled) {
+ TISInputSourceWrapper tis;
+ tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
+ tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
+ return;
+ }
+ TISInputSourceWrapper::CurrentInputSource().
+ InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
+}
+
+nsresult
+TextInputHandlerBase::SynthesizeNativeKeyEvent(
+ int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ static const uint32_t sModifierFlagMap[][2] = {
+ { nsIWidget::CAPS_LOCK, NSAlphaShiftKeyMask },
+ { nsIWidget::SHIFT_L, NSShiftKeyMask | 0x0002 },
+ { nsIWidget::SHIFT_R, NSShiftKeyMask | 0x0004 },
+ { nsIWidget::CTRL_L, NSControlKeyMask | 0x0001 },
+ { nsIWidget::CTRL_R, NSControlKeyMask | 0x2000 },
+ { nsIWidget::ALT_L, NSAlternateKeyMask | 0x0020 },
+ { nsIWidget::ALT_R, NSAlternateKeyMask | 0x0040 },
+ { nsIWidget::COMMAND_L, NSCommandKeyMask | 0x0008 },
+ { nsIWidget::COMMAND_R, NSCommandKeyMask | 0x0010 },
+ { nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask },
+ { nsIWidget::HELP, NSHelpKeyMask },
+ { nsIWidget::FUNCTION, NSFunctionKeyMask }
+ };
+
+ uint32_t modifierFlags = 0;
+ for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
+ if (aModifierFlags & sModifierFlagMap[i][0]) {
+ modifierFlags |= sModifierFlagMap[i][1];
+ }
+ }
+
+ NSInteger windowNumber = [[mView window] windowNumber];
+ bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
+ NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown;
+ NSEvent* downEvent =
+ [NSEvent keyEventWithType:eventType
+ location:NSMakePoint(0,0)
+ modifierFlags:modifierFlags
+ timestamp:0
+ windowNumber:windowNumber
+ context:[NSGraphicsContext currentContext]
+ characters:nsCocoaUtils::ToNSString(aCharacters)
+ charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
+ isARepeat:NO
+ keyCode:aNativeKeyCode];
+
+ NSEvent* upEvent = sendFlagsChangedEvent ?
+ nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent);
+
+ if (downEvent && (sendFlagsChangedEvent || upEvent)) {
+ KeyboardLayoutOverride currentLayout = mKeyboardOverride;
+ mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
+ mKeyboardOverride.mOverrideEnabled = true;
+ [NSApp sendEvent:downEvent];
+ if (upEvent) {
+ [NSApp sendEvent:upEvent];
+ }
+ // processKeyDownEvent and keyUp block exceptions so we're sure to
+ // reach here to restore mKeyboardOverride
+ mKeyboardOverride = currentLayout;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NSInteger
+TextInputHandlerBase::GetWindowLevel()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
+ this, TrueOrFalse(Destroyed())));
+
+ if (Destroyed()) {
+ return NSNormalWindowLevel;
+ }
+
+ // When an <input> element on a XUL <panel> is focused, the actual focused view
+ // is the panel's parent view (mView). But the editor is displayed on the
+ // popped-up widget's view (editorView). We want the latter's window level.
+ NSView<mozView>* editorView = mWidget->GetEditorView();
+ NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
+ NSInteger windowLevel = [[editorView window] level];
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)",
+ this, GetWindowLevelName(windowLevel), windowLevel));
+
+ return windowLevel;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
+}
+
+NS_IMETHODIMP
+TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Don't try to replace a native event if one already exists.
+ // OS X doesn't have an OS modifier, can't make a native event.
+ if (aKeyEvent.mNativeKeyEvent || aKeyEvent.mModifiers & MODIFIER_OS) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
+ "mod=0x%X", this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode,
+ aKeyEvent.mModifiers));
+
+ NSEventType eventType;
+ if (aKeyEvent.mMessage == eKeyUp) {
+ eventType = NSKeyUp;
+ } else {
+ eventType = NSKeyDown;
+ }
+
+ static const uint32_t sModifierFlagMap[][2] = {
+ { MODIFIER_SHIFT, NSShiftKeyMask },
+ { MODIFIER_CONTROL, NSControlKeyMask },
+ { MODIFIER_ALT, NSAlternateKeyMask },
+ { MODIFIER_ALTGRAPH, NSAlternateKeyMask },
+ { MODIFIER_META, NSCommandKeyMask },
+ { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask },
+ { MODIFIER_NUMLOCK, NSNumericPadKeyMask }
+ };
+
+ NSUInteger modifierFlags = 0;
+ for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
+ if (aKeyEvent.mModifiers & sModifierFlagMap[i][0]) {
+ modifierFlags |= sModifierFlagMap[i][1];
+ }
+ }
+
+ NSInteger windowNumber = [[mView window] windowNumber];
+
+ NSString* characters;
+ if (aKeyEvent.mCharCode) {
+ characters = [NSString stringWithCharacters:
+ reinterpret_cast<const unichar*>(&(aKeyEvent.mCharCode)) length:1];
+ } else {
+ uint32_t cocoaCharCode =
+ nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.mKeyCode);
+ characters = [NSString stringWithCharacters:
+ reinterpret_cast<const unichar*>(&cocoaCharCode) length:1];
+ }
+
+ aKeyEvent.mNativeKeyEvent =
+ [NSEvent keyEventWithType:eventType
+ location:NSMakePoint(0,0)
+ modifierFlags:modifierFlags
+ timestamp:0
+ windowNumber:windowNumber
+ context:[NSGraphicsContext currentContext]
+ characters:characters
+ charactersIgnoringModifiers:characters
+ isARepeat:NO
+ keyCode:0]; // Native key code not currently needed
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+TextInputHandlerBase::SetSelection(NSRange& aRange)
+{
+ MOZ_ASSERT(!Destroyed());
+
+ RefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
+ WidgetSelectionEvent selectionEvent(true, eSetSelection, mWidget);
+ selectionEvent.mOffset = aRange.location;
+ selectionEvent.mLength = aRange.length;
+ selectionEvent.mReversed = false;
+ selectionEvent.mExpandToClusterBoundary = false;
+ DispatchEvent(selectionEvent);
+ NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
+ return !Destroyed();
+}
+
+/* static */ bool
+TextInputHandlerBase::IsPrintableChar(char16_t aChar)
+{
+ return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
+}
+
+
+/* static */ bool
+TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode)
+{
+ // this table is used to determine which keys are special and should not
+ // generate a charCode
+ switch (aNativeKeyCode) {
+ // modifiers - we don't get separate events for these yet
+ case kVK_Escape:
+ case kVK_Shift:
+ case kVK_RightShift:
+ case kVK_Command:
+ case kVK_RightCommand:
+ case kVK_CapsLock:
+ case kVK_Control:
+ case kVK_RightControl:
+ case kVK_Option:
+ case kVK_RightOption:
+ case kVK_ANSI_KeypadClear:
+ case kVK_Function:
+
+ // function keys
+ case kVK_F1:
+ case kVK_F2:
+ case kVK_F3:
+ case kVK_F4:
+ case kVK_F5:
+ case kVK_F6:
+ case kVK_F7:
+ case kVK_F8:
+ case kVK_F9:
+ case kVK_F10:
+ case kVK_F11:
+ case kVK_F12:
+ case kVK_PC_Pause:
+ case kVK_PC_ScrollLock:
+ case kVK_PC_PrintScreen:
+ case kVK_F16:
+ case kVK_F17:
+ case kVK_F18:
+ case kVK_F19:
+
+ case kVK_PC_Insert:
+ case kVK_PC_Delete:
+ case kVK_Tab:
+ case kVK_PC_Backspace:
+ case kVK_PC_ContextMenu:
+
+ case kVK_JIS_Eisu:
+ case kVK_JIS_Kana:
+
+ case kVK_Home:
+ case kVK_End:
+ case kVK_PageUp:
+ case kVK_PageDown:
+ case kVK_LeftArrow:
+ case kVK_RightArrow:
+ case kVK_UpArrow:
+ case kVK_DownArrow:
+ case kVK_Return:
+ case kVK_ANSI_KeypadEnter:
+ case kVK_Powerbook_KeypadEnter:
+ return true;
+ }
+ return false;
+}
+
+/* static */ bool
+TextInputHandlerBase::IsNormalCharInputtingEvent(
+ const WidgetKeyboardEvent& aKeyEvent)
+{
+ // this is not character inputting event, simply.
+ if (aKeyEvent.mNativeCharacters.IsEmpty() ||
+ aKeyEvent.IsMeta()) {
+ return false;
+ }
+ return !IsControlChar(aKeyEvent.mNativeCharacters[0]);
+}
+
+/* static */ bool
+TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode)
+{
+ switch (aNativeKeyCode) {
+ case kVK_CapsLock:
+ case kVK_RightCommand:
+ case kVK_Command:
+ case kVK_Shift:
+ case kVK_Option:
+ case kVK_Control:
+ case kVK_RightShift:
+ case kVK_RightOption:
+ case kVK_RightControl:
+ case kVK_Function:
+ return true;
+ }
+ return false;
+}
+
+/* static */ void
+TextInputHandlerBase::EnableSecureEventInput()
+{
+ sSecureEventInputCount++;
+ ::EnableSecureEventInput();
+}
+
+/* static */ void
+TextInputHandlerBase::DisableSecureEventInput()
+{
+ if (!sSecureEventInputCount) {
+ return;
+ }
+ sSecureEventInputCount--;
+ ::DisableSecureEventInput();
+}
+
+/* static */ bool
+TextInputHandlerBase::IsSecureEventInputEnabled()
+{
+ NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(),
+ "Some other process has enabled secure event input");
+ return !!sSecureEventInputCount;
+}
+
+/* static */ void
+TextInputHandlerBase::EnsureSecureEventInputDisabled()
+{
+ while (sSecureEventInputCount) {
+ TextInputHandlerBase::DisableSecureEventInput();
+ }
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandlerBase::KeyEventState implementation
+ *
+ ******************************************************************************/
+
+void
+TextInputHandlerBase::KeyEventState::InitKeyEvent(
+ TextInputHandlerBase* aHandler,
+ WidgetKeyboardEvent& aKeyEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aHandler);
+ MOZ_RELEASE_ASSERT(mKeyEvent);
+
+ NSEvent* nativeEvent = mKeyEvent;
+ if (!mInsertedString.IsEmpty()) {
+ nsAutoString unhandledString;
+ GetUnhandledString(unhandledString);
+ NSString* unhandledNSString =
+ nsCocoaUtils::ToNSString(unhandledString);
+ // If the key event's some characters were already handled by
+ // InsertString() calls, we need to create a dummy event which doesn't
+ // include the handled characters.
+ nativeEvent =
+ [NSEvent keyEventWithType:[mKeyEvent type]
+ location:[mKeyEvent locationInWindow]
+ modifierFlags:[mKeyEvent modifierFlags]
+ timestamp:[mKeyEvent timestamp]
+ windowNumber:[mKeyEvent windowNumber]
+ context:[mKeyEvent context]
+ characters:unhandledNSString
+ charactersIgnoringModifiers:[mKeyEvent charactersIgnoringModifiers]
+ isARepeat:[mKeyEvent isARepeat]
+ keyCode:[mKeyEvent keyCode]];
+ }
+
+ aHandler->InitKeyEvent(nativeEvent, aKeyEvent, mInsertString);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+TextInputHandlerBase::KeyEventState::GetUnhandledString(
+ nsAString& aUnhandledString) const
+{
+ aUnhandledString.Truncate();
+ if (NS_WARN_IF(!mKeyEvent)) {
+ return;
+ }
+ nsAutoString characters;
+ nsCocoaUtils::GetStringForNSString([mKeyEvent characters],
+ characters);
+ if (characters.IsEmpty()) {
+ return;
+ }
+ if (mInsertedString.IsEmpty()) {
+ aUnhandledString = characters;
+ return;
+ }
+
+ // The insertes string must match with the start of characters.
+ MOZ_ASSERT(StringBeginsWith(characters, mInsertedString));
+
+ aUnhandledString = nsDependentSubstring(characters, mInsertedString.Length());
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandlerBase::AutoInsertStringClearer implementation
+ *
+ ******************************************************************************/
+
+TextInputHandlerBase::AutoInsertStringClearer::~AutoInsertStringClearer()
+{
+ if (mState && mState->mInsertString) {
+ // If inserting string is a part of characters of the event,
+ // we should record it as inserted string.
+ nsAutoString characters;
+ nsCocoaUtils::GetStringForNSString([mState->mKeyEvent characters],
+ characters);
+ nsAutoString insertedString(mState->mInsertedString);
+ insertedString += *mState->mInsertString;
+ if (StringBeginsWith(characters, insertedString)) {
+ mState->mInsertedString = insertedString;
+ }
+ }
+ if (mState) {
+ mState->mInsertString = nullptr;
+ }
+}
diff --git a/widget/cocoa/VibrancyManager.h b/widget/cocoa/VibrancyManager.h
new file mode 100644
index 000000000..7a7ea3af1
--- /dev/null
+++ b/widget/cocoa/VibrancyManager.h
@@ -0,0 +1,120 @@
+/* -*- 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)
+ {
+ MOZ_ASSERT(SystemSupportsVibrancy(),
+ "Don't instantiate this if !SystemSupportsVibrancy()");
+ }
+
+ /**
+ * Update the placement of the NSVisualEffectViews inside the container
+ * NSView so that they cover aRegion, and create new NSVisualEffectViews
+ * or remove existing ones as needed.
+ * @param aType The vibrancy type to use in the region.
+ * @param aRegion The vibrant area, in device pixels.
+ */
+ void UpdateVibrantRegion(VibrancyType aType,
+ const LayoutDeviceIntRegion& aRegion);
+
+ bool HasVibrantRegions() { return !mVibrantRegions.IsEmpty(); }
+
+ /**
+ * Clear the vibrant areas that we know about.
+ * The clearing happens in the current NSGraphicsContext. If you call this
+ * from within an -[NSView drawRect:] implementation, the currrent
+ * NSGraphicsContext is already correctly set to the window drawing context.
+ */
+ void ClearVibrantAreas() const;
+
+ /**
+ * Return the fill color that should be drawn on top of the cleared window
+ * parts. Usually this would be drawn by -[NSVisualEffectView drawRect:].
+ * The returned color is opaque if the system-wide "Reduce transparency"
+ * preference is set.
+ */
+ NSColor* VibrancyFillColorForType(VibrancyType aType);
+
+ /**
+ * Return the font smoothing background color that should be used for text
+ * drawn on top of the vibrant window parts.
+ */
+ NSColor* VibrancyFontSmoothingBackgroundColorForType(VibrancyType aType);
+
+ /**
+ * Check whether the operating system supports vibrancy at all.
+ * You may only create a VibrancyManager instance if this returns true.
+ * @return Whether VibrancyManager can be used on this OS.
+ */
+ static bool SystemSupportsVibrancy();
+
+protected:
+ void ClearVibrantRegion(const LayoutDeviceIntRegion& aVibrantRegion) const;
+ NSView* CreateEffectView(VibrancyType aType);
+
+ 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 000000000..b6176de2b
--- /dev/null
+++ b/widget/cocoa/VibrancyManager.mm
@@ -0,0 +1,271 @@
+/* -*- 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"
+#import <objc/message.h>
+
+using namespace mozilla;
+
+void
+VibrancyManager::UpdateVibrantRegion(VibrancyType aType,
+ const LayoutDeviceIntRegion& aRegion)
+{
+ if (aRegion.IsEmpty()) {
+ mVibrantRegions.Remove(uint32_t(aType));
+ return;
+ }
+ auto& vr = *mVibrantRegions.LookupOrAdd(uint32_t(aType));
+ vr.UpdateRegion(aRegion, mCoordinateConverter, mContainerView, ^() {
+ return this->CreateEffectView(aType);
+ });
+}
+
+void
+VibrancyManager::ClearVibrantAreas() const
+{
+ for (auto iter = mVibrantRegions.ConstIter(); !iter.Done(); iter.Next()) {
+ ClearVibrantRegion(iter.UserData()->Region());
+ }
+}
+
+void
+VibrancyManager::ClearVibrantRegion(const LayoutDeviceIntRegion& aVibrantRegion) const
+{
+ [[NSColor clearColor] set];
+
+ for (auto iter = aVibrantRegion.RectIter(); !iter.Done(); iter.Next()) {
+ NSRectFill(mCoordinateConverter.DevPixelsToCocoaPoints(iter.Get()));
+ }
+}
+
+@interface NSView(CurrentFillColor)
+- (NSColor*)_currentFillColor;
+@end
+
+static NSColor*
+AdjustedColor(NSColor* aFillColor, VibrancyType aType)
+{
+ if (aType == VibrancyType::MENU && [aFillColor alphaComponent] == 1.0) {
+ // The opaque fill color that's used for the menu background when "Reduce
+ // vibrancy" is checked in the system accessibility prefs is too dark.
+ // This is probably because we're not using the right material for menus,
+ // see VibrancyManager::CreateEffectView.
+ return [NSColor colorWithDeviceWhite:0.96 alpha:1.0];
+ }
+ return aFillColor;
+}
+
+NSColor*
+VibrancyManager::VibrancyFillColorForType(VibrancyType aType)
+{
+ NSView* view = mVibrantRegions.LookupOrAdd(uint32_t(aType))->GetAnyView();
+
+ if (view && [view respondsToSelector:@selector(_currentFillColor)]) {
+ // -[NSVisualEffectView _currentFillColor] is the color that our view
+ // would draw during its drawRect implementation, if we hadn't
+ // disabled that.
+ return AdjustedColor([view _currentFillColor], aType);
+ }
+ return [NSColor whiteColor];
+}
+
+@interface NSView(FontSmoothingBackgroundColor)
+- (NSColor*)fontSmoothingBackgroundColor;
+@end
+
+NSColor*
+VibrancyManager::VibrancyFontSmoothingBackgroundColorForType(VibrancyType aType)
+{
+ NSView* view = mVibrantRegions.LookupOrAdd(uint32_t(aType))->GetAnyView();
+
+ if (view && [view respondsToSelector:@selector(fontSmoothingBackgroundColor)]) {
+ return [view fontSmoothingBackgroundColor];
+ }
+ return [NSColor clearColor];
+}
+
+static void
+DrawRectNothing(id self, SEL _cmd, NSRect aRect)
+{
+ // The super implementation would clear the background.
+ // That's fine for views that are placed below their content, but our
+ // setup is different: Our drawn content is drawn to mContainerView, which
+ // sits below this EffectView. So we must not clear the background here,
+ // because we'd erase that drawn content.
+ // Of course the regular content drawing still needs to clear the background
+ // behind vibrant areas. This is taken care of by having nsNativeThemeCocoa
+ // return true from NeedToClearBackgroundBehindWidget for vibrant widgets.
+}
+
+static NSView*
+HitTestNil(id self, SEL _cmd, NSPoint aPoint)
+{
+ // This view must be transparent to mouse events.
+ return nil;
+}
+
+static BOOL
+AllowsVibrancyYes(id self, SEL _cmd)
+{
+ // Means that the foreground is blended using a vibrant blend mode.
+ return YES;
+}
+
+static Class
+CreateEffectViewClass(BOOL aForegroundVibrancy)
+{
+ // Create a class called EffectView that inherits from NSVisualEffectView
+ // and overrides the methods -[NSVisualEffectView drawRect:] and
+ // -[NSView hitTest:].
+ Class NSVisualEffectViewClass = NSClassFromString(@"NSVisualEffectView");
+ const char* className = aForegroundVibrancy
+ ? "EffectViewWithForegroundVibrancy" : "EffectViewWithoutForegroundVibrancy";
+ Class EffectViewClass = objc_allocateClassPair(NSVisualEffectViewClass, className, 0);
+ class_addMethod(EffectViewClass, @selector(drawRect:), (IMP)DrawRectNothing,
+ "v@:{CGRect={CGPoint=dd}{CGSize=dd}}");
+ class_addMethod(EffectViewClass, @selector(hitTest:), (IMP)HitTestNil,
+ "@@:{CGPoint=dd}");
+ if (aForegroundVibrancy) {
+ // Also override the -[NSView allowsVibrancy] method to return YES.
+ class_addMethod(EffectViewClass, @selector(allowsVibrancy), (IMP)AllowsVibrancyYes, "I@:");
+ }
+ return EffectViewClass;
+}
+
+static id
+AppearanceForVibrancyType(VibrancyType aType)
+{
+ Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
+ switch (aType) {
+ case VibrancyType::LIGHT:
+ case VibrancyType::TOOLTIP:
+ case VibrancyType::MENU:
+ case VibrancyType::HIGHLIGHTED_MENUITEM:
+ case VibrancyType::SHEET:
+ case VibrancyType::SOURCE_LIST:
+ case VibrancyType::SOURCE_LIST_SELECTION:
+ case VibrancyType::ACTIVE_SOURCE_LIST_SELECTION:
+ return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
+ withObject:@"NSAppearanceNameVibrantLight"];
+ case VibrancyType::DARK:
+ return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
+ withObject:@"NSAppearanceNameVibrantDark"];
+ }
+}
+
+#if !defined(MAC_OS_X_VERSION_10_10) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10
+enum {
+ NSVisualEffectStateFollowsWindowActiveState,
+ NSVisualEffectStateActive,
+ NSVisualEffectStateInactive
+};
+
+enum {
+ NSVisualEffectMaterialTitlebar = 3
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_11) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11
+enum {
+ NSVisualEffectMaterialMenu = 5,
+ NSVisualEffectMaterialSidebar = 7
+};
+#endif
+
+static NSUInteger
+VisualEffectStateForVibrancyType(VibrancyType aType)
+{
+ switch (aType) {
+ case VibrancyType::TOOLTIP:
+ case VibrancyType::MENU:
+ case VibrancyType::HIGHLIGHTED_MENUITEM:
+ case VibrancyType::SHEET:
+ // Tooltip and menu windows are never "key" and sheets always looks
+ // active, so we need to tell the vibrancy effect to look active
+ // regardless of window state.
+ return NSVisualEffectStateActive;
+ default:
+ return NSVisualEffectStateFollowsWindowActiveState;
+ }
+}
+
+static BOOL
+HasVibrantForeground(VibrancyType aType)
+{
+ switch (aType) {
+ case VibrancyType::MENU:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+enum {
+ NSVisualEffectMaterialSelection = 4
+};
+#endif
+
+@interface NSView(NSVisualEffectViewMethods)
+- (void)setState:(NSUInteger)state;
+- (void)setMaterial:(NSUInteger)material;
+- (void)setEmphasized:(BOOL)emphasized;
+@end
+
+NSView*
+VibrancyManager::CreateEffectView(VibrancyType aType)
+{
+ static Class EffectViewClassWithoutForegroundVibrancy = CreateEffectViewClass(NO);
+ static Class EffectViewClassWithForegroundVibrancy = CreateEffectViewClass(YES);
+
+ Class EffectViewClass = HasVibrantForeground(aType)
+ ? EffectViewClassWithForegroundVibrancy : EffectViewClassWithoutForegroundVibrancy;
+ NSView* effectView = [[EffectViewClass alloc] initWithFrame:NSZeroRect];
+ [effectView performSelector:@selector(setAppearance:)
+ withObject:AppearanceForVibrancyType(aType)];
+ [effectView setState:VisualEffectStateForVibrancyType(aType)];
+
+ BOOL canUseElCapitanMaterials = nsCocoaFeatures::OnElCapitanOrLater();
+ if (aType == VibrancyType::MENU) {
+ // Before 10.11 there is no material that perfectly matches the menu
+ // look. Of all available material types, NSVisualEffectMaterialTitlebar
+ // is the one that comes closest.
+ [effectView setMaterial:canUseElCapitanMaterials ? NSVisualEffectMaterialMenu
+ : NSVisualEffectMaterialTitlebar];
+ } else if (aType == VibrancyType::SOURCE_LIST && canUseElCapitanMaterials) {
+ [effectView setMaterial:NSVisualEffectMaterialSidebar];
+ } else if (aType == VibrancyType::HIGHLIGHTED_MENUITEM ||
+ aType == VibrancyType::SOURCE_LIST_SELECTION ||
+ aType == VibrancyType::ACTIVE_SOURCE_LIST_SELECTION) {
+ [effectView setMaterial:NSVisualEffectMaterialSelection];
+ if ([effectView respondsToSelector:@selector(setEmphasized:)] &&
+ aType != VibrancyType::SOURCE_LIST_SELECTION) {
+ [effectView setEmphasized:YES];
+ }
+ }
+
+ return effectView;
+}
+
+static bool
+ComputeSystemSupportsVibrancy()
+{
+#ifdef __x86_64__
+ return NSClassFromString(@"NSAppearance") &&
+ NSClassFromString(@"NSVisualEffectView");
+#else
+ // objc_allocateClassPair doesn't work in 32 bit mode, so turn off vibrancy.
+ return false;
+#endif
+}
+
+/* static */ bool
+VibrancyManager::SystemSupportsVibrancy()
+{
+ static bool supportsVibrancy = ComputeSystemSupportsVibrancy();
+ return supportsVibrancy;
+}
diff --git a/widget/cocoa/ViewRegion.h b/widget/cocoa/ViewRegion.h
new file mode 100644
index 000000000..a8efccaff
--- /dev/null
+++ b/widget/cocoa/ViewRegion.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 ViewRegion_h
+#define ViewRegion_h
+
+#include "Units.h"
+#include "nsTArray.h"
+
+@class NSView;
+
+namespace mozilla {
+
+/**
+ * Manages a set of NSViews to cover a LayoutDeviceIntRegion.
+ */
+class ViewRegion {
+public:
+ ~ViewRegion();
+
+ mozilla::LayoutDeviceIntRegion Region() { return mRegion; }
+
+ /**
+ * Update the region.
+ * @param aRegion The new region.
+ * @param aCoordinateConverter The nsChildView to use for converting
+ * LayoutDeviceIntRect device pixel coordinates into Cocoa NSRect coordinates.
+ * @param aContainerView The view that's going to be the superview of the
+ * NSViews which will be created for this region.
+ * @param aViewCreationCallback A block that instantiates new NSViews.
+ * @return Whether or not the region changed.
+ */
+ bool UpdateRegion(const mozilla::LayoutDeviceIntRegion& aRegion,
+ const nsChildView& aCoordinateConverter,
+ NSView* aContainerView,
+ NSView* (^aViewCreationCallback)());
+
+ /**
+ * Return an NSView from the region, if there is any.
+ */
+ NSView* GetAnyView() { return mViews.Length() > 0 ? mViews[0] : nil; }
+
+private:
+ mozilla::LayoutDeviceIntRegion mRegion;
+ nsTArray<NSView*> mViews;
+};
+
+} // namespace mozilla
+
+#endif // ViewRegion_h
diff --git a/widget/cocoa/ViewRegion.mm b/widget/cocoa/ViewRegion.mm
new file mode 100644
index 000000000..b3605caa2
--- /dev/null
+++ b/widget/cocoa/ViewRegion.mm
@@ -0,0 +1,71 @@
+/* -*- 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>
+
+using namespace mozilla;
+
+ViewRegion::~ViewRegion()
+{
+ for (size_t i = 0; i < mViews.Length(); i++) {
+ [mViews[i] removeFromSuperview];
+ }
+}
+
+bool
+ViewRegion::UpdateRegion(const LayoutDeviceIntRegion& aRegion,
+ const nsChildView& aCoordinateConverter,
+ NSView* aContainerView,
+ NSView* (^aViewCreationCallback)())
+{
+ if (mRegion == aRegion) {
+ return false;
+ }
+
+ // We need to construct the required region using as many EffectViews
+ // as necessary. We try to update the geometry of existing views if
+ // possible, or create new ones or remove old ones if the number of
+ // rects in the region has changed.
+
+ nsTArray<NSView*> viewsToRecycle;
+ mViews.SwapElements(viewsToRecycle);
+ // The mViews array is now empty.
+
+ size_t i = 0;
+ for (auto iter = aRegion.RectIter();
+ !iter.Done() || i < viewsToRecycle.Length();
+ i++) {
+ if (!iter.Done()) {
+ NSView* view = nil;
+ NSRect rect = aCoordinateConverter.DevPixelsToCocoaPoints(iter.Get());
+ if (i < viewsToRecycle.Length()) {
+ view = viewsToRecycle[i];
+ } else {
+ view = aViewCreationCallback();
+ [aContainerView addSubview:view];
+
+ // Now that the view is in the view hierarchy, it'll be kept alive by
+ // its superview, so we can drop our reference.
+ [view release];
+ }
+ if (!NSEqualRects(rect, [view frame])) {
+ [view setFrame:rect];
+ }
+ [view setNeedsDisplay:YES];
+ mViews.AppendElement(view);
+ iter.Next();
+ } else {
+ // Our new region is made of fewer rects than the old region, so we can
+ // remove this view. We only have a weak reference to it, so removing it
+ // from the view hierarchy will release it.
+ [viewsToRecycle[i] removeFromSuperview];
+ }
+ }
+
+ mRegion = aRegion;
+ return true;
+}
diff --git a/widget/cocoa/WidgetTraceEvent.mm b/widget/cocoa/WidgetTraceEvent.mm
new file mode 100644
index 000000000..7023a17ba
--- /dev/null
+++ b/widget/cocoa/WidgetTraceEvent.mm
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <Cocoa/Cocoa.h>
+#include "CustomCocoaEvents.h"
+#include <Foundation/NSAutoreleasePool.h>
+#include <mozilla/CondVar.h>
+#include <mozilla/Mutex.h>
+#include "mozilla/WidgetTraceEvent.h"
+
+using mozilla::CondVar;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+
+namespace {
+
+Mutex* sMutex = NULL;
+CondVar* sCondVar = NULL;
+bool sTracerProcessed = false;
+
+} // namespace
+
+namespace mozilla {
+
+bool InitWidgetTracing()
+{
+ sMutex = new Mutex("Event tracer thread mutex");
+ sCondVar = new CondVar(*sMutex, "Event tracer thread condvar");
+ return sMutex && sCondVar;
+}
+
+void CleanUpWidgetTracing()
+{
+ delete sMutex;
+ delete sCondVar;
+ sMutex = NULL;
+ sCondVar = NULL;
+}
+
+// This function is called from the main (UI) thread.
+void SignalTracerThread()
+{
+ if (!sMutex || !sCondVar)
+ return;
+ MutexAutoLock lock(*sMutex);
+ if (!sTracerProcessed) {
+ sTracerProcessed = true;
+ sCondVar->Notify();
+ }
+}
+
+// This function is called from the background tracer thread.
+bool FireAndWaitForTracerEvent()
+{
+ MOZ_ASSERT(sMutex && sCondVar, "Tracing not initialized!");
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ MutexAutoLock lock(*sMutex);
+ if (sTracerProcessed) {
+ // Things are out of sync. This is likely because we're in
+ // the middle of shutting down. Just return false and hope the
+ // tracer thread is quitting anyway.
+ return false;
+ }
+
+ // Post an application-defined event to the main thread's event queue
+ // and wait for it to get processed.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeTrace
+ data1:0
+ data2:0]
+ atStart:NO];
+ while (!sTracerProcessed)
+ sCondVar->Wait();
+ sTracerProcessed = false;
+ [pool release];
+ return true;
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/crashtests/373122-1-inner.html b/widget/cocoa/crashtests/373122-1-inner.html
new file mode 100644
index 000000000..5c14166b7
--- /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 000000000..a57e5f424
--- /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 000000000..554b2dac7
--- /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 000000000..800eaa355
--- /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 000000000..fe6e4532b
--- /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 000000000..1bbc27ba0
--- /dev/null
+++ b/widget/cocoa/crashtests/435223-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div style="min-width: -moz-max-content;"><div style="-moz-appearance: button;"><div style="margin: 0 100%;"></div></div></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/444260-1.xul b/widget/cocoa/crashtests/444260-1.xul
new file mode 100644
index 000000000..f1a84023d
--- /dev/null
+++ b/widget/cocoa/crashtests/444260-1.xul
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<hbox><button width="7788025414616">S</button></hbox>
+</window>
diff --git a/widget/cocoa/crashtests/444864-1.html b/widget/cocoa/crashtests/444864-1.html
new file mode 100644
index 000000000..f8bac76e6
--- /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 000000000..449459180
--- /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 000000000..cc9b9700c
--- /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 000000000..cab7e7eb3
--- /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 000000000..d25d92315
--- /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 000000000..b65fe0139
--- /dev/null
+++ b/widget/cocoa/crashtests/crashtests.list
@@ -0,0 +1,11 @@
+skip-if(!cocoaWidget) load 373122-1.html # bug 1300017
+load 397209-1.html
+load 403296-1.xhtml
+load 419737-1.html
+load 435223-1.html
+load 444260-1.xul
+load 444864-1.html
+load 449111-1.html
+load 460349-1.xhtml
+load 460387-1.html
+load 464589-1.html
diff --git a/widget/cocoa/cursors/arrowN.png b/widget/cocoa/cursors/arrowN.png
new file mode 100644
index 000000000..5ca8ec5ac
--- /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 000000000..d00e87636
--- /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 000000000..9b2d19e0f
--- /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 000000000..5d011c1fd
--- /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 000000000..5284eaec5
--- /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 000000000..5e6738cff
--- /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 000000000..4e3e19e22
--- /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 000000000..6a92cf680
--- /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 000000000..5e5416b4e
--- /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 000000000..0ac53a973
--- /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 000000000..1360f8227
--- /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 000000000..ad146e486
--- /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 000000000..4c16bb8bd
--- /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 000000000..b48f03ae0
--- /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 000000000..f62c04657
--- /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 000000000..98d19e9ef
--- /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 000000000..0a077fa67
--- /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 000000000..31bca3c90
--- /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 000000000..0419be0af
--- /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 000000000..e48fd0cb3
--- /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 000000000..8f5faee5f
--- /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 000000000..3a80e7ce9
--- /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 000000000..0574a584c
--- /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 000000000..9a0a276c3
--- /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 000000000..6a1948f52
--- /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 000000000..7d637f4be
--- /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 000000000..5dd054dd4
--- /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 000000000..5ac63c25c
--- /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 000000000..ee7528c59
--- /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 000000000..41c47af11
--- /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 000000000..275bf1c69
--- /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 000000000..fdd3f8e71
--- /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 000000000..19d2d8912
--- /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 000000000..0ed46ce75
--- /dev/null
+++ b/widget/cocoa/cursors/zoomOut@2x.png
Binary files differ
diff --git a/widget/cocoa/moz.build b/widget/cocoa/moz.build
new file mode 100644
index 000000000..7c995d900
--- /dev/null
+++ b/widget/cocoa/moz.build
@@ -0,0 +1,141 @@
+# -*- 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/.
+
+XPIDL_SOURCES += [
+ 'nsPIWidgetCocoa.idl',
+]
+
+XPIDL_MODULE = 'widget_cocoa'
+
+EXPORTS += [
+ 'mozView.h',
+ 'nsBidiKeyboard.h',
+ 'nsChangeObserver.h',
+ 'nsCocoaDebugUtils.h',
+ 'nsCocoaFeatures.h',
+ 'nsCocoaUtils.h',
+]
+
+UNIFIED_SOURCES += [
+ 'ComplexTextInputPanel.mm',
+ 'GfxInfo.mm',
+ 'NativeKeyBindings.mm',
+ 'nsAppShell.mm',
+ 'nsBidiKeyboard.mm',
+ 'nsCocoaFeatures.mm',
+ 'nsCocoaUtils.mm',
+ 'nsCocoaWindow.mm',
+ 'nsColorPicker.mm',
+ 'nsCursorManager.mm',
+ 'nsDeviceContextSpecX.mm',
+ 'nsFilePicker.mm',
+ 'nsIdleServiceX.mm',
+ 'nsLookAndFeel.mm',
+ 'nsMacCursor.mm',
+ 'nsMacDockSupport.mm',
+ 'nsMacWebAppUtils.mm',
+ 'nsMenuBarX.mm',
+ 'nsMenuGroupOwnerX.mm',
+ 'nsMenuItemIconX.mm',
+ 'nsMenuItemX.mm',
+ 'nsMenuUtilsX.mm',
+ 'nsMenuX.mm',
+ 'nsPrintDialogX.mm',
+ 'nsPrintOptionsX.mm',
+ 'nsPrintSettingsX.mm',
+ 'nsScreenCocoa.mm',
+ 'nsScreenManagerCocoa.mm',
+ 'nsSound.mm',
+ 'nsStandaloneNativeMenu.mm',
+ 'nsSystemStatusBarCocoa.mm',
+ 'nsToolkit.mm',
+ 'nsWidgetFactory.mm',
+ 'nsWindowMap.mm',
+ 'OSXNotificationCenter.mm',
+ 'RectTextureImage.mm',
+ 'SwipeTracker.mm',
+ 'TextInputHandler.mm',
+ 'VibrancyManager.mm',
+ 'ViewRegion.mm',
+ 'WidgetTraceEvent.mm',
+]
+
+# These files cannot be built in unified mode because they cause symbol conflicts
+SOURCES += [
+ 'nsChildView.mm',
+ 'nsClipboard.mm',
+ 'nsCocoaDebugUtils.mm',
+ 'nsDragService.mm',
+ 'nsNativeThemeCocoa.mm',
+]
+
+if not CONFIG['RELEASE_OR_BETA'] or CONFIG['MOZ_DEBUG']:
+ SOURCES += [
+ 'nsSandboxViolationSink.mm',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+# XXX: We should fix these warnings.
+ALLOW_COMPILER_WARNINGS = True
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/layout/forms',
+ '/layout/generic',
+ '/layout/style',
+ '/layout/xul',
+ '/widget',
+]
+
+RESOURCE_FILES.cursors += [
+ 'cursors/arrowN.png',
+ 'cursors/arrowN@2x.png',
+ 'cursors/arrowS.png',
+ 'cursors/arrowS@2x.png',
+ 'cursors/cell.png',
+ 'cursors/cell@2x.png',
+ 'cursors/colResize.png',
+ 'cursors/colResize@2x.png',
+ 'cursors/help.png',
+ 'cursors/help@2x.png',
+ 'cursors/move.png',
+ 'cursors/move@2x.png',
+ 'cursors/rowResize.png',
+ 'cursors/rowResize@2x.png',
+ 'cursors/sizeNE.png',
+ 'cursors/sizeNE@2x.png',
+ 'cursors/sizeNESW.png',
+ 'cursors/sizeNESW@2x.png',
+ 'cursors/sizeNS.png',
+ 'cursors/sizeNS@2x.png',
+ 'cursors/sizeNW.png',
+ 'cursors/sizeNW@2x.png',
+ 'cursors/sizeNWSE.png',
+ 'cursors/sizeNWSE@2x.png',
+ 'cursors/sizeSE.png',
+ 'cursors/sizeSE@2x.png',
+ 'cursors/sizeSW.png',
+ 'cursors/sizeSW@2x.png',
+ 'cursors/vtIBeam.png',
+ 'cursors/vtIBeam@2x.png',
+ 'cursors/zoomIn.png',
+ 'cursors/zoomIn@2x.png',
+ 'cursors/zoomOut.png',
+ 'cursors/zoomOut@2x.png',
+]
+
+# These resources go in $(DIST)/bin/res/MainMenu.nib, but we can't use a magic
+# RESOURCE_FILES.MainMenu.nib attribute, since that would put the files in
+# $(DIST)/bin/res/MainMenu/nib. Instead, we call __setattr__ directly to create
+# an attribute with the correct name.
+RESOURCE_FILES.__setattr__('MainMenu.nib', [
+ 'resources/MainMenu.nib/classes.nib',
+ 'resources/MainMenu.nib/info.nib',
+ 'resources/MainMenu.nib/keyedobjects.nib',
+])
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
diff --git a/widget/cocoa/mozView.h b/widget/cocoa/mozView.h
new file mode 100644
index 000000000..9e94e3ab4
--- /dev/null
+++ b/widget/cocoa/mozView.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 mozView_h_
+#define mozView_h_
+
+#undef DARWIN
+#import <Cocoa/Cocoa.h>
+class nsIWidget;
+
+namespace mozilla {
+namespace widget{
+class TextInputHandler;
+} // namespace widget
+} // namespace mozilla
+
+// A protocol listing all the methods that an object which wants
+// to live in gecko's widget hierarchy must implement. |nsChildView|
+// makes assumptions that any NSView with which it comes in contact will
+// implement this protocol.
+@protocol mozView
+
+ // aHandler is Gecko's default text input handler: It implements the
+ // NSTextInput protocol to handle key events. Don't make aHandler a
+ // strong reference -- that causes a memory leak.
+- (void)installTextInputHandler:(mozilla::widget::TextInputHandler*)aHandler;
+- (void)uninstallTextInputHandler;
+
+ // access the nsIWidget associated with this view. DOES NOT ADDREF.
+- (nsIWidget*)widget;
+
+ // return a context menu for this view
+- (NSMenu*)contextMenu;
+
+ // Allows callers to do a delayed invalidate (e.g., if an invalidate
+ // happens during drawing)
+- (void)setNeedsPendingDisplay;
+- (void)setNeedsPendingDisplayInRect:(NSRect)invalidRect;
+
+ // 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 000000000..b7836b639
--- /dev/null
+++ b/widget/cocoa/nsAppShell.h
@@ -0,0 +1,71 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Runs the main native Cocoa run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#ifndef nsAppShell_h_
+#define nsAppShell_h_
+
+#include "nsBaseAppShell.h"
+#include "nsTArray.h"
+
+// GeckoNSApplication
+//
+// Subclass of NSApplication for filtering out certain events.
+@interface GeckoNSApplication : NSApplication
+{
+}
+@end
+
+@class AppShellDelegate;
+
+class nsAppShell : public nsBaseAppShell
+{
+public:
+ NS_IMETHOD ResumeNative(void);
+
+ nsAppShell();
+
+ nsresult Init();
+
+ NS_IMETHOD Run(void);
+ NS_IMETHOD Exit(void);
+ NS_IMETHOD OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait);
+ NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal *aThread,
+ bool aEventWasProcessed);
+
+ // public only to be visible to Objective-C code that must call it
+ void WillTerminate();
+
+protected:
+ virtual ~nsAppShell();
+
+ virtual void ScheduleNativeEventCallback();
+ virtual bool ProcessNextNativeEvent(bool aMayWait);
+
+ static void ProcessGeckoEvents(void* aInfo);
+
+protected:
+ CFMutableArrayRef mAutoreleasePools;
+
+ AppShellDelegate* mDelegate;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mCFRunLoopSource;
+
+ bool mRunningEventLoop;
+ bool mStarted;
+ bool mTerminated;
+ bool mSkippedNativeCallback;
+ bool mRunningCocoaEmbedded;
+
+ int32_t mNativeEventCallbackDepth;
+ // Can be set from different threads, so must be modified atomically
+ int32_t mNativeEventScheduledDepth;
+};
+
+#endif // nsAppShell_h_
diff --git a/widget/cocoa/nsAppShell.mm b/widget/cocoa/nsAppShell.mm
new file mode 100644
index 000000000..33ce8e742
--- /dev/null
+++ b/widget/cocoa/nsAppShell.mm
@@ -0,0 +1,907 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Runs the main native Cocoa run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#include "CustomCocoaEvents.h"
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "nsIWindowMediator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsChildView.h"
+#include "nsToolkit.h"
+#include "TextInputHandler.h"
+#include "mozilla/HangMonitor.h"
+#include "GeckoProfiler.h"
+#include "pratom.h"
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+#include "nsSandboxViolationSink.h"
+#endif
+
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+
+using namespace mozilla::widget;
+
+// A wake lock listener that disables screen saver when requested by
+// Gecko. For example when we're playing video in a foreground tab we
+// don't want the screen saver to turn on.
+
+class MacWakeLockListener final : public nsIDOMMozWakeLockListener {
+public:
+ NS_DECL_ISUPPORTS;
+
+private:
+ ~MacWakeLockListener() {}
+
+ IOPMAssertionID mAssertionID = kIOPMNullAssertionID;
+
+ NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) override {
+ if (!aTopic.EqualsASCII("screen")) {
+ return NS_OK;
+ }
+ // Note the wake lock code ensures that we're not sent duplicate
+ // "locked-foreground" notifications when multiple wake locks are held.
+ if (aState.EqualsASCII("locked-foreground")) {
+ // Prevent screen saver.
+ CFStringRef cf_topic =
+ ::CFStringCreateWithCharacters(kCFAllocatorDefault,
+ reinterpret_cast<const UniChar*>
+ (aTopic.Data()),
+ aTopic.Length());
+ IOReturn success =
+ ::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn,
+ cf_topic,
+ &mAssertionID);
+ CFRelease(cf_topic);
+ if (success != kIOReturnSuccess) {
+ NS_WARNING("failed to disable screensaver");
+ }
+ } else {
+ // Re-enable screen saver.
+ NS_WARNING("Releasing screensaver");
+ if (mAssertionID != kIOPMNullAssertionID) {
+ IOReturn result = ::IOPMAssertionRelease(mAssertionID);
+ if (result != kIOReturnSuccess) {
+ NS_WARNING("failed to release screensaver");
+ }
+ }
+ }
+ return NS_OK;
+ }
+}; // MacWakeLockListener
+
+// defined in nsCocoaWindow.mm
+extern int32_t gXULModalLevel;
+
+static bool gAppShellMethodsSwizzled = false;
+
+@implementation GeckoNSApplication
+
+- (void)sendEvent:(NSEvent *)anEvent
+{
+ mozilla::HangMonitor::NotifyActivity();
+ if ([anEvent type] == NSApplicationDefined &&
+ [anEvent subtype] == kEventSubtypeTrace) {
+ mozilla::SignalTracerThread();
+ return;
+ }
+ [super sendEvent:anEvent];
+}
+
+#if defined(MAC_OS_X_VERSION_10_12) && \
+ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 && \
+ __LP64__
+// 10.12 changed `mask` to NSEventMask (unsigned long long) for x86_64 builds.
+- (NSEvent*)nextEventMatchingMask:(NSEventMask)mask
+#else
+- (NSEvent*)nextEventMatchingMask:(NSUInteger)mask
+#endif
+ untilDate:(NSDate*)expiration
+ inMode:(NSString*)mode
+ dequeue:(BOOL)flag
+{
+ if (expiration) {
+ mozilla::HangMonitor::Suspend();
+ }
+ NSEvent* nextEvent = [super nextEventMatchingMask:mask
+ untilDate:expiration inMode:mode dequeue:flag];
+ if (expiration) {
+ mozilla::HangMonitor::NotifyActivity();
+ }
+ return nextEvent;
+}
+
+@end
+
+// AppShellDelegate
+//
+// Cocoa bridge class. An object of this class is registered to receive
+// notifications.
+//
+@interface AppShellDelegate : NSObject
+{
+ @private
+ nsAppShell* mAppShell;
+}
+
+- (id)initWithAppShell:(nsAppShell*)aAppShell;
+- (void)applicationWillTerminate:(NSNotification*)aNotification;
+- (void)beginMenuTracking:(NSNotification*)aNotification;
+@end
+
+// nsAppShell implementation
+
+NS_IMETHODIMP
+nsAppShell::ResumeNative(void)
+{
+ nsresult retval = nsBaseAppShell::ResumeNative();
+ if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
+ mSkippedNativeCallback)
+ {
+ mSkippedNativeCallback = false;
+ ScheduleNativeEventCallback();
+ }
+ return retval;
+}
+
+nsAppShell::nsAppShell()
+: mAutoreleasePools(nullptr)
+, mDelegate(nullptr)
+, mCFRunLoop(NULL)
+, mCFRunLoopSource(NULL)
+, mRunningEventLoop(false)
+, mStarted(false)
+, mTerminated(false)
+, mSkippedNativeCallback(false)
+, mNativeEventCallbackDepth(0)
+, mNativeEventScheduledDepth(0)
+{
+ // A Cocoa event loop is running here if (and only if) we've been embedded
+ // by a Cocoa app.
+ mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
+}
+
+nsAppShell::~nsAppShell()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mCFRunLoop) {
+ if (mCFRunLoopSource) {
+ ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
+ kCFRunLoopCommonModes);
+ ::CFRelease(mCFRunLoopSource);
+ }
+ ::CFRelease(mCFRunLoop);
+ }
+
+ if (mAutoreleasePools) {
+ NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
+ "nsAppShell destroyed without popping all autorelease pools");
+ ::CFRelease(mAutoreleasePools);
+ }
+
+ [mDelegate release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
+mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
+
+static void
+AddScreenWakeLockListener()
+{
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
+ POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sWakeLockListener = new MacWakeLockListener();
+ sPowerManagerService->AddWakeLockListener(sWakeLockListener);
+ } else {
+ NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+static void
+RemoveScreenWakeLockListener()
+{
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
+ POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
+ sPowerManagerService = nullptr;
+ sWakeLockListener = nullptr;
+ }
+}
+
+// An undocumented CoreGraphics framework method, present in the same form
+// since at least OS X 10.5.
+extern "C" CGError CGSSetDebugOptions(int options);
+
+// Init
+//
+// Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
+// interrupt the main native run loop.
+//
+// public
+nsresult
+nsAppShell::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // No event loop is running yet (unless an embedding app that uses
+ // NSApplicationMain() is running).
+ NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
+
+ // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
+ // by |this|. CFArray is used instead of NSArray because NSArray wants to
+ // retain each object you add to it, and you can't retain an
+ // NSAutoreleasePool.
+ mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
+ NS_ENSURE_STATE(mAutoreleasePools);
+
+ // Get the path of the nib file, which lives in the GRE location
+ nsCOMPtr<nsIFile> nibFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
+ nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
+
+ nsAutoCString nibPath;
+ rv = nibFile->GetNativePath(nibPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This call initializes NSApplication unless:
+ // 1) we're using xre -- NSApp's already been initialized by
+ // MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
+ // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
+ // already been initialized and its main run loop is already running.
+ [NSBundle loadNibFile:
+ [NSString stringWithUTF8String:(const char*)nibPath.get()]
+ externalNameTable:
+ [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
+ forKey:@"NSOwner"]
+ withZone:NSDefaultMallocZone()];
+
+ mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
+ NS_ENSURE_STATE(mDelegate);
+
+ // Add a CFRunLoopSource to the main native run loop. The source is
+ // responsible for interrupting the run loop when Gecko events are ready.
+
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ NS_ENSURE_STATE(mCFRunLoop);
+ ::CFRetain(mCFRunLoop);
+
+ CFRunLoopSourceContext context;
+ bzero(&context, sizeof(context));
+ // context.version = 0;
+ context.info = this;
+ context.perform = ProcessGeckoEvents;
+
+ mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
+ NS_ENSURE_STATE(mCFRunLoopSource);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
+
+ rv = nsBaseAppShell::Init();
+
+ if (!gAppShellMethodsSwizzled) {
+ // We should only replace the original terminate: method if we're not
+ // running in a Cocoa embedder. See bug 604901.
+ if (!mRunningCocoaEmbedded) {
+ nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
+ @selector(nsAppShell_NSApplication_terminate:));
+ }
+ gAppShellMethodsSwizzled = true;
+ }
+
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ // Explicitly turn off CGEvent logging. This works around bug 1092855.
+ // If there are already CGEvents in the log, turning off logging also
+ // causes those events to be written to disk. But at this point no
+ // CGEvents have yet been processed. CGEvents are events (usually
+ // input events) pulled from the WindowServer. An option of 0x80000008
+ // turns on CGEvent logging.
+ CGSSetDebugOptions(0x80000007);
+ }
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ if (Preferences::GetBool("security.sandbox.mac.track.violations", false)) {
+ nsSandboxViolationSink::Start();
+ }
+#endif
+
+ [localPool release];
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// ProcessGeckoEvents
+//
+// The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
+// signalled from ScheduleNativeEventCallback.
+//
+// Arrange for Gecko events to be processed on demand (in response to a call
+// to ScheduleNativeEventCallback(), if processing of Gecko events via "native
+// methods" hasn't been suspended). This happens in NativeEventCallback().
+//
+// protected static
+void
+nsAppShell::ProcessGeckoEvents(void* aInfo)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ PROFILER_LABEL("Events", "ProcessGeckoEvents",
+ js::ProfileEntry::Category::EVENTS);
+
+ nsAppShell* self = static_cast<nsAppShell*> (aInfo);
+
+ if (self->mRunningEventLoop) {
+ self->mRunningEventLoop = false;
+
+ // The run loop may be sleeping -- [NSRunLoop runMode:...]
+ // won't return until it's given a reason to wake up. Awaken it by
+ // posting a bogus event. There's no need to make the event
+ // presentable.
+ //
+ // But _don't_ set windowNumber to '-1' -- that can lead to nasty
+ // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
+ // these fake events, because the -1 has gotten changed into the number
+ // of an actual NSWindow object, and that NSWindow object has just been
+ // destroyed). Setting windowNumber to '0' seems to work fine -- this
+ // seems to prevent the OS from ever trying to associate our bogus event
+ // with a particular NSWindow object.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeNone
+ data1:0
+ data2:0]
+ atStart:NO];
+ }
+
+ if (self->mSuspendNativeCount <= 0) {
+ ++self->mNativeEventCallbackDepth;
+ self->NativeEventCallback();
+ --self->mNativeEventCallbackDepth;
+ } else {
+ self->mSkippedNativeCallback = true;
+ }
+
+ // Still needed to avoid crashes on quit in most Mochitests.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeNone
+ data1:0
+ data2:0]
+ atStart:NO];
+
+ // Normally every call to ScheduleNativeEventCallback() results in
+ // exactly one call to ProcessGeckoEvents(). So each Release() here
+ // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
+ // But if Exit() is called just after ScheduleNativeEventCallback(), the
+ // corresponding call to ProcessGeckoEvents() will never happen. We check
+ // for this possibility in two different places -- here and in Exit()
+ // itself. If we find here that Exit() has been called (that mTerminated
+ // is true), it's because we've been called recursively, that Exit() was
+ // called from self->NativeEventCallback() above, and that we're unwinding
+ // the recursion. In this case we'll never be called again, and we balance
+ // here any extra calls to ScheduleNativeEventCallback().
+ //
+ // When ProcessGeckoEvents() is called recursively, it's because of a
+ // call to ScheduleNativeEventCallback() from NativeEventCallback(). We
+ // balance the "extra" AddRefs here (rather than always in Exit()) in order
+ // to ensure that 'self' stays alive until the end of this method. We also
+ // make sure not to finish the balancing until all the recursion has been
+ // unwound.
+ if (self->mTerminated) {
+ int32_t releaseCount = 0;
+ if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
+ releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
+ self->mNativeEventCallbackDepth);
+ }
+ while (releaseCount-- > self->mNativeEventCallbackDepth)
+ self->Release();
+ } else {
+ // As best we can tell, every call to ProcessGeckoEvents() is triggered
+ // by a call to ScheduleNativeEventCallback(). But we've seen a few
+ // (non-reproducible) cases of double-frees that *might* have been caused
+ // by spontaneous calls (from the OS) to ProcessGeckoEvents(). So we
+ // deal with that possibility here.
+ if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
+ PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
+ NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
+ } else {
+ self->Release();
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// WillTerminate
+//
+// Called by the AppShellDelegate when an NSApplicationWillTerminate
+// notification is posted. After this method is called, native events should
+// no longer be processed. The NSApplicationWillTerminate notification is
+// only posted when [NSApp terminate:] is called, which doesn't happen on a
+// "normal" application quit.
+//
+// public
+void
+nsAppShell::WillTerminate()
+{
+ if (mTerminated)
+ return;
+
+ // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
+ // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+
+ mTerminated = true;
+}
+
+// ScheduleNativeEventCallback
+//
+// Called (possibly on a non-main thread) when Gecko has an event that
+// needs to be processed. The Gecko event needs to be processed on the
+// main thread, so the native run loop must be interrupted.
+//
+// In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
+// ensure that ScheduleNativeEventCallback() is called no more than once
+// per call to NativeEventCallback(). ProcessGeckoEvents() can skip its
+// call to NativeEventCallback() if processing of Gecko events by native
+// means is suspended (using nsIAppShell::SuspendNative()), which will
+// suspend calls from nsBaseAppShell::OnDispatchedEvent() to
+// ScheduleNativeEventCallback(). But when Gecko event processing by
+// native means is resumed (in ResumeNative()), an extra call is made to
+// ScheduleNativeEventCallback() (from ResumeNative()). This triggers
+// another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
+// and nsBaseAppShell::OnDispatchedEvent() resumes calling
+// ScheduleNativeEventCallback().
+//
+// protected virtual
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTerminated)
+ return;
+
+ // Each AddRef() here is normally balanced by exactly one Release() in
+ // ProcessGeckoEvents(). But there are exceptions, for which see
+ // ProcessGeckoEvents() and Exit().
+ NS_ADDREF_THIS();
+ PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
+
+ // This will invoke ProcessGeckoEvents on the main thread.
+ ::CFRunLoopSourceSignal(mCFRunLoopSource);
+ ::CFRunLoopWakeUp(mCFRunLoop);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Undocumented Cocoa Event Manager function, present in the same form since
+// at least OS X 10.6.
+extern "C" EventAttributes GetEventAttributes(EventRef inEvent);
+
+// ProcessNextNativeEvent
+//
+// If aMayWait is false, process a single native event. If it is true, run
+// the native run loop until stopped by ProcessGeckoEvents.
+//
+// Returns true if more events are waiting in the native event queue.
+//
+// protected virtual
+bool
+nsAppShell::ProcessNextNativeEvent(bool aMayWait)
+{
+ bool moreEvents = false;
+
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ bool eventProcessed = false;
+ NSString* currentMode = nil;
+
+ if (mTerminated)
+ return false;
+
+ bool wasRunningEventLoop = mRunningEventLoop;
+ mRunningEventLoop = aMayWait;
+ NSDate* waitUntil = nil;
+ if (aMayWait)
+ waitUntil = [NSDate distantFuture];
+
+ NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
+
+ EventQueueRef currentEventQueue = GetCurrentEventQueue();
+ EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
+
+ if (aMayWait) {
+ mozilla::HangMonitor::Suspend();
+ }
+
+ // Only call -[NSApp sendEvent:] (and indirectly send user-input events to
+ // Gecko) if aMayWait is true. Tbis ensures most calls to -[NSApp
+ // sendEvent:] happen under nsAppShell::Run(), at the lowest level of
+ // recursion -- thereby making it less likely Gecko will process user-input
+ // events in the wrong order or skip some of them. It also avoids eating
+ // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls
+ // us) -- thereby avoiding the starvation of nsIRunnable events in
+ // nsThread::ProcessNextEvent(). For more information see bug 996848.
+ do {
+ // No autorelease pool is provided here, because OnProcessNextEvent
+ // and AfterProcessNextEvent are responsible for maintaining it.
+ NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
+ "No autorelease pool for native event");
+
+ if (aMayWait) {
+ currentMode = [currentRunLoop currentMode];
+ if (!currentMode)
+ currentMode = NSDefaultRunLoopMode;
+ NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:waitUntil
+ inMode:currentMode
+ dequeue:YES];
+ if (nextEvent) {
+ mozilla::HangMonitor::NotifyActivity();
+ [NSApp sendEvent:nextEvent];
+ eventProcessed = true;
+ }
+ } else {
+ // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
+ // loop, though it does queue up any newly available events from the
+ // window server.
+ EventRef currentEvent = AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
+ kEventQueueOptionsNone);
+ if (!currentEvent) {
+ continue;
+ }
+ EventAttributes attrs = GetEventAttributes(currentEvent);
+ UInt32 eventKind = GetEventKind(currentEvent);
+ UInt32 eventClass = GetEventClass(currentEvent);
+ bool osCocoaEvent =
+ ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) ||
+ ((eventClass == 'cgs ') && (eventKind != NSApplicationDefined)));
+ // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
+ // (i.e. a user input event), we shouldn't process it here while
+ // aMayWait is false. Likewise if currentEvent will eventually be
+ // turned into an OS-defined Cocoa event, or otherwise needs AppKit
+ // processing. Doing otherwise risks doing too much work here, and
+ // preventing the event from being properly processed by the AppKit
+ // framework.
+ if ((attrs != kEventAttributeNone) || osCocoaEvent) {
+ // Since we can't process the next event here (while aMayWait is false),
+ // we want moreEvents to be false on return.
+ eventProcessed = false;
+ // This call to ReleaseEvent() matches a call to RetainEvent() in
+ // AcquireFirstMatchingEventInQueue() above.
+ ReleaseEvent(currentEvent);
+ break;
+ }
+ // This call to RetainEvent() matches a call to ReleaseEvent() in
+ // RemoveEventFromQueue() below.
+ RetainEvent(currentEvent);
+ RemoveEventFromQueue(currentEventQueue, currentEvent);
+ SendEventToEventTarget(currentEvent, eventDispatcherTarget);
+ // This call to ReleaseEvent() matches a call to RetainEvent() in
+ // AcquireFirstMatchingEventInQueue() above.
+ ReleaseEvent(currentEvent);
+ eventProcessed = true;
+ }
+ } while (mRunningEventLoop);
+
+ if (eventProcessed) {
+ moreEvents =
+ (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
+ kEventQueueOptionsNone) != NULL);
+ }
+
+ mRunningEventLoop = wasRunningEventLoop;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+
+ if (!moreEvents) {
+ nsChildView::UpdateCurrentInputEventCount();
+ }
+
+ return moreEvents;
+}
+
+// Run
+//
+// Overrides the base class's Run() method to call [NSApp run] (which spins
+// the native run loop until the application quits). Since (unlike the base
+// class's Run() method) we don't process any Gecko events here, they need
+// to be processed elsewhere (in NativeEventCallback(), called from
+// ProcessGeckoEvents()).
+//
+// Camino called [NSApp run] on its own (via NSApplicationMain()), and so
+// didn't call nsAppShell::Run().
+//
+// public
+NS_IMETHODIMP
+nsAppShell::Run(void)
+{
+ NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
+ if (mStarted || mTerminated)
+ return NS_OK;
+
+ mStarted = true;
+
+ AddScreenWakeLockListener();
+
+ NS_OBJC_TRY_ABORT([NSApp run]);
+
+ RemoveScreenWakeLockListener();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // This method is currently called more than once -- from (according to
+ // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
+ // XPCOM shutdown notification that nsBaseAppShell has registered to
+ // receive. So we need to ensure that multiple calls won't break anything.
+ // But we should also complain about it (since it isn't quite kosher).
+ if (mTerminated) {
+ NS_WARNING("nsAppShell::Exit() called redundantly");
+ return NS_OK;
+ }
+
+ mTerminated = true;
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ nsSandboxViolationSink::Stop();
+#endif
+
+ // Quoting from Apple's doc on the [NSApplication stop:] method (from their
+ // doc on the NSApplication class): "If this method is invoked during a
+ // modal event loop, it will break that loop but not the main event loop."
+ // nsAppShell::Exit() shouldn't be called from a modal event loop. So if
+ // it is we complain about it (to users of debug builds) and call [NSApp
+ // stop:] one extra time. (I'm not sure if modal event loops can be nested
+ // -- Apple's docs don't say one way or the other. But the return value
+ // of [NSApp _isRunningModal] doesn't change immediately after a call to
+ // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
+ // will do the job.)
+ BOOL cocoaModal = [NSApp _isRunningModal];
+ NS_ASSERTION(!cocoaModal,
+ "Don't call nsAppShell::Exit() from a modal event loop!");
+ if (cocoaModal)
+ [NSApp stop:nullptr];
+ [NSApp stop:nullptr];
+
+ // A call to Exit() just after a call to ScheduleNativeEventCallback()
+ // prevents the (normally) matching call to ProcessGeckoEvents() from
+ // happening. If we've been called from ProcessGeckoEvents() (as usually
+ // happens), we take care of it there. But if we have an unbalanced call
+ // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
+ // stack, we need to take care of the problem here.
+ if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
+ int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
+ while (releaseCount-- > 0)
+ NS_RELEASE_THIS();
+ }
+
+ return nsBaseAppShell::Exit();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// OnProcessNextEvent
+//
+// This nsIThreadObserver method is called prior to processing an event.
+// Set up an autorelease pool that will service any autoreleased Cocoa
+// objects during this event. This includes native events processed by
+// ProcessNextNativeEvent. The autorelease pool will be popped by
+// AfterProcessNextEvent, it is important for these two methods to be
+// tightly coupled.
+//
+// public
+NS_IMETHODIMP
+nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ASSERTION(mAutoreleasePools,
+ "No stack on which to store autorelease pool");
+
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ ::CFArrayAppendValue(mAutoreleasePools, pool);
+
+ return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// AfterProcessNextEvent
+//
+// This nsIThreadObserver method is called after event processing is complete.
+// The Cocoa implementation cleans up the autorelease pool create by the
+// previous OnProcessNextEvent call.
+//
+// public
+NS_IMETHODIMP
+nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
+ bool aEventWasProcessed)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
+
+ NS_ASSERTION(mAutoreleasePools && count,
+ "Processed an event, but there's no autorelease pool?");
+
+ const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>
+ (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
+ ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
+ [pool release];
+
+ return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+
+// AppShellDelegate implementation
+
+
+@implementation AppShellDelegate
+// initWithAppShell:
+//
+// Constructs the AppShellDelegate object
+- (id)initWithAppShell:(nsAppShell*)aAppShell
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [self init])) {
+ mAppShell = aAppShell;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationWillTerminate:)
+ name:NSApplicationWillTerminateNotification
+ object:NSApp];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationDidBecomeActive:)
+ name:NSApplicationDidBecomeActiveNotification
+ object:NSApp];
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(beginMenuTracking:)
+ name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
+ object:nil];
+ }
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// applicationWillTerminate:
+//
+// Notify the nsAppShell that native event processing should be discontinued.
+- (void)applicationWillTerminate:(NSNotification*)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mAppShell->WillTerminate();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// applicationDidBecomeActive
+//
+// Make sure TextInputHandler::sLastModifierState is updated when we become
+// active (since we won't have received [ChildView flagsChanged:] messages
+// while inactive).
+- (void)applicationDidBecomeActive:(NSNotification*)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
+ // to worry about getting an NSInternalInconsistencyException here.
+ NSEvent* currentEvent = [NSApp currentEvent];
+ if (currentEvent) {
+ TextInputHandler::sLastModifierState =
+ [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// beginMenuTracking
+//
+// Roll up our context menu (if any) when some other app (or the OS) opens
+// any sort of menu. But make sure we don't do this for notifications we
+// send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
+- (void)beginMenuTracking:(NSNotification*)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSString *sender = [aNotification object];
+ if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget)
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// We hook terminate: in order to make OS-initiated termination work nicely
+// with Gecko's shutdown sequence. (Two ways to trigger OS-initiated
+// termination: 1) Quit from the Dock menu; 2) Log out from (or shut down)
+// your computer while the browser is active.)
+@interface NSApplication (MethodSwizzling)
+- (void)nsAppShell_NSApplication_terminate:(id)sender;
+@end
+
+@implementation NSApplication (MethodSwizzling)
+
+// Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
+// has returned NSTerminateNow. This method "subclasses" and replaces the
+// OS's original implementation. The only thing the orginal method does which
+// we need is that it posts NSApplicationWillTerminateNotification. Everything
+// else is unneeded (because it's handled elsewhere), or actively interferes
+// with Gecko's shutdown sequence. For example the original terminate: method
+// causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
+// above), which means that nothing runs after the call to nsAppStartup::Run()
+// in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
+// and NS_ShutdownXPCOM() never get called.
+- (void)nsAppShell_NSApplication_terminate:(id)sender
+{
+ [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification
+ object:NSApp];
+}
+
+@end
diff --git a/widget/cocoa/nsBidiKeyboard.h b/widget/cocoa/nsBidiKeyboard.h
new file mode 100644
index 000000000..e7e7ac872
--- /dev/null
+++ b/widget/cocoa/nsBidiKeyboard.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBidiKeyboard_h_
+#define nsBidiKeyboard_h_
+
+#include "nsIBidiKeyboard.h"
+
+class nsBidiKeyboard : public nsIBidiKeyboard
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBIDIKEYBOARD
+
+ nsBidiKeyboard();
+
+protected:
+ virtual ~nsBidiKeyboard();
+};
+
+#endif // nsBidiKeyboard_h_
diff --git a/widget/cocoa/nsBidiKeyboard.mm b/widget/cocoa/nsBidiKeyboard.mm
new file mode 100644
index 000000000..e0fc86aeb
--- /dev/null
+++ b/widget/cocoa/nsBidiKeyboard.mm
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBidiKeyboard.h"
+#include "nsCocoaUtils.h"
+#include "TextInputHandler.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard)
+
+nsBidiKeyboard::nsBidiKeyboard() : nsIBidiKeyboard()
+{
+ Reset();
+}
+
+nsBidiKeyboard::~nsBidiKeyboard()
+{
+}
+
+NS_IMETHODIMP nsBidiKeyboard::Reset()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(bool *aIsRTL)
+{
+ *aIsRTL = TISInputSourceWrapper::CurrentInputSource().IsForRTLLanguage();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult)
+{
+ // not implemented yet
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/widget/cocoa/nsChangeObserver.h b/widget/cocoa/nsChangeObserver.h
new file mode 100644
index 000000000..1b9a00173
--- /dev/null
+++ b/widget/cocoa/nsChangeObserver.h
@@ -0,0 +1,44 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsChangeObserver_h_
+#define nsChangeObserver_h_
+
+class nsIContent;
+class nsIDocument;
+class nsIAtom;
+
+#define NS_DECL_CHANGEOBSERVER \
+void ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute) override; \
+void ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, int32_t aIndexInContainer) override; \
+void ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer, nsIContent *aChild) override;
+
+// Something that wants to be alerted to changes in attributes or changes in
+// its corresponding content object.
+//
+// This interface is used by our menu code so we only have to have one
+// nsIDocumentObserver.
+//
+// Any class that implements this interface must take care to unregister itself
+// on deletion.
+class nsChangeObserver
+{
+public:
+ // XXX use dom::Element
+ virtual void ObserveAttributeChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ nsIAtom* aAttribute)=0;
+
+ virtual void ObserveContentRemoved(nsIDocument* aDocument,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)=0;
+
+ virtual void ObserveContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild)=0;
+};
+
+#endif // nsChangeObserver_h_
diff --git a/widget/cocoa/nsChildView.h b/widget/cocoa/nsChildView.h
new file mode 100644
index 000000000..f6fb44633
--- /dev/null
+++ b/widget/cocoa/nsChildView.h
@@ -0,0 +1,664 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsChildView_h_
+#define nsChildView_h_
+
+// formal protocols
+#include "mozView.h"
+#ifdef ACCESSIBILITY
+#include "mozilla/a11y/Accessible.h"
+#include "mozAccessibleProtocol.h"
+#endif
+
+#include "nsISupports.h"
+#include "nsBaseWidget.h"
+#include "nsWeakPtr.h"
+#include "TextInputHandler.h"
+#include "nsCocoaUtils.h"
+#include "gfxQuartzSurface.h"
+#include "GLContextTypes.h"
+#include "mozilla/Mutex.h"
+#include "nsRegion.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsString.h"
+#include "nsIDragService.h"
+#include "ViewRegion.h"
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#import <AppKit/NSOpenGL.h>
+
+class nsChildView;
+class nsCocoaWindow;
+
+namespace {
+class GLPresenter;
+} // namespace
+
+namespace mozilla {
+class InputData;
+class PanGestureInput;
+class SwipeTracker;
+struct SwipeEventQueue;
+class VibrancyManager;
+namespace layers {
+class GLManager;
+class IAPZCTreeManager;
+} // namespace layers
+namespace widget {
+class RectTextureImage;
+class WidgetRenderingContext;
+} // namespace widget
+} // namespace mozilla
+
+@interface NSEvent (Undocumented)
+
+// Return Cocoa event's corresponding Carbon event. Not initialized (on
+// synthetic events) until the OS actually "sends" the event. This method
+// has been present in the same form since at least OS X 10.2.8.
+- (EventRef)_eventRef;
+
+@end
+
+@interface NSView (Undocumented)
+
+// Draws the title string of a window.
+// Present on NSThemeFrame since at least 10.6.
+// _drawTitleBar is somewhat complex, and has changed over the years
+// since OS X 10.6. But in that time it's never done anything that
+// would break when called outside of -[NSView drawRect:] (which we
+// sometimes do), or whose output can't be redirected to a
+// CGContextRef object (which we also sometimes do). This is likely
+// to remain true for the indefinite future. However we should
+// check _drawTitleBar in each new major version of OS X. For more
+// information see bug 877767.
+- (void)_drawTitleBar:(NSRect)aRect;
+
+// Returns an NSRect that is the bounding box for all an NSView's dirty
+// rectangles (ones that need to be redrawn). The full list of dirty
+// rectangles can be obtained by calling -[NSView _dirtyRegion] and then
+// calling -[NSRegion getRects:count:] on what it returns. Both these
+// methods have been present in the same form since at least OS X 10.5.
+// Unlike -[NSView getRectsBeingDrawn:count:], these methods can be called
+// outside a call to -[NSView drawRect:].
+- (NSRect)_dirtyRect;
+
+// Undocumented method of one or more of NSFrameView's subclasses. Called
+// when one or more of the titlebar buttons needs to be repositioned, to
+// disappear, or to reappear (say if the window's style changes). If
+// 'redisplay' is true, the entire titlebar (the window's top 22 pixels) is
+// marked as needing redisplay. This method has been present in the same
+// format since at least OS X 10.5.
+- (void)_tileTitlebarAndRedisplay:(BOOL)redisplay;
+
+// The following undocumented methods are used to work around bug 1069658,
+// which is an Apple bug or design flaw that effects Yosemite. None of them
+// were present prior to Yosemite (OS X 10.10).
+- (NSView *)titlebarView; // Method of NSThemeFrame
+- (NSView *)titlebarContainerView; // Method of NSThemeFrame
+- (BOOL)transparent; // Method of NSTitlebarView and NSTitlebarContainerView
+- (void)setTransparent:(BOOL)transparent; // Method of NSTitlebarView and
+ // NSTitlebarContainerView
+
+@end
+
+@interface ChildView : NSView<
+#ifdef ACCESSIBILITY
+ mozAccessible,
+#endif
+ mozView, NSTextInputClient>
+{
+@private
+ // the nsChildView that created the view. It retains this NSView, so
+ // the link back to it must be weak.
+ nsChildView* mGeckoChild;
+
+ // Text input handler for mGeckoChild and us. Note that this is a weak
+ // reference. Ideally, this should be a strong reference but a ChildView
+ // object can live longer than the mGeckoChild that owns it. And if
+ // mTextInputHandler were a strong reference, this would make it difficult
+ // for Gecko's leak detector to detect leaked TextInputHandler objects.
+ // This is initialized by [mozView installTextInputHandler:aHandler] and
+ // cleared by [mozView uninstallTextInputHandler].
+ mozilla::widget::TextInputHandler* mTextInputHandler; // [WEAK]
+
+ // when mouseDown: is called, we store its event here (strong)
+ NSEvent* mLastMouseDownEvent;
+
+ // Needed for IME support in e10s mode. Strong.
+ NSEvent* mLastKeyDownEvent;
+
+ // Whether the last mouse down event was blocked from Gecko.
+ BOOL mBlockedLastMouseDown;
+
+ // when acceptsFirstMouse: is called, we store the event here (strong)
+ NSEvent* mClickThroughMouseDownEvent;
+
+ // rects that were invalidated during a draw, so have pending drawing
+ NSMutableArray* mPendingDirtyRects;
+ BOOL mPendingFullDisplay;
+ BOOL mPendingDisplay;
+
+ // WheelStart/Stop events should always come in pairs. This BOOL records the
+ // last received event so that, when we receive one of the events, we make sure
+ // to send its pair event first, in case we didn't yet for any reason.
+ BOOL mExpectingWheelStop;
+
+ // Set to YES when our GL surface has been updated and we need to call
+ // updateGLContext before we composite.
+ BOOL mNeedsGLUpdate;
+
+ // Holds our drag service across multiple drag calls. The reference to the
+ // service is obtained when the mouse enters the view and is released when
+ // the mouse exits or there is a drop. This prevents us from having to
+ // re-establish the connection to the service manager many times per second
+ // when handling |draggingUpdated:| messages.
+ nsIDragService* mDragService;
+
+ NSOpenGLContext *mGLContext;
+
+ // Simple gestures support
+ //
+ // mGestureState is used to detect when Cocoa has called both
+ // magnifyWithEvent and rotateWithEvent within the same
+ // beginGestureWithEvent and endGestureWithEvent sequence. We
+ // discard the spurious gesture event so as not to confuse Gecko.
+ //
+ // mCumulativeMagnification keeps track of the total amount of
+ // magnification peformed during a magnify gesture so that we can
+ // send that value with the final MozMagnifyGesture event.
+ //
+ // mCumulativeRotation keeps track of the total amount of rotation
+ // performed during a rotate gesture so we can send that value with
+ // the final MozRotateGesture event.
+ enum {
+ eGestureState_None,
+ eGestureState_StartGesture,
+ eGestureState_MagnifyGesture,
+ eGestureState_RotateGesture
+ } mGestureState;
+ float mCumulativeMagnification;
+ float mCumulativeRotation;
+
+ BOOL mWaitingForPaint;
+
+#ifdef __LP64__
+ // Support for fluid swipe tracking.
+ BOOL* mCancelSwipeAnimation;
+#endif
+
+ // Whether this uses off-main-thread compositing.
+ BOOL mUsingOMTCompositor;
+
+ // The mask image that's used when painting into the titlebar using basic
+ // CGContext painting (i.e. non-accelerated).
+ CGImageRef mTopLeftCornerMask;
+}
+
+// class initialization
++ (void)initialize;
+
++ (void)registerViewForDraggedTypes:(NSView*)aView;
+
+// these are sent to the first responder when the window key status changes
+- (void)viewsWindowDidBecomeKey;
+- (void)viewsWindowDidResignKey;
+
+// Stop NSView hierarchy being changed during [ChildView drawRect:]
+- (void)delayedTearDown;
+
+- (void)sendFocusEvent:(mozilla::EventMessage)eventMessage;
+
+- (void)handleMouseMoved:(NSEvent*)aEvent;
+
+- (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
+ enter:(BOOL)aEnter
+ exitFrom:(mozilla::WidgetMouseEvent::ExitFrom)aExitFrom;
+
+- (void)updateGLContext;
+- (void)_surfaceNeedsUpdate:(NSNotification*)notification;
+
+- (bool)preRender:(NSOpenGLContext *)aGLContext;
+- (void)postRender:(NSOpenGLContext *)aGLContext;
+
+- (BOOL)isCoveringTitlebar;
+
+- (void)viewWillStartLiveResize;
+- (void)viewDidEndLiveResize;
+
+- (NSColor*)vibrancyFillColorForThemeGeometryType:(nsITheme::ThemeGeometryType)aThemeGeometryType;
+- (NSColor*)vibrancyFontSmoothingBackgroundColorForThemeGeometryType:(nsITheme::ThemeGeometryType)aThemeGeometryType;
+
+// Simple gestures support
+//
+// XXX - The swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
+// rotateWithEvent, and endGestureWithEvent methods are part of a
+// PRIVATE interface exported by nsResponder and reverse-engineering
+// was necessary to obtain the methods' prototypes. Thus, Apple may
+// change the interface in the future without notice.
+//
+// The prototypes were obtained from the following link:
+// http://cocoadex.com/2008/02/nsevent-modifications-swipe-ro.html
+- (void)swipeWithEvent:(NSEvent *)anEvent;
+- (void)beginGestureWithEvent:(NSEvent *)anEvent;
+- (void)magnifyWithEvent:(NSEvent *)anEvent;
+- (void)smartMagnifyWithEvent:(NSEvent *)anEvent;
+- (void)rotateWithEvent:(NSEvent *)anEvent;
+- (void)endGestureWithEvent:(NSEvent *)anEvent;
+
+- (void)scrollWheel:(NSEvent *)anEvent;
+- (void)handleAsyncScrollEvent:(CGEventRef)cgEvent ofType:(CGEventType)type;
+
+- (void)setUsingOMTCompositor:(BOOL)aUseOMTC;
+
+- (NSEvent*)lastKeyDownEvent;
+@end
+
+class ChildViewMouseTracker {
+
+public:
+
+ static void MouseMoved(NSEvent* aEvent);
+ static void MouseScrolled(NSEvent* aEvent);
+ static void OnDestroyView(ChildView* aView);
+ static void OnDestroyWindow(NSWindow* aWindow);
+ static BOOL WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent,
+ ChildView* aView, BOOL isClickThrough = NO);
+ static void MouseExitedWindow(NSEvent* aEvent);
+ static void MouseEnteredWindow(NSEvent* aEvent);
+ static void ReEvaluateMouseEnterState(NSEvent* aEvent = nil, ChildView* aOldView = nil);
+ static void ResendLastMouseMoveEvent();
+ static ChildView* ViewForEvent(NSEvent* aEvent);
+
+ static ChildView* sLastMouseEventView;
+ static NSEvent* sLastMouseMoveEvent;
+ static NSWindow* sWindowUnderMouse;
+ static NSPoint sLastScrollEventScreenLocation;
+};
+
+//-------------------------------------------------------------------------
+//
+// nsChildView
+//
+//-------------------------------------------------------------------------
+
+class nsChildView : public nsBaseWidget
+{
+private:
+ typedef nsBaseWidget Inherited;
+ typedef mozilla::layers::IAPZCTreeManager IAPZCTreeManager;
+
+public:
+ nsChildView();
+
+ // nsIWidget interface
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+
+ virtual void Destroy() override;
+
+ NS_IMETHOD Show(bool aState) override;
+ virtual bool IsVisible() const override;
+
+ NS_IMETHOD SetParent(nsIWidget* aNewParent) override;
+ virtual nsIWidget* GetParent(void) override;
+ virtual float GetDPI() override;
+
+ NS_IMETHOD Move(double aX, double aY) override;
+ NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD Resize(double aX, double aY,
+ double aWidth, double aHeight, bool aRepaint) override;
+
+ NS_IMETHOD Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ NS_IMETHOD SetFocus(bool aRaise) override;
+ virtual LayoutDeviceIntRect GetBounds() override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+
+ // Returns the "backing scale factor" of the view's window, which is the
+ // ratio of pixels in the window's backing store to Cocoa points. Prior to
+ // HiDPI support in OS X 10.7, this was always 1.0, but in HiDPI mode it
+ // will be 2.0 (and might potentially other values as screen resolutions
+ // evolve). This gives the relationship between what Gecko calls "device
+ // pixels" and the Cocoa "points" coordinate system.
+ CGFloat BackingScaleFactor() const;
+
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final {
+ return mozilla::DesktopToLayoutDeviceScale(BackingScaleFactor());
+ }
+
+ // Call if the window's backing scale factor changes - i.e., it is moved
+ // between HiDPI and non-HiDPI screens
+ void BackingScaleFactorChanged();
+
+ virtual double GetDefaultScaleInternal() override;
+
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect &aRect) override;
+
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual bool ShowsResizeIndicator(LayoutDeviceIntRect* aResizerRect) override;
+
+ static bool ConvertStatus(nsEventStatus aStatus)
+ { return aStatus == nsEventStatus_eConsumeNoDefault; }
+ NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+
+ virtual bool WidgetTypeSupportsAcceleration() override;
+ virtual bool ShouldUseOffMainThreadCompositing() override;
+
+ NS_IMETHOD SetCursor(nsCursor aCursor) override;
+ NS_IMETHOD SetCursor(imgIContainer* aCursor, uint32_t aHotspotX, uint32_t aHotspotY) override;
+
+ NS_IMETHOD SetTitle(const nsAString& title) override;
+
+ NS_IMETHOD GetAttention(int32_t aCycleCount) override;
+
+ virtual bool HasPendingInputEvent() override;
+
+ NS_IMETHOD ActivateNativeMenuItemAt(const nsAString& indexString) override;
+ NS_IMETHOD ForceUpdateNativeMenuAt(const nsAString& indexString) override;
+ NS_IMETHOD GetSelectionAsPlaintext(nsAString& aResult) override;
+
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override;
+ NS_IMETHOD_(TextEventDispatcherListener*)
+ GetNativeTextEventDispatcherListener() override;
+ NS_IMETHOD AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent) override;
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) override;
+ bool ExecuteNativeKeyBindingRemapped(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ uint32_t aGeckoKeyCode,
+ uint32_t aCocoaKeyCode);
+ virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
+
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+
+ virtual nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters,
+ nsIObserver* aObserver) override;
+
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override
+ { return SynthesizeNativeMouseEvent(aPoint, NSMouseMoved, 0, aObserver); }
+ virtual nsresult SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ double aDeltaX,
+ double aDeltaY,
+ double aDeltaZ,
+ uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags,
+ nsIObserver* aObserver) override;
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+
+ // Mac specific methods
+
+ virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent& event);
+
+ void WillPaintWindow();
+ bool PaintWindow(LayoutDeviceIntRegion aRegion);
+ bool PaintWindowInContext(CGContextRef aContext, const LayoutDeviceIntRegion& aRegion,
+ mozilla::gfx::IntSize aSurfaceSize);
+
+#ifdef ACCESSIBILITY
+ already_AddRefed<mozilla::a11y::Accessible> GetDocumentAccessible();
+#endif
+
+ virtual void CreateCompositor() override;
+ virtual void PrepareWindowEffects() override;
+ virtual void CleanupWindowEffects() override;
+ virtual bool PreRender(mozilla::widget::WidgetRenderingContext* aContext) override;
+ virtual void PostRender(mozilla::widget::WidgetRenderingContext* aContext) override;
+ virtual void DrawWindowOverlay(mozilla::widget::WidgetRenderingContext* aManager,
+ LayoutDeviceIntRect aRect) override;
+
+ virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) override;
+
+ virtual void UpdateWindowDraggingRegion(const LayoutDeviceIntRegion& aRegion) override;
+ LayoutDeviceIntRegion GetNonDraggableRegion() { return mNonDraggableRegion.Region(); }
+
+ virtual void ReportSwipeStarted(uint64_t aInputBlockId, bool aStartSwipe) override;
+
+ virtual void LookUpDictionary(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical,
+ const LayoutDeviceIntPoint& aPoint) override;
+
+ void ResetParent();
+
+ static bool DoHasPendingInputEvent();
+ static uint32_t GetCurrentInputEventCount();
+ static void UpdateCurrentInputEventCount();
+
+ NSView<mozView>* GetEditorView();
+
+ nsCocoaWindow* GetXULWindowWidget();
+
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) override;
+
+ mozilla::widget::TextInputHandler* GetTextInputHandler()
+ {
+ return mTextInputHandler;
+ }
+
+ void ClearVibrantAreas();
+ NSColor* VibrancyFillColorForThemeGeometryType(nsITheme::ThemeGeometryType aThemeGeometryType);
+ NSColor* VibrancyFontSmoothingBackgroundColorForThemeGeometryType(nsITheme::ThemeGeometryType aThemeGeometryType);
+
+ // unit conversion convenience functions
+ int32_t CocoaPointsToDevPixels(CGFloat aPts) const {
+ return nsCocoaUtils::CocoaPointsToDevPixels(aPts, BackingScaleFactor());
+ }
+ LayoutDeviceIntPoint CocoaPointsToDevPixels(const NSPoint& aPt) const {
+ return nsCocoaUtils::CocoaPointsToDevPixels(aPt, BackingScaleFactor());
+ }
+ LayoutDeviceIntPoint CocoaPointsToDevPixelsRoundDown(const NSPoint& aPt) const {
+ return nsCocoaUtils::CocoaPointsToDevPixelsRoundDown(aPt, BackingScaleFactor());
+ }
+ LayoutDeviceIntRect CocoaPointsToDevPixels(const NSRect& aRect) const {
+ return nsCocoaUtils::CocoaPointsToDevPixels(aRect, BackingScaleFactor());
+ }
+ CGFloat DevPixelsToCocoaPoints(int32_t aPixels) const {
+ return nsCocoaUtils::DevPixelsToCocoaPoints(aPixels, BackingScaleFactor());
+ }
+ NSRect DevPixelsToCocoaPoints(const LayoutDeviceIntRect& aRect) const {
+ return nsCocoaUtils::DevPixelsToCocoaPoints(aRect, BackingScaleFactor());
+ }
+
+ already_AddRefed<mozilla::gfx::DrawTarget>
+ StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ mozilla::layers::BufferMode* aBufferMode) override;
+ void EndRemoteDrawing() override;
+ void CleanupRemoteDrawing() override;
+ bool InitCompositor(mozilla::layers::Compositor* aCompositor) override;
+
+ IAPZCTreeManager* APZCTM() { return mAPZC ; }
+
+ NS_IMETHOD StartPluginIME(const mozilla::WidgetKeyboardEvent& aKeyboardEvent,
+ int32_t aPanelX, int32_t aPanelY,
+ nsString& aCommitted) override;
+
+ virtual void SetPluginFocused(bool& aFocused) override;
+
+ bool IsPluginFocused() { return mPluginFocused; }
+
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+
+ void DispatchAPZWheelInputEvent(mozilla::InputData& aEvent, bool aCanTriggerSwipe);
+
+ void SwipeFinished();
+
+protected:
+ virtual ~nsChildView();
+
+ void ReportMoveEvent();
+ void ReportSizeEvent();
+
+ // override to create different kinds of child views. Autoreleases, so
+ // caller must retain.
+ virtual NSView* CreateCocoaView(NSRect inFrame);
+ void TearDownView();
+
+ virtual already_AddRefed<nsIWidget>
+ AllocateChildPopupWidget() override
+ {
+ static NS_DEFINE_IID(kCPopUpCID, NS_POPUP_CID);
+ nsCOMPtr<nsIWidget> widget = do_CreateInstance(kCPopUpCID);
+ return widget.forget();
+ }
+
+ void ConfigureAPZCTreeManager() override;
+ void ConfigureAPZControllerThread() override;
+
+ void DoRemoteComposition(const LayoutDeviceIntRect& aRenderRect);
+
+ // Overlay drawing functions for OpenGL drawing
+ void DrawWindowOverlay(mozilla::layers::GLManager* aManager, LayoutDeviceIntRect aRect);
+ void MaybeDrawResizeIndicator(mozilla::layers::GLManager* aManager);
+ void MaybeDrawRoundedCorners(mozilla::layers::GLManager* aManager, const LayoutDeviceIntRect& aRect);
+ void MaybeDrawTitlebar(mozilla::layers::GLManager* aManager);
+
+ // Redraw the contents of mTitlebarCGContext on the main thread, as
+ // determined by mDirtyTitlebarRegion.
+ void UpdateTitlebarCGContext();
+
+ LayoutDeviceIntRect RectContainingTitlebarControls();
+ void UpdateVibrancy(const nsTArray<ThemeGeometry>& aThemeGeometries);
+ mozilla::VibrancyManager& EnsureVibrancyManager();
+
+ nsIWidget* GetWidgetForListenerEvents();
+
+ struct SwipeInfo {
+ bool wantsSwipe;
+ uint32_t allowedDirections;
+ };
+
+ SwipeInfo SendMayStartSwipe(const mozilla::PanGestureInput& aSwipeStartEvent);
+ void TrackScrollEventAsSwipe(const mozilla::PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections);
+
+protected:
+
+ NSView<mozView>* mView; // my parallel cocoa view (ChildView or NativeScrollbarView), [STRONG]
+ RefPtr<mozilla::widget::TextInputHandler> mTextInputHandler;
+ InputContext mInputContext;
+
+ NSView<mozView>* mParentView;
+ nsIWidget* mParentWidget;
+
+#ifdef ACCESSIBILITY
+ // weak ref to this childview's associated mozAccessible for speed reasons
+ // (we get queried for it *a lot* but don't want to own it)
+ nsWeakPtr mAccessible;
+#endif
+
+ // Protects the view from being teared down while a composition is in
+ // progress on the compositor thread.
+ mozilla::Mutex mViewTearDownLock;
+
+ mozilla::Mutex mEffectsLock;
+
+ // May be accessed from any thread, protected
+ // by mEffectsLock.
+ bool mShowsResizeIndicator;
+ LayoutDeviceIntRect mResizeIndicatorRect;
+ bool mHasRoundedBottomCorners;
+ int mDevPixelCornerRadius;
+ bool mIsCoveringTitlebar;
+ bool mIsFullscreen;
+ bool mIsOpaque;
+ LayoutDeviceIntRect mTitlebarRect;
+
+ // The area of mTitlebarCGContext that needs to be redrawn during the next
+ // transaction. Accessed from any thread, protected by mEffectsLock.
+ LayoutDeviceIntRegion mUpdatedTitlebarRegion;
+ CGContextRef mTitlebarCGContext;
+
+ // Compositor thread only
+ mozilla::UniquePtr<mozilla::widget::RectTextureImage> mResizerImage;
+ mozilla::UniquePtr<mozilla::widget::RectTextureImage> mCornerMaskImage;
+ mozilla::UniquePtr<mozilla::widget::RectTextureImage> mTitlebarImage;
+ mozilla::UniquePtr<mozilla::widget::RectTextureImage> mBasicCompositorImage;
+
+ // The area of mTitlebarCGContext that has changed and needs to be
+ // uploaded to to mTitlebarImage. Main thread only.
+ nsIntRegion mDirtyTitlebarRegion;
+
+ mozilla::ViewRegion mNonDraggableRegion;
+
+ // Cached value of [mView backingScaleFactor], to avoid sending two obj-c
+ // messages (respondsToSelector, backingScaleFactor) every time we need to
+ // use it.
+ // ** We'll need to reinitialize this if the backing resolution changes. **
+ mutable CGFloat mBackingScaleFactor;
+
+ bool mVisible;
+ bool mDrawing;
+ bool mIsDispatchPaint; // Is a paint event being dispatched
+
+ bool mPluginFocused;
+
+ // Used in OMTC BasicLayers mode. Presents the BasicCompositor result
+ // surface to the screen using an OpenGL context.
+ mozilla::UniquePtr<GLPresenter> mGLPresenter;
+
+ mozilla::UniquePtr<mozilla::VibrancyManager> mVibrancyManager;
+ RefPtr<mozilla::SwipeTracker> mSwipeTracker;
+ mozilla::UniquePtr<mozilla::SwipeEventQueue> mSwipeEventQueue;
+
+ // Only used for drawRect-based painting in popups.
+ RefPtr<mozilla::gfx::DrawTarget> mBackingSurface;
+
+ // This flag is only used when APZ is off. It indicates that the current pan
+ // gesture was processed as a swipe. Sometimes the swipe animation can finish
+ // before momentum events of the pan gesture have stopped firing, so this
+ // flag tells us that we shouldn't allow the remaining events to cause
+ // scrolling. It is reset to false once a new gesture starts (as indicated by
+ // a PANGESTURE_(MAY)START event).
+ bool mCurrentPanGestureBelongsToSwipe;
+
+ static uint32_t sLastInputEventCount;
+
+ void ReleaseTitlebarCGContext();
+
+ // This is used by SynthesizeNativeTouchPoint to maintain state between
+ // multiple synthesized points
+ mozilla::UniquePtr<mozilla::MultiTouchInput> mSynthesizedTouchInput;
+};
+
+#endif // nsChildView_h_
diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm
new file mode 100644
index 000000000..92ccd8b6c
--- /dev/null
+++ b/widget/cocoa/nsChildView.mm
@@ -0,0 +1,6580 @@
+/* -*- Mode: objc; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "mozilla/Logging.h"
+
+#include <unistd.h>
+#include <math.h>
+
+#include "nsChildView.h"
+#include "nsCocoaWindow.h"
+
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+
+#include "nsArrayUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsCOMPtr.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+
+#include "nsFontMetrics.h"
+#include "nsIRollupListener.h"
+#include "nsViewManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsGfxCIID.h"
+#include "nsIDOMSimpleGestureEvent.h"
+#include "nsThemeConstants.h"
+#include "nsIWidgetListener.h"
+#include "nsIPresShell.h"
+
+#include "nsDragService.h"
+#include "nsClipboard.h"
+#include "nsCursorManager.h"
+#include "nsWindowMap.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsMenuUtilsX.h"
+#include "nsMenuBarX.h"
+#include "NativeKeyBindings.h"
+#include "ComplexTextInputPanel.h"
+
+#include "gfxContext.h"
+#include "gfxQuartzSurface.h"
+#include "gfxUtils.h"
+#include "nsRegion.h"
+#include "Layers.h"
+#include "ClientLayerManager.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "GfxTexturesReporter.h"
+#include "GLTextureImage.h"
+#include "GLContextProvider.h"
+#include "GLContextCGL.h"
+#include "ScopedGLHelpers.h"
+#include "HeapCopyOfStackArray.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/GLManager.h"
+#include "mozilla/layers/CompositorOGL.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/BasicCompositor.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/widget/CompositorWidget.h"
+#include "gfxUtils.h"
+#include "gfxPrefs.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#include "mozilla/a11y/Platform.h"
+#endif
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
+#include "mozilla/Preferences.h"
+
+#include <dlfcn.h>
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include "GeckoProfiler.h"
+
+#include "nsIDOMWheelEvent.h"
+#include "mozilla/layers/ChromeProcessController.h"
+#include "nsLayoutUtils.h"
+#include "InputData.h"
+#include "RectTextureImage.h"
+#include "SwipeTracker.h"
+#include "VibrancyManager.h"
+#include "nsNativeThemeCocoa.h"
+#include "nsIDOMWindowUtils.h"
+#include "Units.h"
+#include "UnitTransforms.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+using namespace mozilla::gl;
+using namespace mozilla::widget;
+
+using mozilla::gfx::Matrix4x4;
+
+#undef DEBUG_UPDATE
+#undef INVALIDATE_DEBUGGING // flash areas as they are invalidated
+
+// Don't put more than this many rects in the dirty region, just fluff
+// out to the bounding-box if there are more
+#define MAX_RECTS_IN_REGION 100
+
+PRLogModuleInfo* sCocoaLog = nullptr;
+
+extern "C" {
+ CG_EXTERN void CGContextResetCTM(CGContextRef);
+ CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
+ CG_EXTERN void CGContextResetClip(CGContextRef);
+
+ typedef CFTypeRef CGSRegionObj;
+ CGError CGSNewRegionWithRect(const CGRect *rect, CGSRegionObj *outRegion);
+ CGError CGSNewRegionWithRectList(const CGRect *rects, int rectCount, CGSRegionObj *outRegion);
+}
+
+// defined in nsMenuBarX.mm
+extern NSMenu* sApplicationMenu; // Application menu shared by all menubars
+
+static bool gChildViewMethodsSwizzled = false;
+
+extern nsIArray *gDraggedTransferables;
+
+ChildView* ChildViewMouseTracker::sLastMouseEventView = nil;
+NSEvent* ChildViewMouseTracker::sLastMouseMoveEvent = nil;
+NSWindow* ChildViewMouseTracker::sWindowUnderMouse = nil;
+NSPoint ChildViewMouseTracker::sLastScrollEventScreenLocation = NSZeroPoint;
+
+#ifdef INVALIDATE_DEBUGGING
+static void blinkRect(Rect* r);
+static void blinkRgn(RgnHandle rgn);
+#endif
+
+bool gUserCancelledDrag = false;
+
+uint32_t nsChildView::sLastInputEventCount = 0;
+
+static uint32_t gNumberOfWidgetsNeedingEventThread = 0;
+
+@interface ChildView(Private)
+
+// sets up our view, attaching it to its owning gecko view
+- (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild;
+
+// set up a gecko mouse event based on a cocoa mouse event
+- (void) convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent
+ toGeckoEvent:(WidgetWheelEvent*)outWheelEvent;
+- (void) convertCocoaMouseEvent:(NSEvent*)aMouseEvent
+ toGeckoEvent:(WidgetInputEvent*)outGeckoEvent;
+
+- (NSMenu*)contextMenu;
+
+- (BOOL)isRectObscuredBySubview:(NSRect)inRect;
+
+- (void)processPendingRedraws;
+
+- (void)drawRect:(NSRect)aRect inContext:(CGContextRef)aContext;
+- (LayoutDeviceIntRegion)nativeDirtyRegionWithBoundingRect:(NSRect)aRect;
+- (BOOL)isUsingMainThreadOpenGL;
+- (BOOL)isUsingOpenGL;
+- (void)drawUsingOpenGL;
+- (void)drawUsingOpenGLCallback;
+
+- (BOOL)hasRoundedBottomCorners;
+- (CGFloat)cornerRadius;
+- (void)clearCorners;
+
+-(void)setGLOpaque:(BOOL)aOpaque;
+
+// Overlay drawing functions for traditional CGContext drawing
+- (void)drawTitleString;
+- (void)drawTitlebarHighlight;
+- (void)maskTopCornersInContext:(CGContextRef)aContext;
+
+#if USE_CLICK_HOLD_CONTEXTMENU
+ // called on a timer two seconds after a mouse down to see if we should display
+ // a context menu (click-hold)
+- (void)clickHoldCallback:(id)inEvent;
+#endif
+
+#ifdef ACCESSIBILITY
+- (id<mozAccessible>)accessible;
+#endif
+
+- (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint;
+- (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint;
+- (IAPZCTreeManager*)apzctm;
+
+- (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent;
+- (void)updateWindowDraggableState;
+
+- (bool)shouldConsiderStartingSwipeFromEvent:(NSEvent*)aEvent;
+
+@end
+
+@interface EventThreadRunner : NSObject
+{
+ NSThread* mThread;
+}
+- (id)init;
+
++ (void)start;
++ (void)stop;
+
+@end
+
+@interface NSView(NSThemeFrameCornerRadius)
+- (float)roundedCornerRadius;
+@end
+
+@interface NSView(DraggableRegion)
+- (CGSRegionObj)_regionForOpaqueDescendants:(NSRect)aRect forMove:(BOOL)aForMove;
+- (CGSRegionObj)_regionForOpaqueDescendants:(NSRect)aRect forMove:(BOOL)aForMove forUnderTitlebar:(BOOL)aForUnderTitlebar;
+@end
+
+@interface NSWindow(NSWindowShouldZoomOnDoubleClick)
++ (BOOL)_shouldZoomOnDoubleClick; // present on 10.7 and above
+@end
+
+// Starting with 10.7 the bottom corners of all windows are rounded.
+// Unfortunately, the standard rounding that OS X applies to OpenGL views
+// does not use anti-aliasing and looks very crude. Since we want a smooth,
+// anti-aliased curve, we'll draw it ourselves.
+// Additionally, we need to turn off the OS-supplied rounding because it
+// eats into our corner's curve. We do that by overriding an NSSurface method.
+@interface NSSurface @end
+
+@implementation NSSurface(DontCutOffCorners)
+- (CGSRegionObj)_createRoundedBottomRegionForRect:(CGRect)rect
+{
+ // Create a normal rect region without rounded bottom corners.
+ CGSRegionObj region;
+ CGSNewRegionWithRect(&rect, &region);
+ return region;
+}
+@end
+
+#pragma mark -
+
+// Flips a screen coordinate from a point in the cocoa coordinate system (bottom-left rect) to a point
+// that is a "flipped" cocoa coordinate system (starts in the top-left).
+static inline void
+FlipCocoaScreenCoordinate(NSPoint &inPoint)
+{
+ inPoint.y = nsCocoaUtils::FlippedScreenY(inPoint.y);
+}
+
+void EnsureLogInitialized()
+{
+ if (!sCocoaLog) {
+ sCocoaLog = PR_NewLogModule("nsCocoaWidgets");
+ }
+}
+
+namespace {
+
+// Used for OpenGL drawing from the compositor thread for OMTC BasicLayers.
+// We need to use OpenGL for this because there seems to be no other robust
+// way of drawing from a secondary thread without locking, which would cause
+// deadlocks in our setup. See bug 882523.
+class GLPresenter : public GLManager
+{
+public:
+ static mozilla::UniquePtr<GLPresenter> CreateForWindow(nsIWidget* aWindow)
+ {
+ // Contrary to CompositorOGL, we allow unaccelerated OpenGL contexts to be
+ // used. BasicCompositor only requires very basic GL functionality.
+ RefPtr<GLContext> context = gl::GLContextProvider::CreateForWindow(aWindow, false);
+ return context ? MakeUnique<GLPresenter>(context) : nullptr;
+ }
+
+ explicit GLPresenter(GLContext* aContext);
+ virtual ~GLPresenter();
+
+ virtual GLContext* gl() const override { return mGLContext; }
+ virtual ShaderProgramOGL* GetProgram(GLenum aTarget, gfx::SurfaceFormat aFormat) override
+ {
+ MOZ_ASSERT(aTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+ MOZ_ASSERT(aFormat == gfx::SurfaceFormat::R8G8B8A8);
+ return mRGBARectProgram.get();
+ }
+ virtual const gfx::Matrix4x4& GetProjMatrix() const override
+ {
+ return mProjMatrix;
+ }
+ virtual void ActivateProgram(ShaderProgramOGL *aProg) override
+ {
+ mGLContext->fUseProgram(aProg->GetProgram());
+ }
+ virtual void BindAndDrawQuad(ShaderProgramOGL *aProg,
+ const gfx::Rect& aLayerRect,
+ const gfx::Rect& aTextureRect) override;
+
+ void BeginFrame(LayoutDeviceIntSize aRenderSize);
+ void EndFrame();
+
+ NSOpenGLContext* GetNSOpenGLContext()
+ {
+ return GLContextCGL::Cast(mGLContext)->GetNSOpenGLContext();
+ }
+
+protected:
+ RefPtr<mozilla::gl::GLContext> mGLContext;
+ mozilla::UniquePtr<mozilla::layers::ShaderProgramOGL> mRGBARectProgram;
+ gfx::Matrix4x4 mProjMatrix;
+ GLuint mQuadVBO;
+};
+
+} // unnamed namespace
+
+namespace mozilla {
+
+struct SwipeEventQueue {
+ SwipeEventQueue(uint32_t aAllowedDirections, uint64_t aInputBlockId)
+ : allowedDirections(aAllowedDirections)
+ , inputBlockId(aInputBlockId)
+ {}
+
+ nsTArray<PanGestureInput> queuedEvents;
+ uint32_t allowedDirections;
+ uint64_t inputBlockId;
+};
+
+} // namespace mozilla
+
+#pragma mark -
+
+nsChildView::nsChildView() : nsBaseWidget()
+, mView(nullptr)
+, mParentView(nullptr)
+, mParentWidget(nullptr)
+, mViewTearDownLock("ChildViewTearDown")
+, mEffectsLock("WidgetEffects")
+, mShowsResizeIndicator(false)
+, mHasRoundedBottomCorners(false)
+, mIsCoveringTitlebar(false)
+, mIsFullscreen(false)
+, mIsOpaque(false)
+, mTitlebarCGContext(nullptr)
+, mBackingScaleFactor(0.0)
+, mVisible(false)
+, mDrawing(false)
+, mIsDispatchPaint(false)
+{
+ EnsureLogInitialized();
+}
+
+nsChildView::~nsChildView()
+{
+ ReleaseTitlebarCGContext();
+
+ if (mSwipeTracker) {
+ mSwipeTracker->Destroy();
+ mSwipeTracker = nullptr;
+ }
+
+ // Notify the children that we're gone. childView->ResetParent() can change
+ // our list of children while it's being iterated, so the way we iterate the
+ // list must allow for this.
+ for (nsIWidget* kid = mLastChild; kid;) {
+ nsChildView* childView = static_cast<nsChildView*>(kid);
+ kid = kid->GetPrevSibling();
+ childView->ResetParent();
+ }
+
+ NS_WARNING_ASSERTION(
+ mOnDestroyCalled,
+ "nsChildView object destroyed without calling Destroy()");
+
+ DestroyCompositor();
+
+ if (mAPZC && gfxPrefs::AsyncPanZoomSeparateEventThread()) {
+ gNumberOfWidgetsNeedingEventThread--;
+ if (gNumberOfWidgetsNeedingEventThread == 0) {
+ [EventThreadRunner stop];
+ }
+ }
+
+ // An nsChildView object that was in use can be destroyed without Destroy()
+ // ever being called on it. So we also need to do a quick, safe cleanup
+ // here (it's too late to just call Destroy(), which can cause crashes).
+ // It's particularly important to make sure widgetDestroyed is called on our
+ // mView -- this method NULLs mView's mGeckoChild, and NULL checks on
+ // mGeckoChild are used throughout the ChildView class to tell if it's safe
+ // to use a ChildView object.
+ [mView widgetDestroyed]; // Safe if mView is nil.
+ mParentWidget = nil;
+ TearDownView(); // Safe if called twice.
+}
+
+void
+nsChildView::ReleaseTitlebarCGContext()
+{
+ if (mTitlebarCGContext) {
+ CGContextRelease(mTitlebarCGContext);
+ mTitlebarCGContext = nullptr;
+ }
+}
+
+nsresult
+nsChildView::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Because the hidden window is created outside of an event loop,
+ // we need to provide an autorelease pool to avoid leaking cocoa objects
+ // (see bug 559075).
+ nsAutoreleasePool localPool;
+
+ // See NSView (MethodSwizzling) below.
+ if (!gChildViewMethodsSwizzled) {
+ nsToolkit::SwizzleMethods([NSView class], @selector(mouseDownCanMoveWindow),
+ @selector(nsChildView_NSView_mouseDownCanMoveWindow));
+#ifdef __LP64__
+ nsToolkit::SwizzleMethods([NSEvent class], @selector(addLocalMonitorForEventsMatchingMask:handler:),
+ @selector(nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:handler:),
+ true);
+ nsToolkit::SwizzleMethods([NSEvent class], @selector(removeMonitor:),
+ @selector(nsChildView_NSEvent_removeMonitor:), true);
+#endif
+ gChildViewMethodsSwizzled = true;
+ }
+
+ mBounds = aRect;
+
+ // Ensure that the toolkit is created.
+ nsToolkit::GetToolkit();
+
+ BaseCreate(aParent, aInitData);
+
+ // inherit things from the parent view and create our parallel
+ // NSView in the Cocoa display system
+ mParentView = nil;
+ if (aParent) {
+ // inherit the top-level window. NS_NATIVE_WIDGET is always a NSView
+ // regardless of if we're asking a window or a view (for compatibility
+ // with windows).
+ mParentView = (NSView<mozView>*)aParent->GetNativeData(NS_NATIVE_WIDGET);
+ mParentWidget = aParent;
+ } else {
+ // This is the normal case. When we're the root widget of the view hiararchy,
+ // aNativeParent will be the contentView of our window, since that's what
+ // nsCocoaWindow returns when asked for an NS_NATIVE_VIEW.
+ mParentView = reinterpret_cast<NSView<mozView>*>(aNativeParent);
+ }
+
+ // create our parallel NSView and hook it up to our parent. Recall
+ // that NS_NATIVE_WIDGET is the NSView.
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mParentView);
+ NSRect r = nsCocoaUtils::DevPixelsToCocoaPoints(mBounds, scaleFactor);
+ mView = [(NSView<mozView>*)CreateCocoaView(r) retain];
+ if (!mView) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this view was created in a Gecko view hierarchy, the initial state
+ // is hidden. If the view is attached only to a native NSView but has
+ // no Gecko parent (as in embedding), the initial state is visible.
+ if (mParentWidget)
+ [mView setHidden:YES];
+ else
+ mVisible = true;
+
+ // Hook it up in the NSView hierarchy.
+ if (mParentView) {
+ [mParentView addSubview:mView];
+ }
+
+ // if this is a ChildView, make sure that our per-window data
+ // is set up
+ if ([mView isKindOfClass:[ChildView class]])
+ [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:[mView window]];
+
+ NS_ASSERTION(!mTextInputHandler, "mTextInputHandler has already existed");
+ mTextInputHandler = new TextInputHandler(this, mView);
+
+ mPluginFocused = false;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Creates the appropriate child view. Override to create something other than
+// our |ChildView| object. Autoreleases, so caller must retain.
+NSView*
+nsChildView::CreateCocoaView(NSRect inFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[ChildView alloc] initWithFrame:inFrame geckoChild:this] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void nsChildView::TearDownView()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mView)
+ return;
+
+ NSWindow* win = [mView window];
+ NSResponder* responder = [win firstResponder];
+
+ // We're being unhooked from the view hierarchy, don't leave our view
+ // or a child view as the window first responder.
+ if (responder && [responder isKindOfClass:[NSView class]] &&
+ [(NSView*)responder isDescendantOf:mView]) {
+ [win makeFirstResponder:[mView superview]];
+ }
+
+ // If mView is win's contentView, win (mView's NSWindow) "owns" mView --
+ // win has retained mView, and will detach it from the view hierarchy and
+ // release it when necessary (when win is itself destroyed (in a call to
+ // [win dealloc])). So all we need to do here is call [mView release] (to
+ // match the call to [mView retain] in nsChildView::StandardCreate()).
+ // Also calling [mView removeFromSuperviewWithoutNeedingDisplay] causes
+ // mView to be released again and dealloced, while remaining win's
+ // contentView. So if we do that here, win will (for a short while) have
+ // an invalid contentView (for the consequences see bmo bugs 381087 and
+ // 374260).
+ if ([mView isEqual:[win contentView]]) {
+ [mView release];
+ } else {
+ // Stop NSView hierarchy being changed during [ChildView drawRect:]
+ [mView performSelectorOnMainThread:@selector(delayedTearDown) withObject:nil waitUntilDone:false];
+ }
+ mView = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsCocoaWindow*
+nsChildView::GetXULWindowWidget()
+{
+ id windowDelegate = [[mView window] delegate];
+ if (windowDelegate && [windowDelegate isKindOfClass:[WindowDelegate class]]) {
+ return [(WindowDelegate *)windowDelegate geckoWidget];
+ }
+ return nullptr;
+}
+
+void nsChildView::Destroy()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Make sure that no composition is in progress while disconnecting
+ // ourselves from the view.
+ MutexAutoLock lock(mViewTearDownLock);
+
+ if (mOnDestroyCalled)
+ return;
+ mOnDestroyCalled = true;
+
+ // Stuff below may delete the last ref to this
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ [mView widgetDestroyed];
+
+ nsBaseWidget::Destroy();
+
+ NotifyWindowDestroyed();
+ mParentWidget = nil;
+
+ TearDownView();
+
+ nsBaseWidget::OnDestroy();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark -
+
+#if 0
+static void PrintViewHierarchy(NSView *view)
+{
+ while (view) {
+ NSLog(@" view is %x, frame %@", view, NSStringFromRect([view frame]));
+ view = [view superview];
+ }
+}
+#endif
+
+// Return native data according to aDataType
+void* nsChildView::GetNativeData(uint32_t aDataType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
+
+ void* retVal = nullptr;
+
+ switch (aDataType)
+ {
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_DISPLAY:
+ retVal = (void*)mView;
+ break;
+
+ case NS_NATIVE_WINDOW:
+ retVal = [mView window];
+ break;
+
+ case NS_NATIVE_GRAPHIC:
+ NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a Mac OS X child view!");
+ retVal = nullptr;
+ break;
+
+ case NS_NATIVE_OFFSETX:
+ retVal = 0;
+ break;
+
+ case NS_NATIVE_OFFSETY:
+ retVal = 0;
+ break;
+
+ case NS_RAW_NATIVE_IME_CONTEXT:
+ retVal = GetPseudoIMEContext();
+ if (retVal) {
+ break;
+ }
+ retVal = [mView inputContext];
+ // If input context isn't available on this widget, we should set |this|
+ // instead of nullptr since if this returns nullptr, IMEStateManager
+ // cannot manage composition with TextComposition instance. Although,
+ // this case shouldn't occur.
+ if (NS_WARN_IF(!retVal)) {
+ retVal = this;
+ }
+ break;
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+#pragma mark -
+
+nsTransparencyMode nsChildView::GetTransparencyMode()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ nsCocoaWindow* windowWidget = GetXULWindowWidget();
+ return windowWidget ? windowWidget->GetTransparencyMode() : eTransparencyOpaque;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque);
+}
+
+// This is called by nsContainerFrame on the root widget for all window types
+// except popup windows (when nsCocoaWindow::SetTransparencyMode is used instead).
+void nsChildView::SetTransparencyMode(nsTransparencyMode aMode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ nsCocoaWindow* windowWidget = GetXULWindowWidget();
+ if (windowWidget) {
+ windowWidget->SetTransparencyMode(aMode);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool nsChildView::IsVisible() const
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!mVisible) {
+ return mVisible;
+ }
+
+ // mVisible does not accurately reflect the state of a hidden tabbed view
+ // so verify that the view has a window as well
+ // then check native widget hierarchy visibility
+ return ([mView window] != nil) && !NSIsEmptyRect([mView visibleRect]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+// Some NSView methods (e.g. setFrame and setHidden) invalidate the view's
+// bounds in our window. However, we don't want these invalidations because
+// they are unnecessary and because they actually slow us down since we
+// block on the compositor inside drawRect.
+// When we actually need something invalidated, there will be an explicit call
+// to Invalidate from Gecko, so turning these automatic invalidations off
+// won't hurt us in the non-OMTC case.
+// The invalidations inside these NSView methods happen via a call to the
+// private method -[NSWindow _setNeedsDisplayInRect:]. Our BaseWindow
+// implementation of that method is augmented to let us ignore those calls
+// using -[BaseWindow disable/enableSetNeedsDisplay].
+static void
+ManipulateViewWithoutNeedingDisplay(NSView* aView, void (^aCallback)())
+{
+ BaseWindow* win = nil;
+ if ([[aView window] isKindOfClass:[BaseWindow class]]) {
+ win = (BaseWindow*)[aView window];
+ }
+ [win disableSetNeedsDisplay];
+ aCallback();
+ [win enableSetNeedsDisplay];
+}
+
+// Hide or show this component
+NS_IMETHODIMP nsChildView::Show(bool aState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (aState != mVisible) {
+ // Provide an autorelease pool because this gets called during startup
+ // on the "hidden window", resulting in cocoa object leakage if there's
+ // no pool in place.
+ nsAutoreleasePool localPool;
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setHidden:!aState];
+ });
+
+ mVisible = aState;
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Change the parent of this widget
+NS_IMETHODIMP
+nsChildView::SetParent(nsIWidget* aNewParent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mOnDestroyCalled)
+ return NS_OK;
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ if (mParentWidget) {
+ mParentWidget->RemoveChild(this);
+ }
+
+ if (aNewParent) {
+ ReparentNativeWidget(aNewParent);
+ } else {
+ [mView removeFromSuperview];
+ mParentView = nil;
+ }
+
+ mParentWidget = aNewParent;
+
+ if (mParentWidget) {
+ mParentWidget->AddChild(this);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsChildView::ReparentNativeWidget(nsIWidget* aNewParent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_PRECONDITION(aNewParent, "");
+
+ if (mOnDestroyCalled)
+ return;
+
+ NSView<mozView>* newParentView =
+ (NSView<mozView>*)aNewParent->GetNativeData(NS_NATIVE_WIDGET);
+ NS_ENSURE_TRUE_VOID(newParentView);
+
+ // we hold a ref to mView, so this is safe
+ [mView removeFromSuperview];
+ mParentView = newParentView;
+ [mParentView addSubview:mView];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsChildView::ResetParent()
+{
+ if (!mOnDestroyCalled) {
+ if (mParentWidget)
+ mParentWidget->RemoveChild(this);
+ if (mView)
+ [mView removeFromSuperview];
+ }
+ mParentWidget = nullptr;
+}
+
+nsIWidget*
+nsChildView::GetParent()
+{
+ return mParentWidget;
+}
+
+float
+nsChildView::GetDPI()
+{
+ NSWindow* window = [mView window];
+ if (window && [window isKindOfClass:[BaseWindow class]]) {
+ return [(BaseWindow*)window getDPI];
+ }
+
+ return 96.0;
+}
+
+NS_IMETHODIMP nsChildView::Enable(bool aState)
+{
+ return NS_OK;
+}
+
+bool nsChildView::IsEnabled() const
+{
+ return true;
+}
+
+NS_IMETHODIMP nsChildView::SetFocus(bool aRaise)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindow* window = [mView window];
+ if (window)
+ [window makeFirstResponder:mView];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Override to set the cursor on the mac
+NS_IMETHODIMP nsChildView::SetCursor(nsCursor aCursor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if ([mView isDragInProgress])
+ return NS_OK; // Don't change the cursor during dragging.
+
+ nsBaseWidget::SetCursor(aCursor);
+ return [[nsCursorManager sharedInstance] setCursor:aCursor];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// implement to fix "hidden virtual function" warning
+NS_IMETHODIMP nsChildView::SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsBaseWidget::SetCursor(aCursor, aHotspotX, aHotspotY);
+ return [[nsCursorManager sharedInstance] setCursorWithImage:aCursor hotSpotX:aHotspotX hotSpotY:aHotspotY scaleFactor:BackingScaleFactor()];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+#pragma mark -
+
+// Get this component dimension
+LayoutDeviceIntRect
+nsChildView::GetBounds()
+{
+ return !mView ? mBounds : CocoaPointsToDevPixels([mView frame]);
+}
+
+LayoutDeviceIntRect
+nsChildView::GetClientBounds()
+{
+ LayoutDeviceIntRect rect = GetBounds();
+ if (!mParentWidget) {
+ // For top level widgets we want the position on screen, not the position
+ // of this view inside the window.
+ rect.MoveTo(WidgetToScreenOffset());
+ }
+ return rect;
+}
+
+LayoutDeviceIntRect
+nsChildView::GetScreenBounds()
+{
+ LayoutDeviceIntRect rect = GetBounds();
+ rect.MoveTo(WidgetToScreenOffset());
+ return rect;
+}
+
+double
+nsChildView::GetDefaultScaleInternal()
+{
+ return BackingScaleFactor();
+}
+
+CGFloat
+nsChildView::BackingScaleFactor() const
+{
+ if (mBackingScaleFactor > 0.0) {
+ return mBackingScaleFactor;
+ }
+ if (!mView) {
+ return 1.0;
+ }
+ mBackingScaleFactor = nsCocoaUtils::GetBackingScaleFactor(mView);
+ return mBackingScaleFactor;
+}
+
+void
+nsChildView::BackingScaleFactorChanged()
+{
+ CGFloat newScale = nsCocoaUtils::GetBackingScaleFactor(mView);
+
+ // ignore notification if it hasn't really changed (or maybe we have
+ // disabled HiDPI mode via prefs)
+ if (mBackingScaleFactor == newScale) {
+ return;
+ }
+
+ mBackingScaleFactor = newScale;
+ NSRect frame = [mView frame];
+ mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, newScale);
+
+ if (mWidgetListener && !mWidgetListener->GetXULWindow()) {
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->BackingScaleFactorChanged();
+ }
+ }
+}
+
+int32_t
+nsChildView::RoundsWidgetCoordinatesTo()
+{
+ if (BackingScaleFactor() == 2.0) {
+ return 2;
+ }
+ return 1;
+}
+
+// Move this component, aX and aY are in the parent widget coordinate system
+NS_IMETHODIMP nsChildView::Move(double aX, double aY)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ int32_t x = NSToIntRound(aX);
+ int32_t y = NSToIntRound(aY);
+
+ if (!mView || (mBounds.x == x && mBounds.y == y))
+ return NS_OK;
+
+ mBounds.x = x;
+ mBounds.y = y;
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
+ });
+
+ NotifyRollupGeometryChange();
+ ReportMoveEvent();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsChildView::Resize(double aWidth, double aHeight, bool aRepaint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ int32_t width = NSToIntRound(aWidth);
+ int32_t height = NSToIntRound(aHeight);
+
+ if (!mView || (mBounds.width == width && mBounds.height == height))
+ return NS_OK;
+
+ mBounds.width = width;
+ mBounds.height = height;
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
+ });
+
+ if (mVisible && aRepaint)
+ [mView setNeedsDisplay:YES];
+
+ NotifyRollupGeometryChange();
+ ReportSizeEvent();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsChildView::Resize(double aX, double aY,
+ double aWidth, double aHeight, bool aRepaint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ int32_t x = NSToIntRound(aX);
+ int32_t y = NSToIntRound(aY);
+ int32_t width = NSToIntRound(aWidth);
+ int32_t height = NSToIntRound(aHeight);
+
+ BOOL isMoving = (mBounds.x != x || mBounds.y != y);
+ BOOL isResizing = (mBounds.width != width || mBounds.height != height);
+ if (!mView || (!isMoving && !isResizing))
+ return NS_OK;
+
+ if (isMoving) {
+ mBounds.x = x;
+ mBounds.y = y;
+ }
+ if (isResizing) {
+ mBounds.width = width;
+ mBounds.height = height;
+ }
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
+ });
+
+ if (mVisible && aRepaint)
+ [mView setNeedsDisplay:YES];
+
+ NotifyRollupGeometryChange();
+ if (isMoving) {
+ ReportMoveEvent();
+ if (mOnDestroyCalled)
+ return NS_OK;
+ }
+ if (isResizing)
+ ReportSizeEvent();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+static const int32_t resizeIndicatorWidth = 15;
+static const int32_t resizeIndicatorHeight = 15;
+bool nsChildView::ShowsResizeIndicator(LayoutDeviceIntRect* aResizerRect)
+{
+ NSView *topLevelView = mView, *superView = nil;
+ while ((superView = [topLevelView superview]))
+ topLevelView = superView;
+
+ if (![[topLevelView window] showsResizeIndicator] ||
+ !([[topLevelView window] styleMask] & NSResizableWindowMask))
+ return false;
+
+ if (aResizerRect) {
+ NSSize bounds = [topLevelView bounds].size;
+ NSPoint corner = NSMakePoint(bounds.width, [topLevelView isFlipped] ? bounds.height : 0);
+ corner = [topLevelView convertPoint:corner toView:mView];
+ aResizerRect->SetRect(NSToIntRound(corner.x) - resizeIndicatorWidth,
+ NSToIntRound(corner.y) - resizeIndicatorHeight,
+ resizeIndicatorWidth, resizeIndicatorHeight);
+ }
+ return true;
+}
+
+nsresult nsChildView::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "keyevent");
+ return mTextInputHandler->SynthesizeNativeKeyEvent(aNativeKeyboardLayout,
+ aNativeKeyCode,
+ aModifierFlags,
+ aCharacters,
+ aUnmodifiedCharacters);
+}
+
+nsresult nsChildView::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ NSPoint pt =
+ nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
+
+ // Move the mouse cursor to the requested position and reconnect it to the mouse.
+ CGWarpMouseCursorPosition(NSPointToCGPoint(pt));
+ CGAssociateMouseAndMouseCursorPosition(true);
+
+ // aPoint is given with the origin on the top left, but convertScreenToBase
+ // expects a point in a coordinate system that has its origin on the bottom left.
+ NSPoint screenPoint = NSMakePoint(pt.x, nsCocoaUtils::FlippedScreenY(pt.y));
+ NSPoint windowPoint =
+ nsCocoaUtils::ConvertPointFromScreen([mView window], screenPoint);
+
+ NSEvent* event = [NSEvent mouseEventWithType:(NSEventType)aNativeMessage
+ location:windowPoint
+ modifierFlags:aModifierFlags
+ timestamp:[[NSProcessInfo processInfo] systemUptime]
+ windowNumber:[[mView window] windowNumber]
+ context:nil
+ eventNumber:0
+ clickCount:1
+ pressure:0.0];
+
+ if (!event)
+ return NS_ERROR_FAILURE;
+
+ if ([[mView window] isKindOfClass:[BaseWindow class]]) {
+ // Tracking area events don't end up in their tracking areas when sent
+ // through [NSApp sendEvent:], so pass them directly to the right methods.
+ BaseWindow* window = (BaseWindow*)[mView window];
+ if (aNativeMessage == NSMouseEntered) {
+ [window mouseEntered:event];
+ return NS_OK;
+ }
+ if (aNativeMessage == NSMouseExited) {
+ [window mouseExited:event];
+ return NS_OK;
+ }
+ if (aNativeMessage == NSMouseMoved) {
+ [window mouseMoved:event];
+ return NS_OK;
+ }
+ }
+
+ [NSApp sendEvent:event];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsChildView::SynthesizeNativeMouseScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ double aDeltaX,
+ double aDeltaY,
+ double aDeltaZ,
+ uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags,
+ nsIObserver* aObserver)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+
+ NSPoint pt =
+ nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
+
+ // Move the mouse cursor to the requested position and reconnect it to the mouse.
+ CGWarpMouseCursorPosition(NSPointToCGPoint(pt));
+ CGAssociateMouseAndMouseCursorPosition(true);
+
+ // Mostly copied from http://stackoverflow.com/a/6130349
+ CGScrollEventUnit units =
+ (aAdditionalFlags & nsIDOMWindowUtils::MOUSESCROLL_SCROLL_LINES)
+ ? kCGScrollEventUnitLine : kCGScrollEventUnitPixel;
+ CGEventRef cgEvent = CGEventCreateScrollWheelEvent(NULL, units, 3, aDeltaY, aDeltaX, aDeltaZ);
+ if (!cgEvent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CGEventPost(kCGHIDEventTap, cgEvent);
+ CFRelease(cgEvent);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsChildView::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ mozilla::LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aPointerState == TOUCH_HOVER) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mSynthesizedTouchInput) {
+ mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
+ }
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
+ mSynthesizedTouchInput.get(), PR_IntervalNow(), TimeStamp::Now(),
+ aPointerId, aPointerState, pointInWindow, aPointerPressure,
+ aPointerOrientation);
+ DispatchTouchInput(inputToDispatch);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// First argument has to be an NSMenu representing the application's top-level
+// menu bar. The returned item is *not* retained.
+static NSMenuItem* NativeMenuItemWithLocation(NSMenu* menubar, NSString* locationString)
+{
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0)
+ return nil;
+
+ NSMenu* currentSubmenu = [NSApp mainMenu];
+ for (unsigned int i = 0; i < indexCount; i++) {
+ int targetIndex;
+ // We remove the application menu from consideration for the top-level menu
+ if (i == 0)
+ targetIndex = [[indexes objectAtIndex:i] intValue] + 1;
+ else
+ targetIndex = [[indexes objectAtIndex:i] intValue];
+ int itemCount = [currentSubmenu numberOfItems];
+ if (targetIndex < itemCount) {
+ NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
+ // if this is the last index just return the menu item
+ if (i == (indexCount - 1))
+ return menuItem;
+ // if this is not the last index find the submenu and keep going
+ if ([menuItem hasSubmenu])
+ currentSubmenu = [menuItem submenu];
+ else
+ return nil;
+ }
+ }
+
+ return nil;
+}
+
+// Used for testing native menu system structure and event handling.
+NS_IMETHODIMP nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSMenuItem* item = NativeMenuItemWithLocation([NSApp mainMenu], locationString);
+ // We can't perform an action on an item with a submenu, that will raise
+ // an obj-c exception.
+ if (item && ![item hasSubmenu]) {
+ NSMenu* parent = [item menu];
+ if (parent) {
+ // NSLog(@"Performing action for native menu item titled: %@\n",
+ // [[currentSubmenu itemAtIndex:targetIndex] title]);
+ [parent performActionForItemAtIndex:[parent indexOfItem:item]];
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Used for testing native menu system structure and event handling.
+NS_IMETHODIMP nsChildView::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCocoaWindow *widget = GetXULWindowWidget();
+ if (widget) {
+ nsMenuBarX* mb = widget->GetMenuBar();
+ if (mb) {
+ if (indexString.IsEmpty())
+ mb->ForceNativeMenuReload();
+ else
+ mb->ForceUpdateNativeMenuAt(indexString);
+ }
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+#pragma mark -
+
+#ifdef INVALIDATE_DEBUGGING
+
+static Boolean KeyDown(const UInt8 theKey)
+{
+ KeyMap map;
+ GetKeys(map);
+ return ((*((UInt8 *)map + (theKey >> 3)) >> (theKey & 7)) & 1) != 0;
+}
+
+static Boolean caps_lock()
+{
+ return KeyDown(0x39);
+}
+
+static void blinkRect(Rect* r)
+{
+ StRegionFromPool oldClip;
+ if (oldClip != NULL)
+ ::GetClip(oldClip);
+
+ ::ClipRect(r);
+ ::InvertRect(r);
+ UInt32 end = ::TickCount() + 5;
+ while (::TickCount() < end) ;
+ ::InvertRect(r);
+
+ if (oldClip != NULL)
+ ::SetClip(oldClip);
+}
+
+static void blinkRgn(RgnHandle rgn)
+{
+ StRegionFromPool oldClip;
+ if (oldClip != NULL)
+ ::GetClip(oldClip);
+
+ ::SetClip(rgn);
+ ::InvertRgn(rgn);
+ UInt32 end = ::TickCount() + 5;
+ while (::TickCount() < end) ;
+ ::InvertRgn(rgn);
+
+ if (oldClip != NULL)
+ ::SetClip(oldClip);
+}
+
+#endif
+
+// Invalidate this component's visible area
+NS_IMETHODIMP nsChildView::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mView || !mVisible)
+ return NS_OK;
+
+ NS_ASSERTION(GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_CLIENT,
+ "Shouldn't need to invalidate with accelerated OMTC layers!");
+
+ if ([NSView focusView]) {
+ // if a view is focussed (i.e. being drawn), then postpone the invalidate so that we
+ // don't lose it.
+ [mView setNeedsPendingDisplayInRect:DevPixelsToCocoaPoints(aRect)];
+ }
+ else {
+ [mView setNeedsDisplayInRect:DevPixelsToCocoaPoints(aRect)];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+nsChildView::WidgetTypeSupportsAcceleration()
+{
+ // Don't use OpenGL for transparent windows or for popup windows.
+ return mView && [[mView window] isOpaque] &&
+ ![[mView window] isKindOfClass:[PopupWindow class]];
+}
+
+bool
+nsChildView::ShouldUseOffMainThreadCompositing()
+{
+ // Don't use OMTC for transparent windows or for popup windows.
+ if (!mView || ![[mView window] isOpaque] ||
+ [[mView window] isKindOfClass:[PopupWindow class]])
+ return false;
+
+ return nsBaseWidget::ShouldUseOffMainThreadCompositing();
+}
+
+inline uint16_t COLOR8TOCOLOR16(uint8_t color8)
+{
+ // return (color8 == 0xFF ? 0xFFFF : (color8 << 8));
+ return (color8 << 8) | color8; /* (color8 * 257) == (color8 * 0x0101) */
+}
+
+#pragma mark -
+
+nsresult nsChildView::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
+{
+ return NS_OK;
+}
+
+// Invokes callback and ProcessEvent methods on Event Listener object
+NS_IMETHODIMP nsChildView::DispatchEvent(WidgetGUIEvent* event,
+ nsEventStatus& aStatus)
+{
+ RefPtr<nsChildView> kungFuDeathGrip(this);
+
+#ifdef DEBUG
+ debug_DumpEvent(stdout, event->mWidget, event, "something", 0);
+#endif
+
+ NS_ASSERTION(!(mTextInputHandler && mTextInputHandler->IsIMEComposing() &&
+ event->HasKeyEventMessage()),
+ "Any key events should not be fired during IME composing");
+
+ if (event->mFlags.mIsSynthesizedForTests) {
+ WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
+ if (keyEvent) {
+ nsresult rv = mTextInputHandler->AttachNativeKeyEvent(*keyEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ aStatus = nsEventStatus_eIgnore;
+
+ nsIWidgetListener* listener = mWidgetListener;
+
+ // If the listener is NULL, check if the parent is a popup. If it is, then
+ // this child is the popup content view attached to a popup. Get the
+ // listener from the parent popup instead.
+ nsCOMPtr<nsIWidget> parentWidget = mParentWidget;
+ if (!listener && parentWidget) {
+ if (parentWidget->WindowType() == eWindowType_popup) {
+ // Check just in case event->mWidget isn't this widget
+ if (event->mWidget) {
+ listener = event->mWidget->GetWidgetListener();
+ }
+ if (!listener) {
+ event->mWidget = parentWidget;
+ listener = parentWidget->GetWidgetListener();
+ }
+ }
+ }
+
+ if (listener)
+ aStatus = listener->HandleEvent(event, mUseAttachedEvents);
+
+ return NS_OK;
+}
+
+bool nsChildView::DispatchWindowEvent(WidgetGUIEvent& event)
+{
+ nsEventStatus status;
+ DispatchEvent(&event, status);
+ return ConvertStatus(status);
+}
+
+nsIWidget*
+nsChildView::GetWidgetForListenerEvents()
+{
+ // If there is no listener, use the parent popup's listener if that exists.
+ if (!mWidgetListener && mParentWidget &&
+ mParentWidget->WindowType() == eWindowType_popup) {
+ return mParentWidget;
+ }
+
+ return this;
+}
+
+void nsChildView::WillPaintWindow()
+{
+ nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
+
+ nsIWidgetListener* listener = widget->GetWidgetListener();
+ if (listener) {
+ listener->WillPaintWindow(widget);
+ }
+}
+
+bool nsChildView::PaintWindow(LayoutDeviceIntRegion aRegion)
+{
+ nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
+
+ nsIWidgetListener* listener = widget->GetWidgetListener();
+ if (!listener)
+ return false;
+
+ bool returnValue = false;
+ bool oldDispatchPaint = mIsDispatchPaint;
+ mIsDispatchPaint = true;
+ returnValue = listener->PaintWindow(widget, aRegion);
+
+ listener = widget->GetWidgetListener();
+ if (listener) {
+ listener->DidPaintWindow();
+ }
+
+ mIsDispatchPaint = oldDispatchPaint;
+ return returnValue;
+}
+
+bool
+nsChildView::PaintWindowInContext(CGContextRef aContext, const LayoutDeviceIntRegion& aRegion, gfx::IntSize aSurfaceSize)
+{
+ if (!mBackingSurface || mBackingSurface->GetSize() != aSurfaceSize) {
+ mBackingSurface =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(aSurfaceSize,
+ gfx::SurfaceFormat::B8G8R8A8);
+ if (!mBackingSurface) {
+ return false;
+ }
+ }
+
+ RefPtr<gfxContext> targetContext = gfxContext::CreateOrNull(mBackingSurface);
+ MOZ_ASSERT(targetContext); // already checked the draw target above
+
+ // Set up the clip region and clear existing contents in the backing surface.
+ targetContext->NewPath();
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const LayoutDeviceIntRect& r = iter.Get();
+ targetContext->Rectangle(gfxRect(r.x, r.y, r.width, r.height));
+ mBackingSurface->ClearRect(gfx::Rect(r.ToUnknownRect()));
+ }
+ targetContext->Clip();
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(mView);
+ bool painted = false;
+ if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ nsBaseWidget::AutoLayerManagerSetup
+ setupLayerManager(this, targetContext, BufferMode::BUFFER_NONE);
+ painted = PaintWindow(aRegion);
+ } else if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
+ // We only need this so that we actually get DidPaintWindow fired
+ painted = PaintWindow(aRegion);
+ }
+
+ uint8_t* data;
+ gfx::IntSize size;
+ int32_t stride;
+ gfx::SurfaceFormat format;
+
+ if (!mBackingSurface->LockBits(&data, &size, &stride, &format)) {
+ return false;
+ }
+
+ // Draw the backing surface onto the window.
+ CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, data, stride * size.height, NULL);
+ NSColorSpace* colorSpace = [[mView window] colorSpace];
+ CGImageRef image = CGImageCreate(size.width, size.height, 8, 32, stride,
+ [colorSpace CGColorSpace],
+ kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst,
+ provider, NULL, false, kCGRenderingIntentDefault);
+ CGContextSaveGState(aContext);
+ CGContextTranslateCTM(aContext, 0, size.height);
+ CGContextScaleCTM(aContext, 1, -1);
+ CGContextSetBlendMode(aContext, kCGBlendModeCopy);
+ CGContextDrawImage(aContext, CGRectMake(0, 0, size.width, size.height), image);
+ CGImageRelease(image);
+ CGDataProviderRelease(provider);
+ CGContextRestoreGState(aContext);
+
+ mBackingSurface->ReleaseBits(data);
+
+ return painted;
+}
+
+#pragma mark -
+
+void nsChildView::ReportMoveEvent()
+{
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+}
+
+void nsChildView::ReportSizeEvent()
+{
+ if (mWidgetListener)
+ mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+}
+
+#pragma mark -
+
+LayoutDeviceIntPoint nsChildView::GetClientOffset()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSPoint origin = [mView convertPoint:NSMakePoint(0, 0) toView:nil];
+ origin.y = [[mView window] frame].size.height - origin.y;
+ return CocoaPointsToDevPixels(origin);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
+}
+
+// Return the offset between this child view and the screen.
+// @return -- widget origin in device-pixel coords
+LayoutDeviceIntPoint nsChildView::WidgetToScreenOffset()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSPoint origin = NSMakePoint(0, 0);
+
+ // 1. First translate view origin point into window coords.
+ // The returned point is in bottom-left coordinates.
+ origin = [mView convertPoint:origin toView:nil];
+
+ // 2. We turn the window-coord rect's origin into screen (still bottom-left) coords.
+ origin = nsCocoaUtils::ConvertPointToScreen([mView window], origin);
+
+ // 3. Since we're dealing in bottom-left coords, we need to make it top-left coords
+ // before we pass it back to Gecko.
+ FlipCocoaScreenCoordinate(origin);
+
+ // convert to device pixels
+ return CocoaPointsToDevPixels(origin);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0,0));
+}
+
+NS_IMETHODIMP nsChildView::SetTitle(const nsAString& title)
+{
+ // child views don't have titles
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsChildView::GetAttention(int32_t aCycleCount)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [NSApp requestUserAttention:NSInformationalRequest];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+/* static */
+bool nsChildView::DoHasPendingInputEvent()
+{
+ return sLastInputEventCount != GetCurrentInputEventCount();
+}
+
+/* static */
+uint32_t nsChildView::GetCurrentInputEventCount()
+{
+ // Can't use kCGAnyInputEventType because that updates too rarely for us (and
+ // always in increments of 30+!) and because apparently it's sort of broken
+ // on Tiger. So just go ahead and query the counters we care about.
+ static const CGEventType eventTypes[] = {
+ kCGEventLeftMouseDown,
+ kCGEventLeftMouseUp,
+ kCGEventRightMouseDown,
+ kCGEventRightMouseUp,
+ kCGEventMouseMoved,
+ kCGEventLeftMouseDragged,
+ kCGEventRightMouseDragged,
+ kCGEventKeyDown,
+ kCGEventKeyUp,
+ kCGEventScrollWheel,
+ kCGEventTabletPointer,
+ kCGEventOtherMouseDown,
+ kCGEventOtherMouseUp,
+ kCGEventOtherMouseDragged
+ };
+
+ uint32_t eventCount = 0;
+ for (uint32_t i = 0; i < ArrayLength(eventTypes); ++i) {
+ eventCount +=
+ CGEventSourceCounterForEventType(kCGEventSourceStateCombinedSessionState,
+ eventTypes[i]);
+ }
+ return eventCount;
+}
+
+/* static */
+void nsChildView::UpdateCurrentInputEventCount()
+{
+ sLastInputEventCount = GetCurrentInputEventCount();
+}
+
+bool nsChildView::HasPendingInputEvent()
+{
+ return DoHasPendingInputEvent();
+}
+
+#pragma mark -
+
+NS_IMETHODIMP
+nsChildView::StartPluginIME(const mozilla::WidgetKeyboardEvent& aKeyboardEvent,
+ int32_t aPanelX, int32_t aPanelY,
+ nsString& aCommitted)
+{
+ NS_ENSURE_TRUE(mView, NS_ERROR_NOT_AVAILABLE);
+
+ ComplexTextInputPanel* ctiPanel =
+ ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+
+ ctiPanel->PlacePanel(aPanelX, aPanelY);
+ // We deliberately don't use TextInputHandler::GetCurrentKeyEvent() to
+ // obtain the NSEvent* we pass to InterpretKeyEvent(). This works fine in
+ // non-e10s mode. But in e10s mode TextInputHandler::HandleKeyDownEvent()
+ // has already returned, so the relevant KeyEventState* (and its NSEvent*)
+ // is already out of scope. Furthermore we don't *need* to use it.
+ // StartPluginIME() is only ever called to start a new IME session when none
+ // currently exists. So nested IME should never reach here, and so it should
+ // be fine to use the last key-down event received by -[ChildView keyDown:]
+ // (as we currently do).
+ ctiPanel->InterpretKeyEvent([(ChildView*)mView lastKeyDownEvent], aCommitted);
+
+ return NS_OK;
+}
+
+void
+nsChildView::SetPluginFocused(bool& aFocused)
+{
+ if (aFocused == mPluginFocused) {
+ return;
+ }
+ if (!aFocused) {
+ ComplexTextInputPanel* ctiPanel =
+ ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+ if (ctiPanel) {
+ ctiPanel->CancelComposition();
+ }
+ }
+ mPluginFocused = aFocused;
+}
+
+NS_IMETHODIMP_(void)
+nsChildView::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ NS_ENSURE_TRUE_VOID(mTextInputHandler);
+
+ if (mTextInputHandler->IsFocused()) {
+ if (aContext.IsPasswordEditor()) {
+ TextInputHandler::EnableSecureEventInput();
+ } else {
+ TextInputHandler::EnsureSecureEventInputDisabled();
+ }
+ }
+
+ mInputContext = aContext;
+ switch (aContext.mIMEState.mEnabled) {
+ case IMEState::ENABLED:
+ case IMEState::PLUGIN:
+ mTextInputHandler->SetASCIICapableOnly(false);
+ mTextInputHandler->EnableIME(true);
+ if (mInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE) {
+ mTextInputHandler->SetIMEOpenState(
+ mInputContext.mIMEState.mOpen == IMEState::OPEN);
+ }
+ break;
+ case IMEState::DISABLED:
+ mTextInputHandler->SetASCIICapableOnly(false);
+ mTextInputHandler->EnableIME(false);
+ break;
+ case IMEState::PASSWORD:
+ mTextInputHandler->SetASCIICapableOnly(true);
+ mTextInputHandler->EnableIME(false);
+ break;
+ default:
+ NS_ERROR("not implemented!");
+ }
+}
+
+NS_IMETHODIMP_(InputContext)
+nsChildView::GetInputContext()
+{
+ switch (mInputContext.mIMEState.mEnabled) {
+ case IMEState::ENABLED:
+ case IMEState::PLUGIN:
+ if (mTextInputHandler) {
+ mInputContext.mIMEState.mOpen =
+ mTextInputHandler->IsIMEOpened() ? IMEState::OPEN : IMEState::CLOSED;
+ break;
+ }
+ // If mTextInputHandler is null, set CLOSED instead...
+ MOZ_FALLTHROUGH;
+ default:
+ mInputContext.mIMEState.mOpen = IMEState::CLOSED;
+ break;
+ }
+ return mInputContext;
+}
+
+NS_IMETHODIMP_(TextEventDispatcherListener*)
+nsChildView::GetNativeTextEventDispatcherListener()
+{
+ if (NS_WARN_IF(!mTextInputHandler)) {
+ return nullptr;
+ }
+ return mTextInputHandler;
+}
+
+NS_IMETHODIMP
+nsChildView::AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent)
+{
+ NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
+ return mTextInputHandler->AttachNativeKeyEvent(aEvent);
+}
+
+bool
+nsChildView::ExecuteNativeKeyBindingRemapped(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ uint32_t aGeckoKeyCode,
+ uint32_t aCocoaKeyCode)
+{
+ NSEvent *originalEvent = reinterpret_cast<NSEvent*>(aEvent.mNativeKeyEvent);
+
+ WidgetKeyboardEvent modifiedEvent(aEvent);
+ modifiedEvent.mKeyCode = aGeckoKeyCode;
+
+ unichar ch = nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aGeckoKeyCode);
+ NSString *chars =
+ [[[NSString alloc] initWithCharacters:&ch length:1] autorelease];
+
+ modifiedEvent.mNativeKeyEvent =
+ [NSEvent keyEventWithType:[originalEvent type]
+ location:[originalEvent locationInWindow]
+ modifierFlags:[originalEvent modifierFlags]
+ timestamp:[originalEvent timestamp]
+ windowNumber:[originalEvent windowNumber]
+ context:[originalEvent context]
+ characters:chars
+ charactersIgnoringModifiers:chars
+ isARepeat:[originalEvent isARepeat]
+ keyCode:aCocoaKeyCode];
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(modifiedEvent, aCallback, aCallbackData);
+}
+
+NS_IMETHODIMP_(bool)
+nsChildView::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ // If the key is a cursor-movement arrow, and the current selection has
+ // vertical writing-mode, we'll remap so that the movement command
+ // generated (in terms of characters/lines) will be appropriate for
+ // the physical direction of the arrow.
+ if (aEvent.mKeyCode >= NS_VK_LEFT && aEvent.mKeyCode <= NS_VK_DOWN) {
+ WidgetQueryContentEvent query(true, eQuerySelectedText, this);
+ DispatchWindowEvent(query);
+
+ if (query.mSucceeded && query.mReply.mWritingMode.IsVertical()) {
+ uint32_t geckoKey = 0;
+ uint32_t cocoaKey = 0;
+
+ switch (aEvent.mKeyCode) {
+ case NS_VK_LEFT:
+ if (query.mReply.mWritingMode.IsVerticalLR()) {
+ geckoKey = NS_VK_UP;
+ cocoaKey = kVK_UpArrow;
+ } else {
+ geckoKey = NS_VK_DOWN;
+ cocoaKey = kVK_DownArrow;
+ }
+ break;
+
+ case NS_VK_RIGHT:
+ if (query.mReply.mWritingMode.IsVerticalLR()) {
+ geckoKey = NS_VK_DOWN;
+ cocoaKey = kVK_DownArrow;
+ } else {
+ geckoKey = NS_VK_UP;
+ cocoaKey = kVK_UpArrow;
+ }
+ break;
+
+ case NS_VK_UP:
+ geckoKey = NS_VK_LEFT;
+ cocoaKey = kVK_LeftArrow;
+ break;
+
+ case NS_VK_DOWN:
+ geckoKey = NS_VK_RIGHT;
+ cocoaKey = kVK_RightArrow;
+ break;
+ }
+
+ return ExecuteNativeKeyBindingRemapped(aType, aEvent, aCallback,
+ aCallbackData,
+ geckoKey, cocoaKey);
+ }
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(aEvent, aCallback, aCallbackData);
+}
+
+nsIMEUpdatePreference
+nsChildView::GetIMEUpdatePreference()
+{
+ // XXX Shouldn't we move floating window which shows composition string
+ // when plugin has focus and its parent is scrolled or the window is
+ // moved?
+ return nsIMEUpdatePreference();
+}
+
+NSView<mozView>* nsChildView::GetEditorView()
+{
+ NSView<mozView>* editorView = mView;
+ // We need to get editor's view. E.g., when the focus is in the bookmark
+ // dialog, the view is <panel> element of the dialog. At this time, the key
+ // events are processed the parent window's view that has native focus.
+ WidgetQueryContentEvent textContent(true, eQueryTextContent, this);
+ textContent.InitForQueryTextContent(0, 0);
+ DispatchWindowEvent(textContent);
+ if (textContent.mSucceeded && textContent.mReply.mFocusedWidget) {
+ NSView<mozView>* view = static_cast<NSView<mozView>*>(
+ textContent.mReply.mFocusedWidget->GetNativeData(NS_NATIVE_WIDGET));
+ if (view)
+ editorView = view;
+ }
+ return editorView;
+}
+
+#pragma mark -
+
+void
+nsChildView::CreateCompositor()
+{
+ nsBaseWidget::CreateCompositor();
+ if (mCompositorBridgeChild) {
+ [(ChildView *)mView setUsingOMTCompositor:true];
+ }
+}
+
+void
+nsChildView::ConfigureAPZCTreeManager()
+{
+ nsBaseWidget::ConfigureAPZCTreeManager();
+
+ if (gfxPrefs::AsyncPanZoomSeparateEventThread()) {
+ if (gNumberOfWidgetsNeedingEventThread == 0) {
+ [EventThreadRunner start];
+ }
+ gNumberOfWidgetsNeedingEventThread++;
+ }
+}
+
+void
+nsChildView::ConfigureAPZControllerThread()
+{
+ if (gfxPrefs::AsyncPanZoomSeparateEventThread()) {
+ // The EventThreadRunner is the controller thread, but it doesn't
+ // have a MessageLoop.
+ APZThreadUtils::SetControllerThread(nullptr);
+ } else {
+ nsBaseWidget::ConfigureAPZControllerThread();
+ }
+}
+
+LayoutDeviceIntRect
+nsChildView::RectContainingTitlebarControls()
+{
+ // Start with a thin strip at the top of the window for the highlight line.
+ NSRect rect = NSMakeRect(0, 0, [mView bounds].size.width,
+ [(ChildView*)mView cornerRadius]);
+
+ // If we draw the titlebar title string, increase the height to the default
+ // titlebar height. This height does not necessarily include all the titlebar
+ // controls because we may have moved them further down, but at least it will
+ // include the whole title text.
+ BaseWindow* window = (BaseWindow*)[mView window];
+ if ([window wantsTitleDrawn] && [window isKindOfClass:[ToolbarWindow class]]) {
+ CGFloat defaultTitlebarHeight = [(ToolbarWindow*)window titlebarHeight];
+ rect.size.height = std::max(rect.size.height, defaultTitlebarHeight);
+ }
+
+ // Add the rects of the titlebar controls.
+ for (id view in [window titlebarControls]) {
+ rect = NSUnionRect(rect, [mView convertRect:[view bounds] fromView:view]);
+ }
+ return CocoaPointsToDevPixels(rect);
+}
+
+void
+nsChildView::PrepareWindowEffects()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ bool canBeOpaque;
+ {
+ MutexAutoLock lock(mEffectsLock);
+ mShowsResizeIndicator = ShowsResizeIndicator(&mResizeIndicatorRect);
+ mHasRoundedBottomCorners = [(ChildView*)mView hasRoundedBottomCorners];
+ CGFloat cornerRadius = [(ChildView*)mView cornerRadius];
+ mDevPixelCornerRadius = cornerRadius * BackingScaleFactor();
+ mIsCoveringTitlebar = [(ChildView*)mView isCoveringTitlebar];
+ NSInteger styleMask = [[mView window] styleMask];
+ bool wasFullscreen = mIsFullscreen;
+ mIsFullscreen = (styleMask & NSFullScreenWindowMask) || !(styleMask & NSTitledWindowMask);
+
+ canBeOpaque = mIsFullscreen && wasFullscreen;
+ if (canBeOpaque && VibrancyManager::SystemSupportsVibrancy()) {
+ canBeOpaque = !EnsureVibrancyManager().HasVibrantRegions();
+ }
+ if (mIsCoveringTitlebar) {
+ mTitlebarRect = RectContainingTitlebarControls();
+ UpdateTitlebarCGContext();
+ }
+ }
+
+ // If we've just transitioned into or out of full screen then update the opacity on our GLContext.
+ if (canBeOpaque != mIsOpaque) {
+ mIsOpaque = canBeOpaque;
+ [(ChildView*)mView setGLOpaque:canBeOpaque];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsChildView::CleanupWindowEffects()
+{
+ mResizerImage = nullptr;
+ mCornerMaskImage = nullptr;
+ mTitlebarImage = nullptr;
+}
+
+bool
+nsChildView::PreRender(WidgetRenderingContext* aContext)
+{
+ UniquePtr<GLManager> manager(GLManager::CreateGLManager(aContext->mLayerManager));
+ if (!manager) {
+ return true;
+ }
+
+ // The lock makes sure that we don't attempt to tear down the view while
+ // compositing. That would make us unable to call postRender on it when the
+ // composition is done, thus keeping the GL context locked forever.
+ mViewTearDownLock.Lock();
+
+ NSOpenGLContext *glContext = GLContextCGL::Cast(manager->gl())->GetNSOpenGLContext();
+
+ if (![(ChildView*)mView preRender:glContext]) {
+ mViewTearDownLock.Unlock();
+ return false;
+ }
+ return true;
+}
+
+void
+nsChildView::PostRender(WidgetRenderingContext* aContext)
+{
+ UniquePtr<GLManager> manager(GLManager::CreateGLManager(aContext->mLayerManager));
+ if (!manager) {
+ return;
+ }
+ NSOpenGLContext *glContext = GLContextCGL::Cast(manager->gl())->GetNSOpenGLContext();
+ [(ChildView*)mView postRender:glContext];
+ mViewTearDownLock.Unlock();
+}
+
+void
+nsChildView::DrawWindowOverlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+{
+ mozilla::UniquePtr<GLManager> manager(GLManager::CreateGLManager(aContext->mLayerManager));
+ if (manager) {
+ DrawWindowOverlay(manager.get(), aRect);
+ }
+}
+
+void
+nsChildView::DrawWindowOverlay(GLManager* aManager, LayoutDeviceIntRect aRect)
+{
+ GLContext* gl = aManager->gl();
+ ScopedGLState scopedScissorTestState(gl, LOCAL_GL_SCISSOR_TEST, false);
+
+ MaybeDrawTitlebar(aManager);
+ MaybeDrawResizeIndicator(aManager);
+ MaybeDrawRoundedCorners(aManager, aRect);
+}
+
+static void
+ClearRegion(gfx::DrawTarget *aDT, LayoutDeviceIntRegion aRegion)
+{
+ gfxUtils::ClipToRegion(aDT, aRegion.ToUnknownRegion());
+ aDT->ClearRect(gfx::Rect(0, 0, aDT->GetSize().width, aDT->GetSize().height));
+ aDT->PopClip();
+}
+
+static void
+DrawResizer(CGContextRef aCtx)
+{
+ CGContextSetShouldAntialias(aCtx, false);
+ CGPoint points[6];
+ points[0] = CGPointMake(13.0f, 4.0f);
+ points[1] = CGPointMake(3.0f, 14.0f);
+ points[2] = CGPointMake(13.0f, 8.0f);
+ points[3] = CGPointMake(7.0f, 14.0f);
+ points[4] = CGPointMake(13.0f, 12.0f);
+ points[5] = CGPointMake(11.0f, 14.0f);
+ CGContextSetRGBStrokeColor(aCtx, 0.00f, 0.00f, 0.00f, 0.15f);
+ CGContextStrokeLineSegments(aCtx, points, 6);
+
+ points[0] = CGPointMake(13.0f, 5.0f);
+ points[1] = CGPointMake(4.0f, 14.0f);
+ points[2] = CGPointMake(13.0f, 9.0f);
+ points[3] = CGPointMake(8.0f, 14.0f);
+ points[4] = CGPointMake(13.0f, 13.0f);
+ points[5] = CGPointMake(12.0f, 14.0f);
+ CGContextSetRGBStrokeColor(aCtx, 0.13f, 0.13f, 0.13f, 0.54f);
+ CGContextStrokeLineSegments(aCtx, points, 6);
+
+ points[0] = CGPointMake(13.0f, 6.0f);
+ points[1] = CGPointMake(5.0f, 14.0f);
+ points[2] = CGPointMake(13.0f, 10.0f);
+ points[3] = CGPointMake(9.0f, 14.0f);
+ points[5] = CGPointMake(13.0f, 13.9f);
+ points[4] = CGPointMake(13.0f, 14.0f);
+ CGContextSetRGBStrokeColor(aCtx, 0.84f, 0.84f, 0.84f, 0.55f);
+ CGContextStrokeLineSegments(aCtx, points, 6);
+}
+
+void
+nsChildView::MaybeDrawResizeIndicator(GLManager* aManager)
+{
+ MutexAutoLock lock(mEffectsLock);
+ if (!mShowsResizeIndicator) {
+ return;
+ }
+
+ if (!mResizerImage) {
+ mResizerImage = MakeUnique<RectTextureImage>();
+ }
+
+ LayoutDeviceIntSize size = mResizeIndicatorRect.Size();
+ mResizerImage->UpdateIfNeeded(size, LayoutDeviceIntRegion(), ^(gfx::DrawTarget* drawTarget, const LayoutDeviceIntRegion& updateRegion) {
+ ClearRegion(drawTarget, updateRegion);
+ gfx::BorrowedCGContext borrow(drawTarget);
+ DrawResizer(borrow.cg);
+ borrow.Finish();
+ });
+
+ mResizerImage->Draw(aManager, mResizeIndicatorRect.TopLeft());
+}
+
+// Draw the highlight line at the top of the titlebar.
+// This function draws into the current NSGraphicsContext and assumes flippedness.
+static void
+DrawTitlebarHighlight(NSSize aWindowSize, CGFloat aRadius, CGFloat aDevicePixelWidth)
+{
+ [NSGraphicsContext saveGraphicsState];
+
+ // Set up the clip path. We start with the outer rectangle and cut out a
+ // slightly smaller inner rectangle with rounded corners.
+ // The outer corners of the resulting path will be square, but they will be
+ // masked away in a later step.
+ NSBezierPath* path = [NSBezierPath bezierPath];
+ [path setWindingRule:NSEvenOddWindingRule];
+ NSRect pathRect = NSMakeRect(0, 0, aWindowSize.width, aRadius + 2);
+ [path appendBezierPathWithRect:pathRect];
+ pathRect = NSInsetRect(pathRect, aDevicePixelWidth, aDevicePixelWidth);
+ CGFloat innerRadius = aRadius - aDevicePixelWidth;
+ [path appendBezierPathWithRoundedRect:pathRect xRadius:innerRadius yRadius:innerRadius];
+ [path addClip];
+
+ // Now we fill the path with a subtle highlight gradient.
+ // We don't use NSGradient because it's 5x to 15x slower than the manual fill,
+ // as indicated by the performance test in bug 880620.
+ for (CGFloat y = 0; y < aRadius; y += aDevicePixelWidth) {
+ CGFloat t = y / aRadius;
+ [[NSColor colorWithDeviceWhite:1.0 alpha:0.4 * (1.0 - t)] set];
+ NSRectFillUsingOperation(NSMakeRect(0, y, aWindowSize.width, aDevicePixelWidth), NSCompositeSourceOver);
+ }
+
+ [NSGraphicsContext restoreGraphicsState];
+}
+
+static CGContextRef
+CreateCGContext(const LayoutDeviceIntSize& aSize)
+{
+ CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
+ CGContextRef ctx =
+ CGBitmapContextCreate(NULL,
+ aSize.width,
+ aSize.height,
+ 8 /* bitsPerComponent */,
+ aSize.width * 4,
+ cs,
+ kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(cs);
+
+ CGContextTranslateCTM(ctx, 0, aSize.height);
+ CGContextScaleCTM(ctx, 1, -1);
+ CGContextSetInterpolationQuality(ctx, kCGInterpolationLow);
+
+ return ctx;
+}
+
+LayoutDeviceIntSize
+TextureSizeForSize(const LayoutDeviceIntSize& aSize)
+{
+ return LayoutDeviceIntSize(RoundUpPow2(aSize.width),
+ RoundUpPow2(aSize.height));
+}
+
+// When this method is entered, mEffectsLock is already being held.
+void
+nsChildView::UpdateTitlebarCGContext()
+{
+ if (mTitlebarRect.IsEmpty()) {
+ ReleaseTitlebarCGContext();
+ return;
+ }
+
+ NSRect titlebarRect = DevPixelsToCocoaPoints(mTitlebarRect);
+ NSRect dirtyRect = [mView convertRect:[(BaseWindow*)[mView window] getAndResetNativeDirtyRect] fromView:nil];
+ NSRect dirtyTitlebarRect = NSIntersectionRect(titlebarRect, dirtyRect);
+
+ LayoutDeviceIntSize texSize = TextureSizeForSize(mTitlebarRect.Size());
+ if (!mTitlebarCGContext ||
+ CGBitmapContextGetWidth(mTitlebarCGContext) != size_t(texSize.width) ||
+ CGBitmapContextGetHeight(mTitlebarCGContext) != size_t(texSize.height)) {
+ dirtyTitlebarRect = titlebarRect;
+
+ ReleaseTitlebarCGContext();
+
+ mTitlebarCGContext = CreateCGContext(texSize);
+ }
+
+ if (NSIsEmptyRect(dirtyTitlebarRect)) {
+ return;
+ }
+
+ CGContextRef ctx = mTitlebarCGContext;
+
+ CGContextSaveGState(ctx);
+
+ double scale = BackingScaleFactor();
+ CGContextScaleCTM(ctx, scale, scale);
+
+ CGContextClipToRect(ctx, NSRectToCGRect(dirtyTitlebarRect));
+ CGContextClearRect(ctx, NSRectToCGRect(dirtyTitlebarRect));
+
+ NSGraphicsContext* oldContext = [NSGraphicsContext currentContext];
+
+ CGContextSaveGState(ctx);
+
+ BaseWindow* window = (BaseWindow*)[mView window];
+ NSView* frameView = [[window contentView] superview];
+ if (![frameView isFlipped]) {
+ CGContextTranslateCTM(ctx, 0, [frameView bounds].size.height);
+ CGContextScaleCTM(ctx, 1, -1);
+ }
+ NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[frameView isFlipped]];
+ [NSGraphicsContext setCurrentContext:context];
+
+ // Draw the title string.
+ if ([window wantsTitleDrawn] && [frameView respondsToSelector:@selector(_drawTitleBar:)]) {
+ [frameView _drawTitleBar:[frameView bounds]];
+ }
+
+ // Draw the titlebar controls into the titlebar image.
+ for (id view in [window titlebarControls]) {
+ NSRect viewFrame = [view frame];
+ NSRect viewRect = [mView convertRect:viewFrame fromView:frameView];
+ if (!NSIntersectsRect(dirtyTitlebarRect, viewRect)) {
+ continue;
+ }
+ // All of the titlebar controls we're interested in are subclasses of
+ // NSButton.
+ if (![view isKindOfClass:[NSButton class]]) {
+ continue;
+ }
+ NSButton *button = (NSButton *) view;
+ id cellObject = [button cell];
+ if (![cellObject isKindOfClass:[NSCell class]]) {
+ continue;
+ }
+ NSCell *cell = (NSCell *) cellObject;
+
+ CGContextSaveGState(ctx);
+ CGContextTranslateCTM(ctx, viewFrame.origin.x, viewFrame.origin.y);
+
+ if ([context isFlipped] != [view isFlipped]) {
+ CGContextTranslateCTM(ctx, 0, viewFrame.size.height);
+ CGContextScaleCTM(ctx, 1, -1);
+ }
+
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[view isFlipped]]];
+
+ if ([window useBrightTitlebarForeground] && !nsCocoaFeatures::OnYosemiteOrLater() &&
+ view == [window standardWindowButton:NSWindowFullScreenButton]) {
+ // Make the fullscreen button visible on dark titlebar backgrounds by
+ // drawing it into a new transparency layer and turning it white.
+ CGRect r = NSRectToCGRect([view bounds]);
+ CGContextBeginTransparencyLayerWithRect(ctx, r, nullptr);
+
+ // Draw twice for double opacity.
+ [cell drawWithFrame:[button bounds] inView:button];
+ [cell drawWithFrame:[button bounds] inView:button];
+
+ // Make it white.
+ CGContextSetBlendMode(ctx, kCGBlendModeSourceIn);
+ CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
+ CGContextFillRect(ctx, r);
+ CGContextSetBlendMode(ctx, kCGBlendModeNormal);
+
+ CGContextEndTransparencyLayer(ctx);
+ } else {
+ [cell drawWithFrame:[button bounds] inView:button];
+ }
+
+ [NSGraphicsContext setCurrentContext:context];
+ CGContextRestoreGState(ctx);
+ }
+
+ CGContextRestoreGState(ctx);
+
+ DrawTitlebarHighlight([frameView bounds].size, [(ChildView*)mView cornerRadius],
+ DevPixelsToCocoaPoints(1));
+
+ [NSGraphicsContext setCurrentContext:oldContext];
+
+ CGContextRestoreGState(ctx);
+
+ mUpdatedTitlebarRegion.OrWith(CocoaPointsToDevPixels(dirtyTitlebarRect));
+}
+
+// This method draws an overlay in the top of the window which contains the
+// titlebar controls (e.g. close, min, zoom, fullscreen) and the titlebar
+// highlight effect.
+// This is necessary because the real titlebar controls are covered by our
+// OpenGL context. Note that in terms of the NSView hierarchy, our ChildView
+// is actually below the titlebar controls - that's why hovering and clicking
+// them works as expected - but their visual representation is only drawn into
+// the normal window buffer, and the window buffer surface lies below the
+// GLContext surface. In order to make the titlebar controls visible, we have
+// to redraw them inside the OpenGL context surface.
+void
+nsChildView::MaybeDrawTitlebar(GLManager* aManager)
+{
+ MutexAutoLock lock(mEffectsLock);
+ if (!mIsCoveringTitlebar || mIsFullscreen) {
+ return;
+ }
+
+ LayoutDeviceIntRegion updatedTitlebarRegion;
+ updatedTitlebarRegion.And(mUpdatedTitlebarRegion, mTitlebarRect);
+ mUpdatedTitlebarRegion.SetEmpty();
+
+ if (!mTitlebarImage) {
+ mTitlebarImage = MakeUnique<RectTextureImage>();
+ }
+
+ mTitlebarImage->UpdateFromCGContext(mTitlebarRect.Size(),
+ updatedTitlebarRegion,
+ mTitlebarCGContext);
+
+ mTitlebarImage->Draw(aManager, mTitlebarRect.TopLeft());
+}
+
+static void
+DrawTopLeftCornerMask(CGContextRef aCtx, int aRadius)
+{
+ CGContextSetRGBFillColor(aCtx, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillEllipseInRect(aCtx, CGRectMake(0, 0, aRadius * 2, aRadius * 2));
+}
+
+void
+nsChildView::MaybeDrawRoundedCorners(GLManager* aManager,
+ const LayoutDeviceIntRect& aRect)
+{
+ MutexAutoLock lock(mEffectsLock);
+
+ if (!mCornerMaskImage) {
+ mCornerMaskImage = MakeUnique<RectTextureImage>();
+ }
+
+ LayoutDeviceIntSize size(mDevPixelCornerRadius, mDevPixelCornerRadius);
+ mCornerMaskImage->UpdateIfNeeded(size, LayoutDeviceIntRegion(), ^(gfx::DrawTarget* drawTarget, const LayoutDeviceIntRegion& updateRegion) {
+ ClearRegion(drawTarget, updateRegion);
+ RefPtr<gfx::PathBuilder> builder = drawTarget->CreatePathBuilder();
+ builder->Arc(gfx::Point(mDevPixelCornerRadius, mDevPixelCornerRadius), mDevPixelCornerRadius, 0, 2.0f * M_PI);
+ RefPtr<gfx::Path> path = builder->Finish();
+ drawTarget->Fill(path,
+ gfx::ColorPattern(gfx::Color(1.0, 1.0, 1.0, 1.0)),
+ gfx::DrawOptions(1.0f, gfx::CompositionOp::OP_SOURCE));
+ });
+
+ // Use operator destination in: multiply all 4 channels with source alpha.
+ aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA,
+ LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA);
+
+ Matrix4x4 flipX = Matrix4x4::Scaling(-1, 1, 1);
+ Matrix4x4 flipY = Matrix4x4::Scaling(1, -1, 1);
+
+ if (mIsCoveringTitlebar && !mIsFullscreen) {
+ // Mask the top corners.
+ mCornerMaskImage->Draw(aManager, aRect.TopLeft());
+ mCornerMaskImage->Draw(aManager, aRect.TopRight(), flipX);
+ }
+
+ if (mHasRoundedBottomCorners && !mIsFullscreen) {
+ // Mask the bottom corners.
+ mCornerMaskImage->Draw(aManager, aRect.BottomLeft(), flipY);
+ mCornerMaskImage->Draw(aManager, aRect.BottomRight(), flipY * flipX);
+ }
+
+ // Reset blend mode.
+ aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
+ LOCAL_GL_ONE, LOCAL_GL_ONE);
+}
+
+static int32_t
+FindTitlebarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ int32_t aWindowWidth)
+{
+ int32_t titlebarBottom = 0;
+ for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if ((g.mType == nsNativeThemeCocoa::eThemeGeometryTypeTitlebar) &&
+ g.mRect.X() <= 0 &&
+ g.mRect.XMost() >= aWindowWidth &&
+ g.mRect.Y() <= 0) {
+ titlebarBottom = std::max(titlebarBottom, g.mRect.YMost());
+ }
+ }
+ return titlebarBottom;
+}
+
+static int32_t
+FindUnifiedToolbarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ int32_t aWindowWidth, int32_t aTitlebarBottom)
+{
+ int32_t unifiedToolbarBottom = aTitlebarBottom;
+ for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if ((g.mType == nsNativeThemeCocoa::eThemeGeometryTypeToolbar) &&
+ g.mRect.X() <= 0 &&
+ g.mRect.XMost() >= aWindowWidth &&
+ g.mRect.Y() <= aTitlebarBottom) {
+ unifiedToolbarBottom = std::max(unifiedToolbarBottom, g.mRect.YMost());
+ }
+ }
+ return unifiedToolbarBottom;
+}
+
+static LayoutDeviceIntRect
+FindFirstRectOfType(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if (g.mType == aThemeGeometryType) {
+ return g.mRect;
+ }
+ }
+ return LayoutDeviceIntRect();
+}
+
+void
+nsChildView::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries)
+{
+ if (![mView window])
+ return;
+
+ UpdateVibrancy(aThemeGeometries);
+
+ if (![[mView window] isKindOfClass:[ToolbarWindow class]])
+ return;
+
+ // Update unified toolbar height and sheet attachment position.
+ int32_t windowWidth = mBounds.width;
+ int32_t titlebarBottom = FindTitlebarBottom(aThemeGeometries, windowWidth);
+ int32_t unifiedToolbarBottom =
+ FindUnifiedToolbarBottom(aThemeGeometries, windowWidth, titlebarBottom);
+ int32_t toolboxBottom =
+ FindFirstRectOfType(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeToolbox).YMost();
+
+ ToolbarWindow* win = (ToolbarWindow*)[mView window];
+ bool drawsContentsIntoWindowFrame = [win drawsContentsIntoWindowFrame];
+ int32_t titlebarHeight = CocoaPointsToDevPixels([win titlebarHeight]);
+ int32_t contentOffset = drawsContentsIntoWindowFrame ? titlebarHeight : 0;
+ int32_t devUnifiedHeight = titlebarHeight + unifiedToolbarBottom - contentOffset;
+ [win setUnifiedToolbarHeight:DevPixelsToCocoaPoints(devUnifiedHeight)];
+ int32_t devSheetPosition = titlebarHeight + std::max(toolboxBottom, unifiedToolbarBottom) - contentOffset;
+ [win setSheetAttachmentPosition:DevPixelsToCocoaPoints(devSheetPosition)];
+
+ // Update titlebar control offsets.
+ LayoutDeviceIntRect windowButtonRect = FindFirstRectOfType(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeWindowButtons);
+ [win placeWindowButtons:[mView convertRect:DevPixelsToCocoaPoints(windowButtonRect) toView:nil]];
+ LayoutDeviceIntRect fullScreenButtonRect = FindFirstRectOfType(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeFullscreenButton);
+ [win placeFullScreenButton:[mView convertRect:DevPixelsToCocoaPoints(fullScreenButtonRect) toView:nil]];
+}
+
+static LayoutDeviceIntRegion
+GatherThemeGeometryRegion(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ LayoutDeviceIntRegion region;
+ for (size_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if (g.mType == aThemeGeometryType) {
+ region.OrWith(g.mRect);
+ }
+ }
+ return region;
+}
+
+template<typename Region>
+static void MakeRegionsNonOverlappingImpl(Region& aOutUnion) { }
+
+template<typename Region, typename ... Regions>
+static void MakeRegionsNonOverlappingImpl(Region& aOutUnion, Region& aFirst, Regions& ... aRest)
+{
+ MakeRegionsNonOverlappingImpl(aOutUnion, aRest...);
+ aFirst.SubOut(aOutUnion);
+ aOutUnion.OrWith(aFirst);
+}
+
+// Subtracts parts from regions in such a way that they don't have any overlap.
+// Each region in the argument list will have the union of all the regions
+// *following* it subtracted from itself. In other words, the arguments are
+// sorted low priority to high priority.
+template<typename Region, typename ... Regions>
+static void MakeRegionsNonOverlapping(Region& aFirst, Regions& ... aRest)
+{
+ Region unionOfAll;
+ MakeRegionsNonOverlappingImpl(unionOfAll, aFirst, aRest...);
+}
+
+void
+nsChildView::UpdateVibrancy(const nsTArray<ThemeGeometry>& aThemeGeometries)
+{
+ if (!VibrancyManager::SystemSupportsVibrancy()) {
+ return;
+ }
+
+ LayoutDeviceIntRegion sheetRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeSheet);
+ LayoutDeviceIntRegion vibrantLightRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeVibrancyLight);
+ LayoutDeviceIntRegion vibrantDarkRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeVibrancyDark);
+ LayoutDeviceIntRegion menuRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeMenu);
+ LayoutDeviceIntRegion tooltipRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeTooltip);
+ LayoutDeviceIntRegion highlightedMenuItemRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeHighlightedMenuItem);
+ LayoutDeviceIntRegion sourceListRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeSourceList);
+ LayoutDeviceIntRegion sourceListSelectionRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeSourceListSelection);
+ LayoutDeviceIntRegion activeSourceListSelectionRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeActiveSourceListSelection);
+
+ MakeRegionsNonOverlapping(sheetRegion, vibrantLightRegion, vibrantDarkRegion,
+ menuRegion, tooltipRegion, highlightedMenuItemRegion,
+ sourceListRegion, sourceListSelectionRegion,
+ activeSourceListSelectionRegion);
+
+ auto& vm = EnsureVibrancyManager();
+ vm.UpdateVibrantRegion(VibrancyType::LIGHT, vibrantLightRegion);
+ vm.UpdateVibrantRegion(VibrancyType::TOOLTIP, tooltipRegion);
+ vm.UpdateVibrantRegion(VibrancyType::MENU, menuRegion);
+ vm.UpdateVibrantRegion(VibrancyType::HIGHLIGHTED_MENUITEM, highlightedMenuItemRegion);
+ vm.UpdateVibrantRegion(VibrancyType::SHEET, sheetRegion);
+ vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST, sourceListRegion);
+ vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST_SELECTION, sourceListSelectionRegion);
+ vm.UpdateVibrantRegion(VibrancyType::ACTIVE_SOURCE_LIST_SELECTION, activeSourceListSelectionRegion);
+ vm.UpdateVibrantRegion(VibrancyType::DARK, vibrantDarkRegion);
+}
+
+void
+nsChildView::ClearVibrantAreas()
+{
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ EnsureVibrancyManager().ClearVibrantAreas();
+ }
+}
+
+static VibrancyType
+ThemeGeometryTypeToVibrancyType(nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ switch (aThemeGeometryType) {
+ case nsNativeThemeCocoa::eThemeGeometryTypeVibrancyLight:
+ return VibrancyType::LIGHT;
+ case nsNativeThemeCocoa::eThemeGeometryTypeVibrancyDark:
+ return VibrancyType::DARK;
+ case nsNativeThemeCocoa::eThemeGeometryTypeTooltip:
+ return VibrancyType::TOOLTIP;
+ case nsNativeThemeCocoa::eThemeGeometryTypeMenu:
+ return VibrancyType::MENU;
+ case nsNativeThemeCocoa::eThemeGeometryTypeHighlightedMenuItem:
+ return VibrancyType::HIGHLIGHTED_MENUITEM;
+ case nsNativeThemeCocoa::eThemeGeometryTypeSheet:
+ return VibrancyType::SHEET;
+ case nsNativeThemeCocoa::eThemeGeometryTypeSourceList:
+ return VibrancyType::SOURCE_LIST;
+ case nsNativeThemeCocoa::eThemeGeometryTypeSourceListSelection:
+ return VibrancyType::SOURCE_LIST_SELECTION;
+ case nsNativeThemeCocoa::eThemeGeometryTypeActiveSourceListSelection:
+ return VibrancyType::ACTIVE_SOURCE_LIST_SELECTION;
+ default:
+ MOZ_CRASH();
+ }
+}
+
+NSColor*
+nsChildView::VibrancyFillColorForThemeGeometryType(nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ return EnsureVibrancyManager().VibrancyFillColorForType(
+ ThemeGeometryTypeToVibrancyType(aThemeGeometryType));
+ }
+ return [NSColor whiteColor];
+}
+
+NSColor*
+nsChildView::VibrancyFontSmoothingBackgroundColorForThemeGeometryType(nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ return EnsureVibrancyManager().VibrancyFontSmoothingBackgroundColorForType(
+ ThemeGeometryTypeToVibrancyType(aThemeGeometryType));
+ }
+ return [NSColor clearColor];
+}
+
+mozilla::VibrancyManager&
+nsChildView::EnsureVibrancyManager()
+{
+ MOZ_ASSERT(mView, "Only call this once we have a view!");
+ if (!mVibrancyManager) {
+ mVibrancyManager = MakeUnique<VibrancyManager>(*this, mView);
+ }
+ return *mVibrancyManager;
+}
+
+nsChildView::SwipeInfo
+nsChildView::SendMayStartSwipe(const mozilla::PanGestureInput& aSwipeStartEvent)
+{
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ uint32_t direction = (aSwipeStartEvent.mPanDisplacement.x > 0.0)
+ ? (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_RIGHT
+ : (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_LEFT;
+
+ // We're ready to start the animation. Tell Gecko about it, and at the same
+ // time ask it if it really wants to start an animation for this event.
+ // This event also reports back the directions that we can swipe in.
+ LayoutDeviceIntPoint position =
+ RoundedToInt(aSwipeStartEvent.mPanStartPoint * ScreenToLayoutDeviceScale(1));
+ WidgetSimpleGestureEvent geckoEvent =
+ SwipeTracker::CreateSwipeGestureEvent(eSwipeGestureMayStart, this,
+ position);
+ geckoEvent.mDirection = direction;
+ geckoEvent.mDelta = 0.0;
+ geckoEvent.mAllowedDirections = 0;
+ bool shouldStartSwipe = DispatchWindowEvent(geckoEvent); // event cancelled == swipe should start
+
+ SwipeInfo result = { shouldStartSwipe, geckoEvent.mAllowedDirections };
+ return result;
+}
+
+void
+nsChildView::TrackScrollEventAsSwipe(const mozilla::PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections)
+{
+ // If a swipe is currently being tracked kill it -- it's been interrupted
+ // by another gesture event.
+ if (mSwipeTracker) {
+ mSwipeTracker->CancelSwipe();
+ mSwipeTracker->Destroy();
+ mSwipeTracker = nullptr;
+ }
+
+ uint32_t direction = (aSwipeStartEvent.mPanDisplacement.x > 0.0)
+ ? (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_RIGHT
+ : (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_LEFT;
+
+ mSwipeTracker = new SwipeTracker(*this, aSwipeStartEvent,
+ aAllowedDirections, direction);
+
+ if (!mAPZC) {
+ mCurrentPanGestureBelongsToSwipe = true;
+ }
+}
+
+void
+nsChildView::SwipeFinished()
+{
+ mSwipeTracker = nullptr;
+}
+
+already_AddRefed<gfx::DrawTarget>
+nsChildView::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ BufferMode* aBufferMode)
+{
+ // should have created the GLPresenter in InitCompositor.
+ MOZ_ASSERT(mGLPresenter);
+ if (!mGLPresenter) {
+ mGLPresenter = GLPresenter::CreateForWindow(this);
+
+ if (!mGLPresenter) {
+ return nullptr;
+ }
+ }
+
+ LayoutDeviceIntRegion dirtyRegion(aInvalidRegion);
+ LayoutDeviceIntSize renderSize = mBounds.Size();
+
+ if (!mBasicCompositorImage) {
+ mBasicCompositorImage = MakeUnique<RectTextureImage>();
+ }
+
+ RefPtr<gfx::DrawTarget> drawTarget =
+ mBasicCompositorImage->BeginUpdate(renderSize, dirtyRegion);
+
+ if (!drawTarget) {
+ // Composite unchanged textures.
+ DoRemoteComposition(mBounds);
+ return nullptr;
+ }
+
+ aInvalidRegion = mBasicCompositorImage->GetUpdateRegion();
+ *aBufferMode = BufferMode::BUFFER_NONE;
+
+ return drawTarget.forget();
+}
+
+void
+nsChildView::EndRemoteDrawing()
+{
+ mBasicCompositorImage->EndUpdate();
+ DoRemoteComposition(mBounds);
+}
+
+void
+nsChildView::CleanupRemoteDrawing()
+{
+ mBasicCompositorImage = nullptr;
+ mCornerMaskImage = nullptr;
+ mResizerImage = nullptr;
+ mTitlebarImage = nullptr;
+ mGLPresenter = nullptr;
+}
+
+bool
+nsChildView::InitCompositor(Compositor* aCompositor)
+{
+ if (aCompositor->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ if (!mGLPresenter) {
+ mGLPresenter = GLPresenter::CreateForWindow(this);
+ }
+
+ return !!mGLPresenter;
+ }
+ return true;
+}
+
+void
+nsChildView::DoRemoteComposition(const LayoutDeviceIntRect& aRenderRect)
+{
+ if (![(ChildView*)mView preRender:mGLPresenter->GetNSOpenGLContext()]) {
+ return;
+ }
+ mGLPresenter->BeginFrame(aRenderRect.Size());
+
+ // Draw the result from the basic compositor.
+ mBasicCompositorImage->Draw(mGLPresenter.get(), LayoutDeviceIntPoint(0, 0));
+
+ // DrawWindowOverlay doesn't do anything for non-GL, so it didn't paint
+ // anything during the basic compositor transaction. Draw the overlay now.
+ DrawWindowOverlay(mGLPresenter.get(), aRenderRect);
+
+ mGLPresenter->EndFrame();
+
+ [(ChildView*)mView postRender:mGLPresenter->GetNSOpenGLContext()];
+}
+
+@interface NonDraggableView : NSView
+@end
+
+@implementation NonDraggableView
+- (BOOL)mouseDownCanMoveWindow { return NO; }
+- (NSView*)hitTest:(NSPoint)aPoint { return nil; }
+@end
+
+void
+nsChildView::UpdateWindowDraggingRegion(const LayoutDeviceIntRegion& aRegion)
+{
+ // mView returns YES from mouseDownCanMoveWindow, so we need to put NSViews
+ // that return NO from mouseDownCanMoveWindow in the places that shouldn't
+ // be draggable. We can't do it the other way round because returning
+ // YES from mouseDownCanMoveWindow doesn't have any effect if there's a
+ // superview that returns NO.
+ LayoutDeviceIntRegion nonDraggable;
+ nonDraggable.Sub(LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height), aRegion);
+
+ __block bool changed = false;
+
+ // Suppress calls to setNeedsDisplay during NSView geometry changes.
+ ManipulateViewWithoutNeedingDisplay(mView, ^() {
+ changed = mNonDraggableRegion.UpdateRegion(nonDraggable, *this, mView, ^() {
+ return [[NonDraggableView alloc] initWithFrame:NSZeroRect];
+ });
+ });
+
+ if (changed) {
+ // Trigger an update to the window server. This will call
+ // mouseDownCanMoveWindow.
+ // Doing this manually is only necessary because we're suppressing
+ // setNeedsDisplay calls above.
+ [[mView window] setMovableByWindowBackground:NO];
+ [[mView window] setMovableByWindowBackground:YES];
+ }
+}
+
+void
+nsChildView::ReportSwipeStarted(uint64_t aInputBlockId,
+ bool aStartSwipe)
+{
+ if (mSwipeEventQueue && mSwipeEventQueue->inputBlockId == aInputBlockId) {
+ if (aStartSwipe) {
+ PanGestureInput& startEvent = mSwipeEventQueue->queuedEvents[0];
+ TrackScrollEventAsSwipe(startEvent, mSwipeEventQueue->allowedDirections);
+ for (size_t i = 1; i < mSwipeEventQueue->queuedEvents.Length(); i++) {
+ mSwipeTracker->ProcessEvent(mSwipeEventQueue->queuedEvents[i]);
+ }
+ }
+ mSwipeEventQueue = nullptr;
+ }
+}
+
+void
+nsChildView::DispatchAPZWheelInputEvent(InputData& aEvent, bool aCanTriggerSwipe)
+{
+ if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
+ // Give the swipe tracker a first pass at the event. If a new pan gesture
+ // has been started since the beginning of the swipe, the swipe tracker
+ // will know to ignore the event.
+ nsEventStatus status = mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+
+ WidgetWheelEvent event(true, eWheel, this);
+
+ if (mAPZC) {
+ uint64_t inputBlockId = 0;
+ ScrollableLayerGuid guid;
+
+ nsEventStatus result = mAPZC->ReceiveInputEvent(aEvent, &guid, &inputBlockId);
+ if (result == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ switch(aEvent.mInputType) {
+ case PANGESTURE_INPUT: {
+ PanGestureInput& panInput = aEvent.AsPanGestureInput();
+
+ event = panInput.ToWidgetWheelEvent(this);
+ if (aCanTriggerSwipe) {
+ SwipeInfo swipeInfo = SendMayStartSwipe(panInput);
+ event.mCanTriggerSwipe = swipeInfo.wantsSwipe;
+ if (swipeInfo.wantsSwipe) {
+ if (result == nsEventStatus_eIgnore) {
+ // APZ has determined and that scrolling horizontally in the
+ // requested direction is impossible, so it didn't do any
+ // scrolling for the event.
+ // We know now that MayStartSwipe wants a swipe, so we can start
+ // the swipe now.
+ TrackScrollEventAsSwipe(panInput, swipeInfo.allowedDirections);
+ } else {
+ // We don't know whether this event can start a swipe, so we need
+ // to queue up events and wait for a call to ReportSwipeStarted.
+ // APZ might already have started scrolling in response to the
+ // event if it knew that it's the right thing to do. In that case
+ // we'll still get a call to ReportSwipeStarted, and we will
+ // discard the queued events at that point.
+ mSwipeEventQueue = MakeUnique<SwipeEventQueue>(swipeInfo.allowedDirections,
+ inputBlockId);
+ }
+ }
+ }
+
+ if (mSwipeEventQueue && mSwipeEventQueue->inputBlockId == inputBlockId) {
+ mSwipeEventQueue->queuedEvents.AppendElement(panInput);
+ }
+ break;
+ }
+ case SCROLLWHEEL_INPUT: {
+ event = aEvent.AsScrollWheelInput().ToWidgetWheelEvent(this);
+ break;
+ };
+ default:
+ MOZ_CRASH("unsupported event type");
+ return;
+ }
+ if (event.mMessage == eWheel &&
+ (event.mDeltaX != 0 || event.mDeltaY != 0)) {
+ ProcessUntransformedAPZEvent(&event, guid, inputBlockId, result);
+ }
+ return;
+ }
+
+ nsEventStatus status;
+ switch(aEvent.mInputType) {
+ case PANGESTURE_INPUT: {
+ PanGestureInput panInput = aEvent.AsPanGestureInput();
+ if (panInput.mType == PanGestureInput::PANGESTURE_MAYSTART ||
+ panInput.mType == PanGestureInput::PANGESTURE_START) {
+ mCurrentPanGestureBelongsToSwipe = false;
+ }
+ if (mCurrentPanGestureBelongsToSwipe) {
+ // Ignore this event. It's a momentum event from a scroll gesture
+ // that was processed as a swipe, and the swipe animation has
+ // already finished (so mSwipeTracker is already null).
+ MOZ_ASSERT(panInput.IsMomentum(),
+ "If the fingers are still on the touchpad, we should still have a SwipeTracker, and it should have consumed this event.");
+ return;
+ }
+
+ event = panInput.ToWidgetWheelEvent(this);
+ if (aCanTriggerSwipe) {
+ SwipeInfo swipeInfo = SendMayStartSwipe(panInput);
+
+ // We're in the non-APZ case here, but we still want to know whether
+ // the event was routed to a child process, so we use InputAPZContext
+ // to get that piece of information.
+ ScrollableLayerGuid guid;
+ InputAPZContext context(guid, 0, nsEventStatus_eIgnore);
+
+ event.mCanTriggerSwipe = swipeInfo.wantsSwipe;
+ DispatchEvent(&event, status);
+ if (swipeInfo.wantsSwipe) {
+ if (context.WasRoutedToChildProcess()) {
+ // We don't know whether this event can start a swipe, so we need
+ // to queue up events and wait for a call to ReportSwipeStarted.
+ mSwipeEventQueue = MakeUnique<SwipeEventQueue>(swipeInfo.allowedDirections, 0);
+ } else if (event.TriggersSwipe()) {
+ TrackScrollEventAsSwipe(panInput, swipeInfo.allowedDirections);
+ }
+ }
+
+ if (mSwipeEventQueue && mSwipeEventQueue->inputBlockId == 0) {
+ mSwipeEventQueue->queuedEvents.AppendElement(panInput);
+ }
+ return;
+ }
+ break;
+ }
+ case SCROLLWHEEL_INPUT: {
+ event = aEvent.AsScrollWheelInput().ToWidgetWheelEvent(this);
+ break;
+ }
+ default:
+ MOZ_CRASH("unexpected event type");
+ return;
+ }
+ if (event.mMessage == eWheel &&
+ (event.mDeltaX != 0 || event.mDeltaY != 0)) {
+ DispatchEvent(&event, status);
+ }
+}
+
+// When using 10.11, calling showDefinitionForAttributedString causes the
+// following exception on LookupViewService. (rdar://26476091)
+//
+// Exception: decodeObjectForKey: class "TitlebarAndBackgroundColor" not
+// loaded or does not exist
+//
+// So we set temporary color that is NSColor before calling it.
+
+class MOZ_RAII AutoBackgroundSetter final {
+public:
+ explicit AutoBackgroundSetter(NSView* aView) {
+ if (nsCocoaFeatures::OnElCapitanOrLater() &&
+ [[aView window] isKindOfClass:[ToolbarWindow class]]) {
+ mWindow = [(ToolbarWindow*)[aView window] retain];
+ [mWindow setTemporaryBackgroundColor];
+ } else {
+ mWindow = nullptr;
+ }
+ }
+
+ ~AutoBackgroundSetter() {
+ if (mWindow) {
+ [mWindow restoreBackgroundColor];
+ [mWindow release];
+ }
+ }
+
+private:
+ ToolbarWindow* mWindow; // strong
+};
+
+void
+nsChildView::LookUpDictionary(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical,
+ const LayoutDeviceIntPoint& aPoint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSMutableAttributedString* attrStr =
+ nsCocoaUtils::GetNSMutableAttributedString(aText, aFontRangeArray,
+ aIsVertical,
+ BackingScaleFactor());
+ NSPoint pt =
+ nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
+ NSDictionary* attributes = [attrStr attributesAtIndex:0 effectiveRange:nil];
+ NSFont* font = [attributes objectForKey:NSFontAttributeName];
+ if (font) {
+ if (aIsVertical) {
+ pt.x -= [font descender];
+ } else {
+ pt.y += [font ascender];
+ }
+ }
+
+ AutoBackgroundSetter setter(mView);
+ [mView showDefinitionForAttributedString:attrStr atPoint:pt];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#ifdef ACCESSIBILITY
+already_AddRefed<a11y::Accessible>
+nsChildView::GetDocumentAccessible()
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return nullptr;
+
+ if (mAccessible) {
+ RefPtr<a11y::Accessible> ret;
+ CallQueryReferent(mAccessible.get(),
+ static_cast<a11y::Accessible**>(getter_AddRefs(ret)));
+ return ret.forget();
+ }
+
+ // need to fetch the accessible anew, because it has gone away.
+ // cache the accessible in our weak ptr
+ RefPtr<a11y::Accessible> acc = GetRootAccessible();
+ mAccessible = do_GetWeakReference(acc.get());
+
+ return acc.forget();
+}
+#endif
+
+// GLPresenter implementation
+
+GLPresenter::GLPresenter(GLContext* aContext)
+ : mGLContext(aContext)
+{
+ mGLContext->MakeCurrent();
+ ShaderConfigOGL config;
+ config.SetTextureTarget(LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+ mRGBARectProgram = MakeUnique<ShaderProgramOGL>(mGLContext,
+ ProgramProfileOGL::GetProfileFor(config));
+
+ // Create mQuadVBO.
+ mGLContext->fGenBuffers(1, &mQuadVBO);
+ mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO);
+
+ // 1 quad, with the number of the quad (vertexID) encoded in w.
+ GLfloat vertices[] = {
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f, 0.0f,
+ };
+ HeapCopyOfStackArray<GLfloat> verticesOnHeap(vertices);
+ mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER,
+ verticesOnHeap.ByteLength(),
+ verticesOnHeap.Data(),
+ LOCAL_GL_STATIC_DRAW);
+ mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
+}
+
+GLPresenter::~GLPresenter()
+{
+ if (mQuadVBO) {
+ mGLContext->MakeCurrent();
+ mGLContext->fDeleteBuffers(1, &mQuadVBO);
+ mQuadVBO = 0;
+ }
+}
+
+void
+GLPresenter::BindAndDrawQuad(ShaderProgramOGL *aProgram,
+ const gfx::Rect& aLayerRect,
+ const gfx::Rect& aTextureRect)
+{
+ mGLContext->MakeCurrent();
+
+ gfx::Rect layerRects[4];
+ gfx::Rect textureRects[4];
+
+ layerRects[0] = aLayerRect;
+ textureRects[0] = aTextureRect;
+
+ aProgram->SetLayerRects(layerRects);
+ aProgram->SetTextureRects(textureRects);
+
+ const GLuint coordAttribIndex = 0;
+
+ mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO);
+ mGLContext->fVertexAttribPointer(coordAttribIndex, 4,
+ LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
+ (GLvoid*)0);
+ mGLContext->fEnableVertexAttribArray(coordAttribIndex);
+ mGLContext->fDrawArrays(LOCAL_GL_TRIANGLES, 0, 6);
+ mGLContext->fDisableVertexAttribArray(coordAttribIndex);
+}
+
+void
+GLPresenter::BeginFrame(LayoutDeviceIntSize aRenderSize)
+{
+ mGLContext->MakeCurrent();
+
+ mGLContext->fViewport(0, 0, aRenderSize.width, aRenderSize.height);
+
+ // Matrix to transform (0, 0, width, height) to viewport space (-1.0, 1.0,
+ // 2, 2) and flip the contents.
+ gfx::Matrix viewMatrix = gfx::Matrix::Translation(-1.0, 1.0);
+ viewMatrix.PreScale(2.0f / float(aRenderSize.width),
+ 2.0f / float(aRenderSize.height));
+ viewMatrix.PreScale(1.0f, -1.0f);
+
+ gfx::Matrix4x4 matrix3d = gfx::Matrix4x4::From2D(viewMatrix);
+ matrix3d._33 = 0.0f;
+
+ // set the projection matrix for the next time the program is activated
+ mProjMatrix = matrix3d;
+
+ // Default blend function implements "OVER"
+ mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
+ LOCAL_GL_ONE, LOCAL_GL_ONE);
+ mGLContext->fEnable(LOCAL_GL_BLEND);
+
+ mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0);
+ mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT);
+
+ mGLContext->fEnable(LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+}
+
+void
+GLPresenter::EndFrame()
+{
+ mGLContext->SwapBuffers();
+ mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
+}
+
+class WidgetsReleaserRunnable final : public mozilla::Runnable
+{
+public:
+ explicit WidgetsReleaserRunnable(nsTArray<nsCOMPtr<nsIWidget>>&& aWidgetArray)
+ : mWidgetArray(aWidgetArray)
+ {
+ }
+
+ // Do nothing; all this runnable does is hold a reference the widgets in
+ // mWidgetArray, and those references will be dropped when this runnable
+ // is destroyed.
+
+private:
+ nsTArray<nsCOMPtr<nsIWidget>> mWidgetArray;
+};
+
+#pragma mark -
+
+@implementation ChildView
+
+// globalDragPboard is non-null during native drag sessions that did not originate
+// in our native NSView (it is set in |draggingEntered:|). It is unset when the
+// drag session ends for this view, either with the mouse exiting or when a drop
+// occurs in this view.
+NSPasteboard* globalDragPboard = nil;
+
+// gLastDragView and gLastDragMouseDownEvent are used to communicate information
+// to the drag service during drag invocation (starting a drag in from the view).
+// gLastDragView is only non-null while mouseDragged is on the call stack.
+NSView* gLastDragView = nil;
+NSEvent* gLastDragMouseDownEvent = nil;
+
++ (void)initialize
+{
+ static BOOL initialized = NO;
+
+ if (!initialized) {
+ // Inform the OS about the types of services (from the "Services" menu)
+ // that we can handle.
+
+ NSArray *sendTypes = [[NSArray alloc] initWithObjects:NSStringPboardType,NSHTMLPboardType,nil];
+ NSArray *returnTypes = [[NSArray alloc] initWithObjects:NSStringPboardType,NSHTMLPboardType,nil];
+
+ [NSApp registerServicesMenuSendTypes:sendTypes returnTypes:returnTypes];
+
+ [sendTypes release];
+ [returnTypes release];
+
+ initialized = YES;
+ }
+}
+
++ (void)registerViewForDraggedTypes:(NSView*)aView
+{
+ [aView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
+ NSStringPboardType,
+ NSHTMLPboardType,
+ NSURLPboardType,
+ NSFilesPromisePboardType,
+ kWildcardPboardType,
+ kCorePboardType_url,
+ kCorePboardType_urld,
+ kCorePboardType_urln,
+ nil]];
+}
+
+// initWithFrame:geckoChild:
+- (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super initWithFrame:inFrame])) {
+ mGeckoChild = inChild;
+ mPendingDisplay = NO;
+ mBlockedLastMouseDown = NO;
+ mExpectingWheelStop = NO;
+
+ mLastMouseDownEvent = nil;
+ mLastKeyDownEvent = nil;
+ mClickThroughMouseDownEvent = nil;
+ mDragService = nullptr;
+
+ mGestureState = eGestureState_None;
+ mCumulativeMagnification = 0.0;
+ mCumulativeRotation = 0.0;
+
+ mNeedsGLUpdate = NO;
+
+ [self setFocusRingType:NSFocusRingTypeNone];
+
+#ifdef __LP64__
+ mCancelSwipeAnimation = nil;
+#endif
+
+ mTopLeftCornerMask = NULL;
+ }
+
+ // register for things we'll take from other applications
+ [ChildView registerViewForDraggedTypes:self];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(systemMetricsChanged)
+ name:NSControlTintDidChangeNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(systemMetricsChanged)
+ name:NSSystemColorsDidChangeNotification
+ object:nil];
+ // TODO: replace the string with the constant once we build with the 10.7 SDK
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(scrollbarSystemMetricChanged)
+ name:@"NSPreferredScrollerStyleDidChangeNotification"
+ object:nil];
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(systemMetricsChanged)
+ name:@"AppleAquaScrollBarVariantChanged"
+ object:nil
+ suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(_surfaceNeedsUpdate:)
+ name:NSViewGlobalFrameDidChangeNotification
+ object:self];
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// ComplexTextInputPanel's interpretKeyEvent hack won't work without this.
+// It makes calls to +[NSTextInputContext currentContext], deep in system
+// code, return the appropriate context.
+- (NSTextInputContext *)inputContext
+{
+ NSTextInputContext* pluginContext = NULL;
+ if (mGeckoChild && mGeckoChild->IsPluginFocused()) {
+ ComplexTextInputPanel* ctiPanel =
+ ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+ if (ctiPanel) {
+ pluginContext = (NSTextInputContext*) ctiPanel->GetInputContext();
+ }
+ }
+ if (pluginContext) {
+ return pluginContext;
+ } else {
+ if (!mGeckoChild) {
+ // -[ChildView widgetDestroyed] has been called, but
+ // -[ChildView delayedTearDown] has not yet completed. Accessing
+ // [super inputContext] now would uselessly recreate a text input context
+ // for us, under which -[ChildView validAttributesForMarkedText] would
+ // be called and the assertion checking for mTextInputHandler would fail.
+ // We return nil to avoid that.
+ return nil;
+ }
+ return [super inputContext];
+ }
+}
+
+- (void)installTextInputHandler:(TextInputHandler*)aHandler
+{
+ mTextInputHandler = aHandler;
+}
+
+- (void)uninstallTextInputHandler
+{
+ mTextInputHandler = nullptr;
+}
+
+- (bool)preRender:(NSOpenGLContext *)aGLContext
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (![self window] ||
+ ([[self window] isKindOfClass:[BaseWindow class]] &&
+ ![(BaseWindow*)[self window] isVisibleOrBeingShown])) {
+ // Before the window is shown, our GL context's front FBO is not
+ // framebuffer complete, so we refuse to render.
+ return false;
+ }
+
+ if (!mGLContext) {
+ mGLContext = aGLContext;
+ [mGLContext retain];
+ mNeedsGLUpdate = true;
+ }
+
+ CGLLockContext((CGLContextObj)[aGLContext CGLContextObj]);
+
+ if (mNeedsGLUpdate) {
+ [self updateGLContext];
+ mNeedsGLUpdate = NO;
+ }
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+- (void)postRender:(NSOpenGLContext *)aGLContext
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ CGLUnlockContext((CGLContextObj)[aGLContext CGLContextObj]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mGLContext release];
+ [mPendingDirtyRects release];
+ [mLastMouseDownEvent release];
+ [mLastKeyDownEvent release];
+ [mClickThroughMouseDownEvent release];
+ CGImageRelease(mTopLeftCornerMask);
+ ChildViewMouseTracker::OnDestroyView(self);
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
+
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)widgetDestroyed
+{
+ if (mTextInputHandler) {
+ mTextInputHandler->OnDestroyWidget(mGeckoChild);
+ mTextInputHandler = nullptr;
+ }
+ mGeckoChild = nullptr;
+
+ // Just in case we're destroyed abruptly and missed the draggingExited
+ // or performDragOperation message.
+ NS_IF_RELEASE(mDragService);
+}
+
+// mozView method, return our gecko child view widget. Note this does not AddRef.
+- (nsIWidget*) widget
+{
+ return static_cast<nsIWidget*>(mGeckoChild);
+}
+
+- (void)systemMetricsChanged
+{
+ if (mGeckoChild)
+ mGeckoChild->NotifyThemeChanged();
+}
+
+- (void)scrollbarSystemMetricChanged
+{
+ [self systemMetricsChanged];
+
+ if (mGeckoChild) {
+ nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
+ if (listener) {
+ nsIPresShell* presShell = listener->GetPresShell();
+ if (presShell) {
+ presShell->ReconstructFrames();
+ }
+ }
+ }
+}
+
+- (void)setNeedsPendingDisplay
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mPendingFullDisplay = YES;
+ if (!mPendingDisplay) {
+ [self performSelector:@selector(processPendingRedraws) withObject:nil afterDelay:0];
+ mPendingDisplay = YES;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)setNeedsPendingDisplayInRect:(NSRect)invalidRect
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mPendingDirtyRects)
+ mPendingDirtyRects = [[NSMutableArray alloc] initWithCapacity:1];
+ [mPendingDirtyRects addObject:[NSValue valueWithRect:invalidRect]];
+ if (!mPendingDisplay) {
+ [self performSelector:@selector(processPendingRedraws) withObject:nil afterDelay:0];
+ mPendingDisplay = YES;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Clears the queue of any pending invalides
+- (void)processPendingRedraws
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mPendingFullDisplay) {
+ [self setNeedsDisplay:YES];
+ }
+ else if (mPendingDirtyRects) {
+ unsigned int count = [mPendingDirtyRects count];
+ for (unsigned int i = 0; i < count; ++i) {
+ [self setNeedsDisplayInRect:[[mPendingDirtyRects objectAtIndex:i] rectValue]];
+ }
+ }
+ mPendingFullDisplay = NO;
+ mPendingDisplay = NO;
+ [mPendingDirtyRects release];
+ mPendingDirtyRects = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)setNeedsDisplayInRect:(NSRect)aRect
+{
+ if (![self isUsingOpenGL]) {
+ [super setNeedsDisplayInRect:aRect];
+ return;
+ }
+
+ if ([[self window] isVisible] && [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. (These synchronizations
+ // show up in a profiler as CGSDeviceLock / _CGSLockWindow /
+ // _CGSSynchronizeWindowBackingStore.) It also means that Cocoa doesn't
+ // have any potentially expensive invalid rect management for us.
+ 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]];
+ }
+ }
+}
+
+- (NSString*)description
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSString stringWithFormat:@"ChildView %p, gecko child %p, frame %@", self, mGeckoChild, NSStringFromRect([self frame])];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Make the origin of this view the topLeft corner (gecko origin) rather
+// than the bottomLeft corner (standard cocoa origin).
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (BOOL)isOpaque
+{
+ return [[self window] isOpaque];
+}
+
+- (void)sendFocusEvent:(EventMessage)eventMessage
+{
+ if (!mGeckoChild)
+ return;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetGUIEvent focusGuiEvent(true, eventMessage, mGeckoChild);
+ focusGuiEvent.mTime = PR_IntervalNow();
+ mGeckoChild->DispatchEvent(&focusGuiEvent, status);
+}
+
+// We accept key and mouse events, so don't keep passing them up the chain. Allow
+// this to be a 'focused' widget for event dispatch.
+- (BOOL)acceptsFirstResponder
+{
+ return YES;
+}
+
+// Accept mouse down events on background windows
+- (BOOL)acceptsFirstMouse:(NSEvent*)aEvent
+{
+ if (![[self window] isKindOfClass:[PopupWindow class]]) {
+ // We rely on this function to tell us that the mousedown was on a
+ // background window. Inside mouseDown we can't tell whether we were
+ // inactive because at that point we've already been made active.
+ // Unfortunately, acceptsFirstMouse is called for PopupWindows even when
+ // their parent window is active, so ignore this on them for now.
+ mClickThroughMouseDownEvent = [aEvent retain];
+ }
+ return YES;
+}
+
+- (void)scrollRect:(NSRect)aRect by:(NSSize)offset
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Update any pending dirty rects to reflect the new scroll position
+ if (mPendingDirtyRects) {
+ unsigned int count = [mPendingDirtyRects count];
+ for (unsigned int i = 0; i < count; ++i) {
+ NSRect oldRect = [[mPendingDirtyRects objectAtIndex:i] rectValue];
+ NSRect newRect = NSOffsetRect(oldRect, offset.width, offset.height);
+ [mPendingDirtyRects replaceObjectAtIndex:i
+ withObject:[NSValue valueWithRect:newRect]];
+ }
+ }
+ [super scrollRect:aRect by:offset];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)mouseDownCanMoveWindow
+{
+ // Return YES so that parts of this view can be draggable. The non-draggable
+ // parts will be covered by NSViews that return NO from
+ // mouseDownCanMoveWindow and thus override draggability from the inside.
+ // These views are assembled in nsChildView::UpdateWindowDraggingRegion.
+ return YES;
+}
+
+-(void)updateGLContext
+{
+ [mGLContext setView:self];
+ [mGLContext update];
+}
+
+- (void)_surfaceNeedsUpdate:(NSNotification*)notification
+{
+ if (mGLContext) {
+ CGLLockContext((CGLContextObj)[mGLContext CGLContextObj]);
+ mNeedsGLUpdate = YES;
+ CGLUnlockContext((CGLContextObj)[mGLContext CGLContextObj]);
+ }
+}
+
+- (BOOL)wantsBestResolutionOpenGLSurface
+{
+ return nsCocoaUtils::HiDPIEnabled() ? YES : NO;
+}
+
+- (void)viewDidChangeBackingProperties
+{
+ [super viewDidChangeBackingProperties];
+ if (mGeckoChild) {
+ // actually, it could be the color space that's changed,
+ // but we can't tell the difference here except by retrieving
+ // the backing scale factor and comparing to the old value
+ mGeckoChild->BackingScaleFactorChanged();
+ }
+}
+
+- (BOOL)isCoveringTitlebar
+{
+ return [[self window] isKindOfClass:[BaseWindow class]] &&
+ [(BaseWindow*)[self window] mainChildView] == self &&
+ [(BaseWindow*)[self window] drawsContentsIntoWindowFrame];
+}
+
+- (void)viewWillStartLiveResize
+{
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+
+ if (!observerService) {
+ return;
+ }
+
+ observerService->NotifyObservers(nullptr, "live-resize-start", nullptr);
+}
+
+- (void)viewDidEndLiveResize
+{
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+
+ if (!observerService) {
+ return;
+ }
+
+ observerService->NotifyObservers(nullptr, "live-resize-end", nullptr);
+}
+
+- (NSColor*)vibrancyFillColorForThemeGeometryType:(nsITheme::ThemeGeometryType)aThemeGeometryType
+{
+ if (!mGeckoChild) {
+ return [NSColor whiteColor];
+ }
+ return mGeckoChild->VibrancyFillColorForThemeGeometryType(aThemeGeometryType);
+}
+
+- (NSColor*)vibrancyFontSmoothingBackgroundColorForThemeGeometryType:(nsITheme::ThemeGeometryType)aThemeGeometryType
+{
+ if (!mGeckoChild) {
+ return [NSColor clearColor];
+ }
+ return mGeckoChild->VibrancyFontSmoothingBackgroundColorForThemeGeometryType(aThemeGeometryType);
+}
+
+- (LayoutDeviceIntRegion)nativeDirtyRegionWithBoundingRect:(NSRect)aRect
+{
+ LayoutDeviceIntRect boundingRect = mGeckoChild->CocoaPointsToDevPixels(aRect);
+ const NSRect *rects;
+ NSInteger count;
+ [self getRectsBeingDrawn:&rects count:&count];
+
+ if (count > MAX_RECTS_IN_REGION) {
+ return boundingRect;
+ }
+
+ LayoutDeviceIntRegion region;
+ for (NSInteger i = 0; i < count; ++i) {
+ region.Or(region, mGeckoChild->CocoaPointsToDevPixels(rects[i]));
+ }
+ region.And(region, boundingRect);
+ return region;
+}
+
+// The display system has told us that a portion of our view is dirty. Tell
+// gecko to paint it
+- (void)drawRect:(NSRect)aRect
+{
+ CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ [self drawRect:aRect inContext:cgContext];
+
+ // If we're a transparent window and our contents have changed, we need
+ // to make sure the shadow is updated to the new contents.
+ if ([[self window] isKindOfClass:[BaseWindow class]]) {
+ [(BaseWindow*)[self window] deferredInvalidateShadow];
+ }
+}
+
+- (void)drawRect:(NSRect)aRect inContext:(CGContextRef)aContext
+{
+ if (!mGeckoChild || !mGeckoChild->IsVisible())
+ return;
+
+#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 ([self isUsingOpenGL]) {
+ // 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.
+
+ // Since this view is usually declared as opaque, the window's pixel
+ // buffer may now contain garbage which we need to prevent from reaching
+ // the screen. The only place where garbage can show is in the window
+ // corners and the vibrant regions of the window - the rest of the window
+ // is covered by opaque content in our OpenGL surface.
+ // So we need to clear the pixel buffer contents in these areas.
+ mGeckoChild->ClearVibrantAreas();
+ [self clearCorners];
+
+ // Do GL composition and return.
+ [self drawUsingOpenGL];
+ return;
+ }
+
+ PROFILER_LABEL("ChildView", "drawRect",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ // The CGContext that drawRect supplies us with comes with a transform that
+ // scales one user space unit to one Cocoa point, which can consist of
+ // multiple dev pixels. But Gecko expects its supplied context to be scaled
+ // to device pixels, so we need to reverse the scaling.
+ double scale = mGeckoChild->BackingScaleFactor();
+ CGContextSaveGState(aContext);
+ CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale);
+
+ NSSize viewSize = [self bounds].size;
+ gfx::IntSize backingSize = gfx::IntSize::Truncate(viewSize.width * scale, viewSize.height * scale);
+ LayoutDeviceIntRegion region = [self nativeDirtyRegionWithBoundingRect:aRect];
+
+ bool painted = mGeckoChild->PaintWindowInContext(aContext, region, backingSize);
+
+ // 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, NSRectToCGRect(aRect));
+ }
+
+ if ([self isCoveringTitlebar]) {
+ [self drawTitleString];
+ [self drawTitlebarHighlight];
+ [self maskTopCornersInContext:aContext];
+ }
+
+#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, NSRectToCGRect(aRect));
+#endif
+}
+
+- (BOOL)isUsingMainThreadOpenGL
+{
+ if (!mGeckoChild || ![self window])
+ return NO;
+
+ return mGeckoChild->GetLayerManager(nullptr)->GetBackendType() == mozilla::layers::LayersBackend::LAYERS_OPENGL;
+}
+
+- (BOOL)isUsingOpenGL
+{
+ if (!mGeckoChild || ![self window])
+ return NO;
+
+ return mGLContext || mUsingOMTCompositor || [self isUsingMainThreadOpenGL];
+}
+
+- (void)drawUsingOpenGL
+{
+ PROFILER_LABEL("ChildView", "drawUsingOpenGL",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ if (![self isUsingOpenGL] || !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];
+ }
+}
+
+- (BOOL)hasRoundedBottomCorners
+{
+ return [[self window] respondsToSelector:@selector(bottomCornerRounded)] &&
+ [[self window] bottomCornerRounded];
+}
+
+- (CGFloat)cornerRadius
+{
+ NSView* frameView = [[[self window] contentView] superview];
+ if (!frameView || ![frameView respondsToSelector:@selector(roundedCornerRadius)])
+ return 4.0f;
+ return [frameView roundedCornerRadius];
+}
+
+-(void)setGLOpaque:(BOOL)aOpaque
+{
+ CGLLockContext((CGLContextObj)[mGLContext CGLContextObj]);
+ // Make the context opaque for fullscreen (since it performs better), and transparent
+ // for windowed (since we need it for rounded corners).
+ GLint opaque = aOpaque ? 1 : 0;
+ [mGLContext setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
+ CGLUnlockContext((CGLContextObj)[mGLContext CGLContextObj]);
+}
+
+// Accelerated windows have two NSSurfaces:
+// (1) The window's pixel buffer in the back and
+// (2) the OpenGL view in the front.
+// These two surfaces are composited by the window manager. Drawing into the
+// CGContext which is provided by drawRect ends up in (1).
+// When our window has rounded corners, the OpenGL view has transparent pixels
+// in the corners. In these places the contents of the window's pixel buffer
+// can show through. So we need to make sure that the pixel buffer is
+// transparent in the corners so that no garbage reaches the screen.
+// The contents of the pixel buffer in the rest of the window don't matter
+// because they're covered by opaque pixels of the OpenGL context.
+// Making the corners transparent works even though our window is
+// declared "opaque" (in the NSWindow's isOpaque method).
+- (void)clearCorners
+{
+ CGFloat radius = [self cornerRadius];
+ CGFloat w = [self bounds].size.width, h = [self bounds].size.height;
+ [[NSColor clearColor] set];
+
+ if ([self isCoveringTitlebar]) {
+ NSRectFill(NSMakeRect(0, 0, radius, radius));
+ NSRectFill(NSMakeRect(w - radius, 0, radius, radius));
+ }
+
+ if ([self hasRoundedBottomCorners]) {
+ NSRectFill(NSMakeRect(0, h - radius, radius, radius));
+ NSRectFill(NSMakeRect(w - radius, h - radius, radius, radius));
+ }
+}
+
+// This is the analog of nsChildView::MaybeDrawRoundedCorners for CGContexts.
+// We only need to mask the top corners here because Cocoa does the masking
+// for the window's bottom corners automatically (starting with 10.7).
+- (void)maskTopCornersInContext:(CGContextRef)aContext
+{
+ CGFloat radius = [self cornerRadius];
+ int32_t devPixelCornerRadius = mGeckoChild->CocoaPointsToDevPixels(radius);
+
+ // First make sure that mTopLeftCornerMask is set up.
+ if (!mTopLeftCornerMask ||
+ int32_t(CGImageGetWidth(mTopLeftCornerMask)) != devPixelCornerRadius) {
+ CGImageRelease(mTopLeftCornerMask);
+ CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
+ CGContextRef imgCtx = CGBitmapContextCreate(NULL,
+ devPixelCornerRadius,
+ devPixelCornerRadius,
+ 8, devPixelCornerRadius * 4,
+ rgb, kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(rgb);
+ DrawTopLeftCornerMask(imgCtx, devPixelCornerRadius);
+ mTopLeftCornerMask = CGBitmapContextCreateImage(imgCtx);
+ CGContextRelease(imgCtx);
+ }
+
+ // kCGBlendModeDestinationIn is the secret sauce which allows us to erase
+ // already painted pixels. It's defined as R = D * Sa: multiply all channels
+ // of the destination pixel with the alpha of the source pixel. In our case,
+ // the source is mTopLeftCornerMask.
+ CGContextSaveGState(aContext);
+ CGContextSetBlendMode(aContext, kCGBlendModeDestinationIn);
+
+ CGRect destRect = CGRectMake(0, 0, radius, radius);
+
+ // Erase the top left corner...
+ CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
+
+ // ... and the top right corner.
+ CGContextTranslateCTM(aContext, [self bounds].size.width, 0);
+ CGContextScaleCTM(aContext, -1, 1);
+ CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
+
+ CGContextRestoreGState(aContext);
+}
+
+- (void)drawTitleString
+{
+ BaseWindow* window = (BaseWindow*)[self window];
+ if (![window wantsTitleDrawn]) {
+ return;
+ }
+
+ NSView* frameView = [[window contentView] superview];
+ if (![frameView respondsToSelector:@selector(_drawTitleBar:)]) {
+ return;
+ }
+
+ NSGraphicsContext* oldContext = [NSGraphicsContext currentContext];
+ CGContextRef ctx = (CGContextRef)[oldContext graphicsPort];
+ CGContextSaveGState(ctx);
+ if ([oldContext isFlipped] != [frameView isFlipped]) {
+ CGContextTranslateCTM(ctx, 0, [self bounds].size.height);
+ CGContextScaleCTM(ctx, 1, -1);
+ }
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[frameView isFlipped]]];
+ [frameView _drawTitleBar:[frameView bounds]];
+ CGContextRestoreGState(ctx);
+ [NSGraphicsContext setCurrentContext:oldContext];
+}
+
+- (void)drawTitlebarHighlight
+{
+ DrawTitlebarHighlight([self bounds].size, [self cornerRadius],
+ mGeckoChild->DevPixelsToCocoaPoints(1));
+}
+
+- (void)viewWillDraw
+{
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ if (mGeckoChild) {
+ // The OS normally *will* draw our NSWindow, no matter what we do here.
+ // But Gecko can delete our parent widget(s) (along with mGeckoChild)
+ // while processing a paint request, which closes our NSWindow and
+ // makes the OS throw an NSInternalInconsistencyException assertion when
+ // it tries to draw it. Sometimes the OS also aborts the browser process.
+ // So we need to retain our parent(s) here and not release it/them until
+ // the next time through the main thread's run loop. When we do this we
+ // also need to retain and release mGeckoChild, which holds a strong
+ // reference to us. See bug 550392.
+ nsIWidget* parent = mGeckoChild->GetParent();
+ if (parent) {
+ nsTArray<nsCOMPtr<nsIWidget>> widgetArray;
+ while (parent) {
+ widgetArray.AppendElement(parent);
+ parent = parent->GetParent();
+ }
+ widgetArray.AppendElement(mGeckoChild);
+ nsCOMPtr<nsIRunnable> releaserRunnable =
+ new WidgetsReleaserRunnable(Move(widgetArray));
+ NS_DispatchToMainThread(releaserRunnable);
+ }
+
+ if ([self isUsingOpenGL]) {
+ if (ShadowLayerForwarder* slf = mGeckoChild->GetLayerManager()->AsShadowForwarder()) {
+ slf->WindowOverlayChanged();
+ }
+ }
+
+ mGeckoChild->WillPaintWindow();
+ }
+ [super viewWillDraw];
+}
+
+#if USE_CLICK_HOLD_CONTEXTMENU
+//
+// -clickHoldCallback:
+//
+// called from a timer two seconds after a mouse down to see if we should display
+// a context menu (click-hold). |anEvent| is the original mouseDown event. If we're
+// still in that mouseDown by this time, put up the context menu, otherwise just
+// fuhgeddaboutit. |anEvent| has been retained by the OS until after this callback
+// fires so we're ok there.
+//
+// This code currently messes in a bunch of edge cases (bugs 234751, 232964, 232314)
+// so removing it until we get it straightened out.
+//
+- (void)clickHoldCallback:(id)theEvent;
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if( theEvent == [NSApp currentEvent] ) {
+ // we're still in the middle of the same mousedown event here, activate
+ // click-hold context menu by triggering the right mouseDown action.
+ NSEvent* clickHoldEvent = [NSEvent mouseEventWithType:NSRightMouseDown
+ location:[theEvent locationInWindow]
+ modifierFlags:[theEvent modifierFlags]
+ timestamp:[theEvent timestamp]
+ windowNumber:[theEvent windowNumber]
+ context:[theEvent context]
+ eventNumber:[theEvent eventNumber]
+ clickCount:[theEvent clickCount]
+ pressure:[theEvent pressure]];
+ [self rightMouseDown:clickHoldEvent];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+#endif
+
+// If we've just created a non-native context menu, we need to mark it as
+// such and let the OS (and other programs) know when it opens and closes
+// (this is how the OS knows to close other programs' context menus when
+// ours open). We send the initial notification here, but others are sent
+// in nsCocoaWindow::Show().
+- (void)maybeInitContextMenuTracking
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE_VOID(rollupListener);
+ nsCOMPtr<nsIWidget> widget = rollupListener->GetRollupWidget();
+ NS_ENSURE_TRUE_VOID(widget);
+
+ NSWindow *popupWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (!popupWindow || ![popupWindow isKindOfClass:[PopupWindow class]])
+ return;
+
+ [[NSDistributedNotificationCenter defaultCenter]
+ postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
+ object:@"org.mozilla.gecko.PopupWindow"];
+ [(PopupWindow*)popupWindow setIsContextMenu:YES];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Returns true if the event should no longer be processed, false otherwise.
+// This does not return whether or not anything was rolled up.
+- (BOOL)maybeRollup:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ BOOL consumeEvent = NO;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE(rollupListener, false);
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget) {
+ NSWindow* currentPopup = static_cast<NSWindow*>(rollupWidget->GetNativeData(NS_NATIVE_WINDOW));
+ if (!nsCocoaUtils::IsEventOverWindow(theEvent, currentPopup)) {
+ // event is not over the rollup window, default is to roll up
+ bool shouldRollup = true;
+
+ // check to see if scroll events should roll up the popup
+ if ([theEvent type] == NSScrollWheel) {
+ shouldRollup = rollupListener->ShouldRollupOnMouseWheelEvent();
+ // consume scroll events that aren't over the popup
+ // unless the popup is an arrow panel
+ consumeEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
+ }
+
+ // if we're dealing with menus, we probably have submenus and
+ // we don't want to rollup if the click is in a parent menu of
+ // the current submenu
+ uint32_t popupsToRollup = UINT32_MAX;
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain);
+ for (uint32_t i = 0; i < widgetChain.Length(); i++) {
+ nsIWidget* widget = widgetChain[i];
+ NSWindow* currWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (nsCocoaUtils::IsEventOverWindow(theEvent, currWindow)) {
+ // don't roll up if the mouse event occurred within a menu of the
+ // same type. If the mouse event occurred in a menu higher than
+ // that, roll up, but pass the number of popups to Rollup so
+ // that only those of the same type close up.
+ if (i < sameTypeCount) {
+ shouldRollup = false;
+ }
+ else {
+ popupsToRollup = sameTypeCount;
+ }
+ break;
+ }
+ }
+
+ if (shouldRollup) {
+ if ([theEvent type] == NSLeftMouseDown) {
+ NSPoint point = [NSEvent mouseLocation];
+ FlipCocoaScreenCoordinate(point);
+ gfx::IntPoint pos = gfx::IntPoint::Truncate(point.x, point.y);
+ consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, true, &pos, nullptr);
+ }
+ else {
+ consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, true, nullptr, nullptr);
+ }
+ }
+ }
+ }
+
+ return consumeEvent;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+/*
+ * In OS X Mountain Lion and above, smart zoom gestures are implemented in
+ * smartMagnifyWithEvent. In OS X Lion, they are implemented in
+ * magnifyWithEvent. See inline comments for more info.
+ *
+ * The prototypes swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
+ * smartMagnifyWithEvent, rotateWithEvent, and endGestureWithEvent were
+ * obtained from the following links:
+ * https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/Reference/Reference.html
+ * https://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKit.html
+ */
+
+- (void)swipeWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ float deltaX = [anEvent deltaX]; // left=1.0, right=-1.0
+ float deltaY = [anEvent deltaY]; // up=1.0, down=-1.0
+
+ // Setup the "swipe" event.
+ WidgetSimpleGestureEvent geckoEvent(true, eSwipeGesture, mGeckoChild);
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+
+ // Record the left/right direction.
+ if (deltaX > 0.0)
+ geckoEvent.mDirection |= nsIDOMSimpleGestureEvent::DIRECTION_LEFT;
+ else if (deltaX < 0.0)
+ geckoEvent.mDirection |= nsIDOMSimpleGestureEvent::DIRECTION_RIGHT;
+
+ // Record the up/down direction.
+ if (deltaY > 0.0)
+ geckoEvent.mDirection |= nsIDOMSimpleGestureEvent::DIRECTION_UP;
+ else if (deltaY < 0.0)
+ geckoEvent.mDirection |= nsIDOMSimpleGestureEvent::DIRECTION_DOWN;
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)beginGestureWithEvent:(NSEvent *)anEvent
+{
+ if (!anEvent)
+ return;
+
+ mGestureState = eGestureState_StartGesture;
+ mCumulativeMagnification = 0;
+ mCumulativeRotation = 0.0;
+}
+
+- (void)magnifyWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ float deltaZ = [anEvent deltaZ];
+
+ EventMessage msg;
+ switch (mGestureState) {
+ case eGestureState_StartGesture:
+ msg = eMagnifyGestureStart;
+ mGestureState = eGestureState_MagnifyGesture;
+ break;
+
+ case eGestureState_MagnifyGesture:
+ msg = eMagnifyGestureUpdate;
+ break;
+
+ case eGestureState_None:
+ case eGestureState_RotateGesture:
+ default:
+ return;
+ }
+
+ // Setup the event.
+ WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild);
+ geckoEvent.mDelta = deltaZ;
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+ // Keep track of the cumulative magnification for the final "magnify" event.
+ mCumulativeMagnification += deltaZ;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)smartMagnifyWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild) {
+ return;
+ }
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ // Setup the "double tap" event.
+ WidgetSimpleGestureEvent geckoEvent(true, eTapGesture, mGeckoChild);
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.mClickCount = 1;
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+ // Clear the gesture state
+ mGestureState = eGestureState_None;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)rotateWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ float rotation = [anEvent rotation];
+
+ EventMessage msg;
+ switch (mGestureState) {
+ case eGestureState_StartGesture:
+ msg = eRotateGestureStart;
+ mGestureState = eGestureState_RotateGesture;
+ break;
+
+ case eGestureState_RotateGesture:
+ msg = eRotateGestureUpdate;
+ break;
+
+ case eGestureState_None:
+ case eGestureState_MagnifyGesture:
+ default:
+ return;
+ }
+
+ // Setup the event.
+ WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild);
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.mDelta = -rotation;
+ if (rotation > 0.0) {
+ geckoEvent.mDirection = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
+ } else {
+ geckoEvent.mDirection = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE;
+ }
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+ // Keep track of the cumulative rotation for the final "rotate" event.
+ mCumulativeRotation += rotation;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)endGestureWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild) {
+ // Clear the gestures state if we cannot send an event.
+ mGestureState = eGestureState_None;
+ mCumulativeMagnification = 0.0;
+ mCumulativeRotation = 0.0;
+ return;
+ }
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ switch (mGestureState) {
+ case eGestureState_MagnifyGesture:
+ {
+ // Setup the "magnify" event.
+ WidgetSimpleGestureEvent geckoEvent(true, eMagnifyGesture, mGeckoChild);
+ geckoEvent.mDelta = mCumulativeMagnification;
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+ }
+ break;
+
+ case eGestureState_RotateGesture:
+ {
+ // Setup the "rotate" event.
+ WidgetSimpleGestureEvent geckoEvent(true, eRotateGesture, mGeckoChild);
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.mDelta = -mCumulativeRotation;
+ if (mCumulativeRotation > 0.0) {
+ geckoEvent.mDirection = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
+ } else {
+ geckoEvent.mDirection = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE;
+ }
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+ }
+ break;
+
+ case eGestureState_None:
+ case eGestureState_StartGesture:
+ default:
+ break;
+ }
+
+ // Clear the gestures state.
+ mGestureState = eGestureState_None;
+ mCumulativeMagnification = 0.0;
+ mCumulativeRotation = 0.0;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (bool)shouldConsiderStartingSwipeFromEvent:(NSEvent*)anEvent
+{
+ // This method checks whether the AppleEnableSwipeNavigateWithScrolls global
+ // preference is set. If it isn't, fluid swipe tracking is disabled, and a
+ // horizontal two-finger gesture is always a scroll (even in Safari). This
+ // preference can't (currently) be set from the Preferences UI -- only using
+ // 'defaults write'.
+ if (![NSEvent isSwipeTrackingFromScrollEventsEnabled]) {
+ return false;
+ }
+
+ // Only initiate horizontal tracking for gestures that have just begun --
+ // otherwise a scroll to one side of the page can have a swipe tacked on
+ // to it.
+ NSEventPhase eventPhase = nsCocoaUtils::EventPhase(anEvent);
+ if ([anEvent type] != NSScrollWheel ||
+ eventPhase != NSEventPhaseBegan ||
+ ![anEvent hasPreciseScrollingDeltas]) {
+ return false;
+ }
+
+ // Only initiate horizontal tracking for events whose horizontal element is
+ // at least eight times larger than its vertical element. This minimizes
+ // performance problems with vertical scrolls (by minimizing the possibility
+ // that they'll be misinterpreted as horizontal swipes), while still
+ // tolerating a small vertical element to a true horizontal swipe. The number
+ // '8' was arrived at by trial and error.
+ CGFloat deltaX = [anEvent scrollingDeltaX];
+ CGFloat deltaY = [anEvent scrollingDeltaY];
+ return std::abs(deltaX) > std::abs(deltaY) * 8;
+}
+
+- (void)setUsingOMTCompositor:(BOOL)aUseOMTC
+{
+ mUsingOMTCompositor = aUseOMTC;
+}
+
+// Returning NO from this method only disallows ordering on mousedown - in order
+// to prevent it for mouseup too, we need to call [NSApp preventWindowOrdering]
+// when handling the mousedown event.
+- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)aEvent
+{
+ // Always using system-provided window ordering for normal windows.
+ if (![[self window] isKindOfClass:[PopupWindow class]])
+ return NO;
+
+ // Don't reorder when we don't have a parent window, like when we're a
+ // context menu or a tooltip.
+ return ![[self window] parentWindow];
+}
+
+- (void)mouseDown:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([self shouldDelayWindowOrderingForEvent:theEvent]) {
+ [NSApp preventWindowOrdering];
+ }
+
+ // If we've already seen this event due to direct dispatch from menuForEvent:
+ // just bail; if not, remember it.
+ if (mLastMouseDownEvent == theEvent) {
+ [mLastMouseDownEvent release];
+ mLastMouseDownEvent = nil;
+ return;
+ }
+ else {
+ [mLastMouseDownEvent release];
+ mLastMouseDownEvent = [theEvent retain];
+ }
+
+ [gLastDragMouseDownEvent release];
+ gLastDragMouseDownEvent = [theEvent retain];
+
+ // We need isClickThrough because at this point the window we're in might
+ // already have become main, so the check for isMainWindow in
+ // WindowAcceptsEvent isn't enough. It also has to check isClickThrough.
+ BOOL isClickThrough = (theEvent == mClickThroughMouseDownEvent);
+ [mClickThroughMouseDownEvent release];
+ mClickThroughMouseDownEvent = nil;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ if ([self maybeRollup:theEvent] ||
+ !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self, isClickThrough)) {
+ // Remember blocking because that means we want to block mouseup as well.
+ mBlockedLastMouseDown = YES;
+ return;
+ }
+
+#if USE_CLICK_HOLD_CONTEXTMENU
+ // fire off timer to check for click-hold after two seconds. retains |theEvent|
+ [self performSelector:@selector(clickHoldCallback:) withObject:theEvent afterDelay:2.0];
+#endif
+
+ // in order to send gecko events we'll need a gecko widget
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ NSUInteger modifierFlags = [theEvent modifierFlags];
+
+ WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+
+ NSInteger clickCount = [theEvent clickCount];
+ if (mBlockedLastMouseDown && clickCount > 1) {
+ // Don't send a double click if the first click of the double click was
+ // blocked.
+ clickCount--;
+ }
+ geckoEvent.mClickCount = clickCount;
+
+ if (modifierFlags & NSControlKeyMask)
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ else
+ geckoEvent.button = WidgetMouseEvent::eLeftButton;
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ mBlockedLastMouseDown = NO;
+
+ // XXX maybe call markedTextSelectionChanged:client: here?
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)mouseUp:(NSEvent *)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild || mBlockedLastMouseDown)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ if ([theEvent modifierFlags] & NSControlKeyMask)
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ else
+ geckoEvent.button = WidgetMouseEvent::eLeftButton;
+
+ // This might destroy our widget (and null out mGeckoChild).
+ bool defaultPrevented =
+ (mGeckoChild->DispatchInputEvent(&geckoEvent) == nsEventStatus_eConsumeNoDefault);
+
+ // Check to see if we are double-clicking in the titlebar.
+ CGFloat locationInTitlebar = [[self window] frame].size.height - [theEvent locationInWindow].y;
+ LayoutDeviceIntPoint pos = geckoEvent.mRefPoint;
+ if (!defaultPrevented && [theEvent clickCount] == 2 &&
+ !mGeckoChild->GetNonDraggableRegion().Contains(pos.x, pos.y) &&
+ [[self window] isKindOfClass:[ToolbarWindow class]] &&
+ (locationInTitlebar < [(ToolbarWindow*)[self window] titlebarHeight] ||
+ locationInTitlebar < [(ToolbarWindow*)[self window] unifiedToolbarHeight])) {
+ if ([self shouldZoomOnDoubleClick]) {
+ [[self window] performZoom:nil];
+ } else if ([self shouldMinimizeOnTitlebarDoubleClick]) {
+ NSButton *minimizeButton = [[self window] standardWindowButton:NSWindowMiniaturizeButton];
+ [minimizeButton performClick:self];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
+ enter:(BOOL)aEnter
+ exitFrom:(WidgetMouseEvent::ExitFrom)aExitFrom
+{
+ if (!mGeckoChild)
+ return;
+
+ NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, [self window]);
+ NSPoint localEventLocation = [self convertPoint:windowEventLocation fromView:nil];
+
+ EventMessage msg = aEnter ? eMouseEnterIntoWidget : eMouseExitFromWidget;
+ WidgetMouseEvent event(true, msg, mGeckoChild, WidgetMouseEvent::eReal);
+ event.mRefPoint = mGeckoChild->CocoaPointsToDevPixels(localEventLocation);
+
+ event.mExitFrom = aExitFrom;
+
+ nsEventStatus status; // ignored
+ mGeckoChild->DispatchEvent(&event, status);
+}
+
+- (void)handleMouseMoved:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)mouseDragged:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ gLastDragView = self;
+
+ WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+
+ // Note, sending the above event might have destroyed our widget since we didn't retain.
+ // Fine so long as we don't access any local variables from here on.
+ gLastDragView = nil;
+
+ // XXX maybe call markedTextSelectionChanged:client: here?
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)rightMouseDown:(NSEvent *)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ [self maybeRollup:theEvent];
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ // The right mouse went down, fire off a right mouse down event to gecko
+ WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ geckoEvent.mClickCount = [theEvent clickCount];
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild)
+ return;
+
+ // Let the superclass do the context menu stuff.
+ [super rightMouseDown:theEvent];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)rightMouseUp:(NSEvent *)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ geckoEvent.mClickCount = [theEvent clickCount];
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)rightMouseDragged:(NSEvent*)theEvent
+{
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+
+ // send event into Gecko by going directly to the
+ // the widget.
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+}
+
+- (void)otherMouseDown:(NSEvent *)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ if ([self maybeRollup:theEvent] ||
+ !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self))
+ return;
+
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eMiddleButton;
+ geckoEvent.mClickCount = [theEvent clickCount];
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)otherMouseUp:(NSEvent *)theEvent
+{
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eMiddleButton;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+}
+
+- (void)otherMouseDragged:(NSEvent*)theEvent
+{
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eMiddleButton;
+
+ // send event into Gecko by going directly to the
+ // the widget.
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+}
+
+- (void)sendWheelStartOrStop:(EventMessage)msg forEvent:(NSEvent *)theEvent
+{
+ WidgetWheelEvent wheelEvent(true, msg, mGeckoChild);
+ [self convertCocoaMouseWheelEvent:theEvent toGeckoEvent:&wheelEvent];
+ mExpectingWheelStop = (msg == eWheelOperationStart);
+ mGeckoChild->DispatchInputEvent(wheelEvent.AsInputEvent());
+}
+
+- (void)sendWheelCondition:(BOOL)condition
+ first:(EventMessage)first
+ second:(EventMessage)second
+ forEvent:(NSEvent *)theEvent
+{
+ if (mExpectingWheelStop == condition) {
+ [self sendWheelStartOrStop:first forEvent:theEvent];
+ }
+ [self sendWheelStartOrStop:second forEvent:theEvent];
+}
+
+static PanGestureInput::PanGestureType
+PanGestureTypeForEvent(NSEvent* aEvent)
+{
+ switch (nsCocoaUtils::EventPhase(aEvent)) {
+ case NSEventPhaseMayBegin:
+ return PanGestureInput::PANGESTURE_MAYSTART;
+ case NSEventPhaseCancelled:
+ return PanGestureInput::PANGESTURE_CANCELLED;
+ case NSEventPhaseBegan:
+ return PanGestureInput::PANGESTURE_START;
+ case NSEventPhaseChanged:
+ return PanGestureInput::PANGESTURE_PAN;
+ case NSEventPhaseEnded:
+ return PanGestureInput::PANGESTURE_END;
+ case NSEventPhaseNone:
+ switch (nsCocoaUtils::EventMomentumPhase(aEvent)) {
+ case NSEventPhaseBegan:
+ return PanGestureInput::PANGESTURE_MOMENTUMSTART;
+ case NSEventPhaseChanged:
+ return PanGestureInput::PANGESTURE_MOMENTUMPAN;
+ case NSEventPhaseEnded:
+ return PanGestureInput::PANGESTURE_MOMENTUMEND;
+ default:
+ NS_ERROR("unexpected event phase");
+ return PanGestureInput::PANGESTURE_PAN;
+ }
+ default:
+ NS_ERROR("unexpected event phase");
+ return PanGestureInput::PANGESTURE_PAN;
+ }
+}
+
+static int32_t RoundUp(double aDouble)
+{
+ return aDouble < 0 ? static_cast<int32_t>(floor(aDouble)) :
+ static_cast<int32_t>(ceil(aDouble));
+}
+
+static int32_t
+TakeLargestInt(gfx::Float* aFloat)
+{
+ int32_t result(*aFloat); // truncate towards zero
+ *aFloat -= result;
+ return result;
+}
+
+static gfx::IntPoint
+AccumulateIntegerDelta(NSEvent* aEvent)
+{
+ static gfx::Point sAccumulator(0.0f, 0.0f);
+ if (nsCocoaUtils::EventPhase(aEvent) == NSEventPhaseBegan) {
+ sAccumulator = gfx::Point(0.0f, 0.0f);
+ }
+ sAccumulator.x += [aEvent deltaX];
+ sAccumulator.y += [aEvent deltaY];
+ return gfx::IntPoint(TakeLargestInt(&sAccumulator.x),
+ TakeLargestInt(&sAccumulator.y));
+}
+
+static gfx::IntPoint
+GetIntegerDeltaForEvent(NSEvent* aEvent)
+{
+ if (nsCocoaFeatures::OnSierraOrLater() && [aEvent hasPreciseScrollingDeltas]) {
+ // Pixel scroll events (events with hasPreciseScrollingDeltas == YES)
+ // carry pixel deltas in the scrollingDeltaX/Y fields and line scroll
+ // information in the deltaX/Y fields.
+ // Prior to 10.12, these line scroll fields would be zero for most pixel
+ // scroll events and non-zero for some, whenever at least a full line
+ // worth of pixel scrolling had accumulated. That's the behavior we want.
+ // Starting with 10.12 however, pixel scroll events no longer accumulate
+ // deltaX and deltaY; they just report floating point values for every
+ // single event. So we need to do our own accumulation.
+ return AccumulateIntegerDelta(aEvent);
+ }
+
+ // For line scrolls, or pre-10.12, just use the rounded up value of deltaX / deltaY.
+ return gfx::IntPoint(RoundUp([aEvent deltaX]), RoundUp([aEvent deltaY]));
+}
+
+- (void)scrollWheel:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (gfxPrefs::AsyncPanZoomSeparateEventThread() && [self apzctm]) {
+ // Disable main-thread scrolling completely when using APZ with the
+ // separate event thread. This is bug 1013412.
+ return;
+ }
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ ChildViewMouseTracker::MouseScrolled(theEvent);
+
+ if ([self maybeRollup:theEvent]) {
+ return;
+ }
+
+ if (!mGeckoChild) {
+ return;
+ }
+
+ NSEventPhase phase = nsCocoaUtils::EventPhase(theEvent);
+ // Fire eWheelOperationStart/End events when 2 fingers touch/release the
+ // touchpad.
+ if (phase & NSEventPhaseMayBegin) {
+ [self sendWheelCondition:YES
+ first:eWheelOperationEnd
+ second:eWheelOperationStart
+ forEvent:theEvent];
+ } else if (phase & (NSEventPhaseEnded | NSEventPhaseCancelled)) {
+ [self sendWheelCondition:NO
+ first:eWheelOperationStart
+ second:eWheelOperationEnd
+ forEvent:theEvent];
+ }
+
+ if (!mGeckoChild) {
+ return;
+ }
+ RefPtr<nsChildView> geckoChildDeathGrip(mGeckoChild);
+
+ NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(theEvent, [self window]);
+
+ // Use convertWindowCoordinatesRoundDown when converting the position to
+ // integer screen pixels in order to ensure that coordinates which are just
+ // inside the right / bottom edges of the window don't end up outside of the
+ // window after rounding.
+ ScreenPoint position = ViewAs<ScreenPixel>(
+ [self convertWindowCoordinatesRoundDown:locationInWindow],
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ bool usePreciseDeltas = nsCocoaUtils::HasPreciseScrollingDeltas(theEvent) &&
+ Preferences::GetBool("mousewheel.enable_pixel_scrolling", true);
+ bool hasPhaseInformation = nsCocoaUtils::EventHasPhaseInformation(theEvent);
+
+ gfx::IntPoint lineOrPageDelta = -GetIntegerDeltaForEvent(theEvent);
+
+ Modifiers modifiers = nsCocoaUtils::ModifiersForEvent(theEvent);
+
+ NSTimeInterval beforeNow = [[NSProcessInfo processInfo] systemUptime] - [theEvent timestamp];
+ PRIntervalTime eventIntervalTime = PR_IntervalNow() - PR_MillisecondsToInterval(beforeNow * 1000);
+ TimeStamp eventTimeStamp = TimeStamp::Now() - TimeDuration::FromSeconds(beforeNow);
+
+ ScreenPoint preciseDelta;
+ if (usePreciseDeltas) {
+ CGFloat pixelDeltaX = 0, pixelDeltaY = 0;
+ nsCocoaUtils::GetScrollingDeltas(theEvent, &pixelDeltaX, &pixelDeltaY);
+ double scale = geckoChildDeathGrip->BackingScaleFactor();
+ preciseDelta = ScreenPoint(-pixelDeltaX * scale, -pixelDeltaY * scale);
+ }
+
+ if (usePreciseDeltas && hasPhaseInformation) {
+ PanGestureInput panEvent(PanGestureTypeForEvent(theEvent),
+ eventIntervalTime, eventTimeStamp,
+ position, preciseDelta, modifiers);
+ panEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
+ panEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
+
+ if (panEvent.mType == PanGestureInput::PANGESTURE_END) {
+ // Check if there's a momentum start event in the event queue, so that we
+ // can annotate this event.
+ NSEvent* nextWheelEvent =
+ [NSApp nextEventMatchingMask:NSScrollWheelMask
+ untilDate:[NSDate distantPast]
+ inMode:NSDefaultRunLoopMode
+ dequeue:NO];
+ if (nextWheelEvent &&
+ PanGestureTypeForEvent(nextWheelEvent) == PanGestureInput::PANGESTURE_MOMENTUMSTART) {
+ panEvent.mFollowedByMomentum = true;
+ }
+ }
+
+ bool canTriggerSwipe = [self shouldConsiderStartingSwipeFromEvent:theEvent];
+ panEvent.mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection = canTriggerSwipe;
+ geckoChildDeathGrip->DispatchAPZWheelInputEvent(panEvent, canTriggerSwipe);
+ } else if (usePreciseDeltas) {
+ // This is on 10.6 or old touchpads that don't have any phase information.
+ ScrollWheelInput wheelEvent(eventIntervalTime, eventTimeStamp, modifiers,
+ ScrollWheelInput::SCROLLMODE_INSTANT,
+ ScrollWheelInput::SCROLLDELTA_PIXEL,
+ position,
+ preciseDelta.x,
+ preciseDelta.y,
+ false);
+ wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
+ wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
+ wheelEvent.mIsMomentum = nsCocoaUtils::IsMomentumScrollEvent(theEvent);
+ geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent, false);
+ } else {
+ ScrollWheelInput::ScrollMode scrollMode = ScrollWheelInput::SCROLLMODE_INSTANT;
+ if (gfxPrefs::SmoothScrollEnabled() && gfxPrefs::WheelSmoothScrollEnabled()) {
+ scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH;
+ }
+ ScrollWheelInput wheelEvent(eventIntervalTime, eventTimeStamp, modifiers,
+ scrollMode,
+ ScrollWheelInput::SCROLLDELTA_LINE,
+ position,
+ lineOrPageDelta.x,
+ lineOrPageDelta.y,
+ false);
+ wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
+ wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
+ geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent, false);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)handleAsyncScrollEvent:(CGEventRef)cgEvent ofType:(CGEventType)type
+{
+ IAPZCTreeManager* apzctm = [self apzctm];
+ if (!apzctm) {
+ return;
+ }
+
+ CGPoint loc = CGEventGetLocation(cgEvent);
+ loc.y = nsCocoaUtils::FlippedScreenY(loc.y);
+ NSPoint locationInWindow =
+ nsCocoaUtils::ConvertPointFromScreen([self window], NSPointFromCGPoint(loc));
+ ScreenIntPoint location = ViewAs<ScreenPixel>(
+ [self convertWindowCoordinates:locationInWindow],
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ static NSTimeInterval sStartTime = [NSDate timeIntervalSinceReferenceDate];
+ static TimeStamp sStartTimeStamp = TimeStamp::Now();
+
+ if (type == kCGEventScrollWheel) {
+ NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
+ NSEventPhase phase = nsCocoaUtils::EventPhase(event);
+ NSEventPhase momentumPhase = nsCocoaUtils::EventMomentumPhase(event);
+ CGFloat pixelDeltaX = 0, pixelDeltaY = 0;
+ nsCocoaUtils::GetScrollingDeltas(event, &pixelDeltaX, &pixelDeltaY);
+ uint32_t eventTime = ([event timestamp] - sStartTime) * 1000;
+ TimeStamp eventTimeStamp = sStartTimeStamp +
+ TimeDuration::FromSeconds([event timestamp] - sStartTime);
+ NSPoint locationInWindowMoved = NSMakePoint(
+ locationInWindow.x + pixelDeltaX,
+ locationInWindow.y - pixelDeltaY);
+ ScreenIntPoint locationMoved = ViewAs<ScreenPixel>(
+ [self convertWindowCoordinates:locationInWindowMoved],
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+ ScreenPoint delta = ScreenPoint(locationMoved - location);
+ ScrollableLayerGuid guid;
+
+ // MayBegin and Cancelled are dispatched when the fingers start or stop
+ // touching the touchpad before any scrolling has occurred. These events
+ // can be used to control scrollbar visibility or interrupt scroll
+ // animations. They are only dispatched on 10.8 or later, and only by
+ // relatively modern devices.
+ if (phase == NSEventPhaseMayBegin) {
+ PanGestureInput panInput(PanGestureInput::PANGESTURE_MAYSTART, eventTime,
+ eventTimeStamp, location, ScreenPoint(0, 0), 0);
+ apzctm->ReceiveInputEvent(panInput, &guid, nullptr);
+ return;
+ }
+ if (phase == NSEventPhaseCancelled) {
+ PanGestureInput panInput(PanGestureInput::PANGESTURE_CANCELLED, eventTime,
+ eventTimeStamp, location, ScreenPoint(0, 0), 0);
+ apzctm->ReceiveInputEvent(panInput, &guid, nullptr);
+ return;
+ }
+
+ // Legacy scroll events are dispatched by devices that do not have a
+ // concept of a scroll gesture, for example by USB mice with
+ // traditional mouse wheels.
+ // For these kinds of scrolls, we want to surround every single scroll
+ // event with a PANGESTURE_START and a PANGESTURE_END event. The APZC
+ // needs to know that the real scroll gesture can end abruptly after any
+ // one of these events.
+ bool isLegacyScroll = (phase == NSEventPhaseNone &&
+ momentumPhase == NSEventPhaseNone && delta != ScreenPoint(0, 0));
+
+ if (phase == NSEventPhaseBegan || isLegacyScroll) {
+ PanGestureInput panInput(PanGestureInput::PANGESTURE_START, eventTime,
+ eventTimeStamp, location, ScreenPoint(0, 0), 0);
+ apzctm->ReceiveInputEvent(panInput, &guid, nullptr);
+ }
+ if (momentumPhase == NSEventPhaseNone && delta != ScreenPoint(0, 0)) {
+ PanGestureInput panInput(PanGestureInput::PANGESTURE_PAN, eventTime,
+ eventTimeStamp, location, delta, 0);
+ apzctm->ReceiveInputEvent(panInput, &guid, nullptr);
+ }
+ if (phase == NSEventPhaseEnded || isLegacyScroll) {
+ PanGestureInput panInput(PanGestureInput::PANGESTURE_END, eventTime,
+ eventTimeStamp, location, ScreenPoint(0, 0), 0);
+ apzctm->ReceiveInputEvent(panInput, &guid, nullptr);
+ }
+
+ // Any device that can dispatch momentum events supports all three momentum phases.
+ if (momentumPhase == NSEventPhaseBegan) {
+ PanGestureInput panInput(PanGestureInput::PANGESTURE_MOMENTUMSTART, eventTime,
+ eventTimeStamp, location, ScreenPoint(0, 0), 0);
+ apzctm->ReceiveInputEvent(panInput, &guid, nullptr);
+ }
+ if (momentumPhase == NSEventPhaseChanged && delta != ScreenPoint(0, 0)) {
+ PanGestureInput panInput(PanGestureInput::PANGESTURE_MOMENTUMPAN, eventTime,
+ eventTimeStamp, location, delta, 0);
+ apzctm->ReceiveInputEvent(panInput, &guid, nullptr);
+ }
+ if (momentumPhase == NSEventPhaseEnded) {
+ PanGestureInput panInput(PanGestureInput::PANGESTURE_MOMENTUMEND, eventTime,
+ eventTimeStamp, location, ScreenPoint(0, 0), 0);
+ apzctm->ReceiveInputEvent(panInput, &guid, nullptr);
+ }
+ }
+}
+
+-(NSMenu*)menuForEvent:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mGeckoChild)
+ return nil;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ [self maybeRollup:theEvent];
+ if (!mGeckoChild)
+ return nil;
+
+ // Cocoa doesn't always dispatch a mouseDown: for a control-click event,
+ // depends on what we return from menuForEvent:. Gecko always expects one
+ // and expects the mouse down event before the context menu event, so
+ // get that event sent first if this is a left mouse click.
+ if ([theEvent type] == NSLeftMouseDown) {
+ [self mouseDown:theEvent];
+ if (!mGeckoChild)
+ return nil;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eContextMenu, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild)
+ return nil;
+
+ [self maybeInitContextMenuTracking];
+
+ // Go up our view chain to fetch the correct menu to return.
+ return [self contextMenu];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSMenu*)contextMenu
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSView* superView = [self superview];
+ if ([superView respondsToSelector:@selector(contextMenu)])
+ return [(NSView<mozView>*)superView contextMenu];
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void) convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent
+ toGeckoEvent:(WidgetWheelEvent*)outWheelEvent
+{
+ [self convertCocoaMouseEvent:aMouseEvent toGeckoEvent:outWheelEvent];
+
+ bool usePreciseDeltas = nsCocoaUtils::HasPreciseScrollingDeltas(aMouseEvent) &&
+ Preferences::GetBool("mousewheel.enable_pixel_scrolling", true);
+
+ outWheelEvent->mDeltaMode =
+ usePreciseDeltas ? nsIDOMWheelEvent::DOM_DELTA_PIXEL
+ : nsIDOMWheelEvent::DOM_DELTA_LINE;
+ outWheelEvent->mIsMomentum = nsCocoaUtils::IsMomentumScrollEvent(aMouseEvent);
+}
+
+- (void) convertCocoaMouseEvent:(NSEvent*)aMouseEvent
+ toGeckoEvent:(WidgetInputEvent*)outGeckoEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ASSERTION(outGeckoEvent, "convertCocoaMouseEvent:toGeckoEvent: requires non-null aoutGeckoEvent");
+ if (!outGeckoEvent)
+ return;
+
+ nsCocoaUtils::InitInputEvent(*outGeckoEvent, aMouseEvent);
+
+ // convert point to view coordinate system
+ NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(aMouseEvent, [self window]);
+
+ outGeckoEvent->mRefPoint = [self convertWindowCoordinates:locationInWindow];
+
+ WidgetMouseEventBase* mouseEvent = outGeckoEvent->AsMouseEventBase();
+ mouseEvent->buttons = 0;
+ NSUInteger mouseButtons = [NSEvent pressedMouseButtons];
+
+ if (mouseButtons & 0x01) {
+ mouseEvent->buttons |= WidgetMouseEvent::eLeftButtonFlag;
+ }
+ if (mouseButtons & 0x02) {
+ mouseEvent->buttons |= WidgetMouseEvent::eRightButtonFlag;
+ }
+ if (mouseButtons & 0x04) {
+ mouseEvent->buttons |= WidgetMouseEvent::eMiddleButtonFlag;
+ }
+ if (mouseButtons & 0x08) {
+ mouseEvent->buttons |= WidgetMouseEvent::e4thButtonFlag;
+ }
+ if (mouseButtons & 0x10) {
+ mouseEvent->buttons |= WidgetMouseEvent::e5thButtonFlag;
+ }
+
+ switch ([aMouseEvent type]) {
+ case NSLeftMouseDown:
+ case NSLeftMouseUp:
+ case NSLeftMouseDragged:
+ case NSRightMouseDown:
+ case NSRightMouseUp:
+ case NSRightMouseDragged:
+ case NSOtherMouseDown:
+ case NSOtherMouseUp:
+ case NSOtherMouseDragged:
+ if ([aMouseEvent subtype] == NSTabletPointEventSubtype) {
+ mouseEvent->pressure = [aMouseEvent pressure];
+ MOZ_ASSERT(mouseEvent->pressure >= 0.0 && mouseEvent->pressure <= 1.0);
+ }
+ break;
+
+ default:
+ // Don't check other NSEvents for pressure.
+ break;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)shouldZoomOnDoubleClick
+{
+ if ([NSWindow respondsToSelector:@selector(_shouldZoomOnDoubleClick)]) {
+ return [NSWindow _shouldZoomOnDoubleClick];
+ }
+ return nsCocoaFeatures::OnYosemiteOrLater();
+}
+
+- (BOOL)shouldMinimizeOnTitlebarDoubleClick
+{
+ NSString *MDAppleMiniaturizeOnDoubleClickKey =
+ @"AppleMiniaturizeOnDoubleClick";
+ NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+ bool shouldMinimize = [[userDefaults
+ objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue];
+
+ return shouldMinimize;
+}
+
+#pragma mark -
+// NSTextInputClient implementation
+
+- (NSRange)markedRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
+ return mTextInputHandler->MarkedRange();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
+}
+
+- (NSRange)selectedRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
+ return mTextInputHandler->SelectedRange();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
+}
+
+- (BOOL)drawsVerticallyForCharacterAtIndex:(NSUInteger)charIndex
+{
+ NS_ENSURE_TRUE(mTextInputHandler, NO);
+ if (charIndex == NSNotFound) {
+ return NO;
+ }
+ return mTextInputHandler->DrawsVerticallyForCharacterAtIndex(charIndex);
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
+{
+ NS_ENSURE_TRUE(mTextInputHandler, 0);
+ return mTextInputHandler->CharacterIndexForPoint(thePoint);
+}
+
+- (NSArray*)validAttributesForMarkedText
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NS_ENSURE_TRUE(mTextInputHandler, [NSArray array]);
+ return mTextInputHandler->GetValidAttributesForMarkedText();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE_VOID(mGeckoChild);
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ NSAttributedString* attrStr;
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ attrStr = static_cast<NSAttributedString*>(aString);
+ } else {
+ attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
+ }
+
+ mTextInputHandler->InsertText(attrStr, &replacementRange);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)doCommandBySelector:(SEL)aSelector
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild || !mTextInputHandler) {
+ return;
+ }
+
+ const char* sel = reinterpret_cast<const char*>(aSelector);
+ if (!mTextInputHandler->DoCommandBySelector(sel)) {
+ [super doCommandBySelector:aSelector];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)unmarkText
+{
+ NS_ENSURE_TRUE_VOID(mTextInputHandler);
+ mTextInputHandler->CommitIMEComposition();
+}
+
+- (BOOL) hasMarkedText
+{
+ NS_ENSURE_TRUE(mTextInputHandler, NO);
+ return mTextInputHandler->HasMarkedText();
+}
+
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange
+ replacementRange:(NSRange)replacementRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE_VOID(mTextInputHandler);
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ NSAttributedString* attrStr;
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ attrStr = static_cast<NSAttributedString*>(aString);
+ } else {
+ attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
+ }
+
+ mTextInputHandler->SetMarkedText(attrStr, selectedRange, &replacementRange);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)aRange
+ actualRange:(NSRangePointer)actualRange
+{
+ NS_ENSURE_TRUE(mTextInputHandler, nil);
+ return mTextInputHandler->GetAttributedSubstringFromRange(aRange,
+ actualRange);
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange
+ actualRange:(NSRangePointer)actualRange
+{
+ NS_ENSURE_TRUE(mTextInputHandler, NSMakeRect(0.0, 0.0, 0.0, 0.0));
+ return mTextInputHandler->FirstRectForCharacterRange(aRange, actualRange);
+}
+
+- (void)quickLookWithEvent:(NSEvent*)event
+{
+ // Show dictionary by current point
+ WidgetContentCommandEvent
+ contentCommandEvent(true, eContentCommandLookUpDictionary, mGeckoChild);
+ NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
+ contentCommandEvent.mRefPoint = mGeckoChild->CocoaPointsToDevPixels(point);
+ mGeckoChild->DispatchWindowEvent(contentCommandEvent);
+ // The widget might have been destroyed.
+}
+
+- (NSInteger)windowLevel
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mTextInputHandler, [[self window] level]);
+ return mTextInputHandler->GetWindowLevel();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
+}
+
+#pragma mark -
+
+// This is a private API that Cocoa uses.
+// Cocoa will call this after the menu system returns "NO" for "performKeyEquivalent:".
+// We want all they key events we can get so just return YES. In particular, this fixes
+// ctrl-tab - we don't get a "keyDown:" call for that without this.
+- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event
+{
+ return YES;
+}
+
+- (NSEvent*)lastKeyDownEvent
+{
+ return mLastKeyDownEvent;
+}
+
+- (void)keyDown:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mLastKeyDownEvent release];
+ mLastKeyDownEvent = [theEvent retain];
+
+ // Weird things can happen on keyboard input if the key window isn't in the
+ // current space. For example see bug 1056251. To get around this, always
+ // make sure that, if our window is key, it's also made frontmost. Doing
+ // this automatically switches to whatever space our window is in. Safari
+ // does something similar. Our window should normally always be key --
+ // otherwise why is the OS sending us a key down event? But it's just
+ // possible we're in Gecko's hidden window, so we check first.
+ NSWindow *viewWindow = [self window];
+ if (viewWindow && [viewWindow isKeyWindow]) {
+ [viewWindow orderWindow:NSWindowAbove relativeTo:0];
+ }
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ if (!Preferences::GetBool("intl.allow-insecure-text-input", false) &&
+ mGeckoChild && mTextInputHandler && mTextInputHandler->IsFocused()) {
+#ifdef MOZ_CRASHREPORTER
+ 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]);
+#endif
+ if (mGeckoChild->GetInputContext().IsPasswordEditor() &&
+ !TextInputHandler::IsSecureEventInputEnabled()) {
+ #define CRASH_MESSAGE "A password editor has focus, but not in secure input mode"
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("\nBug 893973: ") +
+ NS_LITERAL_CSTRING(CRASH_MESSAGE));
+ CrashReporter::AppendAppNotesToCrashReport(additionalInfo);
+#endif
+ 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"
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("\nBug 893973: ") +
+ NS_LITERAL_CSTRING(CRASH_MESSAGE));
+ CrashReporter::AppendAppNotesToCrashReport(additionalInfo);
+#endif
+ MOZ_CRASH(CRASH_MESSAGE);
+ #undef CRASH_MESSAGE
+ }
+ }
+#endif // #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ bool handled = false;
+ if (mGeckoChild && mTextInputHandler) {
+ handled = mTextInputHandler->HandleKeyDownEvent(theEvent);
+ }
+
+ // We always allow keyboard events to propagate to keyDown: but if they are not
+ // handled we give special Application menu items a chance to act.
+ if (!handled && sApplicationMenu) {
+ [sApplicationMenu performKeyEquivalent:theEvent];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)keyUp:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE(mGeckoChild, );
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ mTextInputHandler->HandleKeyUpEvent(theEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)insertNewline:(id)sender
+{
+ if (mTextInputHandler) {
+ NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"\n"];
+ mTextInputHandler->InsertText(attrStr);
+ [attrStr release];
+ }
+}
+
+- (void)flagsChanged:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE(mGeckoChild, );
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mTextInputHandler->HandleFlagsChanged(theEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL) isFirstResponder
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSResponder* resp = [[self window] firstResponder];
+ return (resp == (NSResponder*)self);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (BOOL)isDragInProgress
+{
+ if (!mDragService)
+ return NO;
+
+ nsCOMPtr<nsIDragSession> dragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(dragSession));
+ return dragSession != nullptr;
+}
+
+- (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent
+{
+ // If we're being destroyed assume the default -- return YES.
+ if (!mGeckoChild)
+ return YES;
+
+ WidgetMouseEvent geckoEvent(true, eMouseActivate, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:aEvent toGeckoEvent:&geckoEvent];
+ return (mGeckoChild->DispatchInputEvent(&geckoEvent) != nsEventStatus_eConsumeNoDefault);
+}
+
+// We must always call through to our superclass, even when mGeckoChild is
+// nil -- otherwise the keyboard focus can end up in the wrong NSView.
+- (BOOL)becomeFirstResponder
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [super becomeFirstResponder];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(YES);
+}
+
+- (void)viewsWindowDidBecomeKey
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ // check to see if the window implements the mozWindow protocol. This
+ // allows embedders to avoid re-entrant calls to -makeKeyAndOrderFront,
+ // which can happen because these activate calls propagate out
+ // to the embedder via nsIEmbeddingSiteWindow::SetFocus().
+ BOOL isMozWindow = [[self window] respondsToSelector:@selector(setSuppressMakeKeyFront:)];
+ if (isMozWindow)
+ [[self window] setSuppressMakeKeyFront:YES];
+
+ nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
+ if (listener)
+ listener->WindowActivated();
+
+ if (isMozWindow)
+ [[self window] setSuppressMakeKeyFront:NO];
+
+ if (mGeckoChild->GetInputContext().IsPasswordEditor()) {
+ TextInputHandler::EnableSecureEventInput();
+ } else {
+ TextInputHandler::EnsureSecureEventInputDisabled();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)viewsWindowDidResignKey
+{
+ if (!mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
+ if (listener)
+ listener->WindowDeactivated();
+
+ TextInputHandler::EnsureSecureEventInputDisabled();
+}
+
+// If the call to removeFromSuperview isn't delayed from nsChildView::
+// TearDownView(), the NSView hierarchy might get changed during calls to
+// [ChildView drawRect:], which leads to "beyond bounds" exceptions in
+// NSCFArray. For more info see bmo bug 373122. Apple's docs claim that
+// removeFromSuperviewWithoutNeedingDisplay "can be safely invoked during
+// display" (whatever "display" means). But it's _not_ true that it can be
+// safely invoked during calls to [NSView drawRect:]. We use
+// removeFromSuperview here because there's no longer any danger of being
+// "invoked during display", and because doing do clears up bmo bug 384343.
+- (void)delayedTearDown
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self removeFromSuperview];
+ [self release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark -
+
+// drag'n'drop stuff
+#define kDragServiceContractID "@mozilla.org/widget/dragservice;1"
+
+- (NSDragOperation)dragOperationFromDragAction:(int32_t)aDragAction
+{
+ if (nsIDragService::DRAGDROP_ACTION_LINK & aDragAction)
+ return NSDragOperationLink;
+ if (nsIDragService::DRAGDROP_ACTION_COPY & aDragAction)
+ return NSDragOperationCopy;
+ if (nsIDragService::DRAGDROP_ACTION_MOVE & aDragAction)
+ return NSDragOperationGeneric;
+ return NSDragOperationNone;
+}
+
+- (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint
+{
+ if (!mGeckoChild) {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+
+ NSPoint localPoint = [self convertPoint:aPoint fromView:nil];
+ return mGeckoChild->CocoaPointsToDevPixels(localPoint);
+}
+
+- (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint
+{
+ if (!mGeckoChild) {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+
+ NSPoint localPoint = [self convertPoint:aPoint fromView:nil];
+ return mGeckoChild->CocoaPointsToDevPixelsRoundDown(localPoint);
+}
+
+- (IAPZCTreeManager*)apzctm
+{
+ return mGeckoChild ? mGeckoChild->APZCTM() : nullptr;
+}
+
+// This is a utility function used by NSView drag event methods
+// to send events. It contains all of the logic needed for Gecko
+// dragging to work. Returns the appropriate cocoa drag operation code.
+- (NSDragOperation)doDragAction:(EventMessage)aMessage sender:(id)aSender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!mGeckoChild)
+ return NSDragOperationNone;
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView doDragAction: entered\n"));
+
+ if (!mDragService) {
+ CallGetService(kDragServiceContractID, &mDragService);
+ NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!");
+ if (!mDragService)
+ return NSDragOperationNone;
+ }
+
+ if (aMessage == eDragEnter) {
+ mDragService->StartDragSession();
+ }
+
+ nsCOMPtr<nsIDragSession> dragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(dragSession));
+ if (dragSession) {
+ if (aMessage == eDragOver) {
+ // fire the drag event at the source. Just ignore whether it was
+ // cancelled or not as there isn't actually a means to stop the drag
+ mDragService->FireDragEventAtSource(eDrag);
+ dragSession->SetCanDrop(false);
+ } else if (aMessage == eDrop) {
+ // We make the assumption that the dragOver handlers have correctly set
+ // the |canDrop| property of the Drag Session.
+ bool canDrop = false;
+ if (!NS_SUCCEEDED(dragSession->GetCanDrop(&canDrop)) || !canDrop) {
+ [self doDragAction:eDragExit sender:aSender];
+
+ nsCOMPtr<nsIDOMNode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ if (!sourceNode) {
+ mDragService->EndDragSession(false);
+ }
+ return NSDragOperationNone;
+ }
+ }
+
+ unsigned int modifierFlags = [[NSApp currentEvent] modifierFlags];
+ uint32_t action = nsIDragService::DRAGDROP_ACTION_MOVE;
+ // force copy = option, alias = cmd-option, default is move
+ if (modifierFlags & NSAlternateKeyMask) {
+ if (modifierFlags & NSCommandKeyMask)
+ action = nsIDragService::DRAGDROP_ACTION_LINK;
+ else
+ action = nsIDragService::DRAGDROP_ACTION_COPY;
+ }
+ dragSession->SetDragAction(action);
+ }
+
+ // set up gecko event
+ WidgetDragEvent geckoEvent(true, aMessage, mGeckoChild);
+ nsCocoaUtils::InitInputEvent(geckoEvent, [NSApp currentEvent]);
+
+ // Use our own coordinates in the gecko event.
+ // Convert event from gecko global coords to gecko view coords.
+ NSPoint draggingLoc = [aSender draggingLocation];
+
+ geckoEvent.mRefPoint = [self convertWindowCoordinates:draggingLoc];
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild)
+ return NSDragOperationNone;
+
+ if (dragSession) {
+ switch (aMessage) {
+ case eDragEnter:
+ case eDragOver: {
+ uint32_t dragAction;
+ dragSession->GetDragAction(&dragAction);
+
+ // If TakeChildProcessDragAction returns something other than
+ // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent
+ // to the child process and this event is also being sent to the child
+ // process. In this case, use the last event's action instead.
+ nsDragService* dragService = static_cast<nsDragService *>(mDragService);
+ int32_t childDragAction = dragService->TakeChildProcessDragAction();
+ if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
+ dragAction = childDragAction;
+ }
+
+ return [self dragOperationFromDragAction:dragAction];
+ }
+ case eDragExit:
+ case eDrop: {
+ nsCOMPtr<nsIDOMNode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ if (!sourceNode) {
+ // We're leaving a window while doing a drag that was
+ // initiated in a different app. End the drag session,
+ // since we're done with it for now (until the user
+ // drags back into mozilla).
+ mDragService->EndDragSession(false);
+ }
+ }
+ default:
+ break;
+ }
+ }
+
+ return NSDragOperationGeneric;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone);
+}
+
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingEntered: entered\n"));
+
+ // there should never be a globalDragPboard when "draggingEntered:" is
+ // called, but just in case we'll take care of it here.
+ [globalDragPboard release];
+
+ // Set the global drag pasteboard that will be used for this drag session.
+ // This will be set back to nil when the drag session ends (mouse exits
+ // the view or a drop happens within the view).
+ globalDragPboard = [[sender draggingPasteboard] retain];
+
+ return [self doDragAction:eDragEnter sender:sender];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone);
+}
+
+- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingUpdated: entered\n"));
+
+ return [self doDragAction:eDragOver sender:sender];
+}
+
+- (void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingExited: entered\n"));
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ [self doDragAction:eDragExit sender:sender];
+ NS_IF_RELEASE(mDragService);
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ BOOL handled = [self doDragAction:eDrop sender:sender] != NSDragOperationNone;
+ NS_IF_RELEASE(mDragService);
+ return handled;
+}
+
+// NSDraggingSource
+- (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint
+{
+ // Get the drag service if it isn't already cached. The drag service
+ // isn't cached when dragging over a different application.
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ if (!dragService) {
+ dragService = do_GetService(kDragServiceContractID);
+ }
+
+ if (dragService) {
+ NSPoint pnt = [NSEvent mouseLocation];
+ FlipCocoaScreenCoordinate(pnt);
+
+ LayoutDeviceIntPoint devPoint = mGeckoChild->CocoaPointsToDevPixels(pnt);
+ dragService->DragMoved(devPoint.x, devPoint.y);
+ }
+}
+
+// NSDraggingSource
+- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ gDraggedTransferables = nullptr;
+
+ NSEvent *currentEvent = [NSApp currentEvent];
+ gUserCancelledDrag = ([currentEvent type] == NSKeyDown &&
+ [currentEvent keyCode] == kVK_Escape);
+
+ if (!mDragService) {
+ CallGetService(kDragServiceContractID, &mDragService);
+ NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!");
+ }
+
+ if (mDragService) {
+ // set the dragend point from the current mouse location
+ nsDragService* dragService = static_cast<nsDragService *>(mDragService);
+ NSPoint pnt = [NSEvent mouseLocation];
+ FlipCocoaScreenCoordinate(pnt);
+ dragService->SetDragEndPoint(gfx::IntPoint::Round(pnt.x, pnt.y));
+
+ // XXX: dropEffect should be updated per |operation|.
+ // As things stand though, |operation| isn't well handled within "our"
+ // events, that is, when the drop happens within the window: it is set
+ // either to NSDragOperationGeneric or to NSDragOperationNone.
+ // For that reason, it's not yet possible to override dropEffect per the
+ // given OS value, and it's also unclear what's the correct dropEffect
+ // value for NSDragOperationGeneric that is passed by other applications.
+ // All that said, NSDragOperationNone is still reliable.
+ if (operation == NSDragOperationNone) {
+ nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
+ dragService->GetDataTransfer(getter_AddRefs(dataTransfer));
+ if (dataTransfer)
+ dataTransfer->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE);
+ }
+
+ mDragService->EndDragSession(true);
+ NS_RELEASE(mDragService);
+ }
+
+ [globalDragPboard release];
+ globalDragPboard = nil;
+ [gLastDragMouseDownEvent release];
+ gLastDragMouseDownEvent = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// NSDraggingSource
+// this is just implemented so we comply with the NSDraggingSource informal protocol
+- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
+{
+ return UINT_MAX;
+}
+
+// This method is a callback typically invoked in response to a drag ending on the desktop
+// or a Findow folder window; the argument passed is a path to the drop location, to be used
+// in constructing a complete pathname for the file(s) we want to create as a result of
+// the drag.
+- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsresult rv;
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView namesOfPromisedFilesDroppedAtDestination: entering callback for promised files\n"));
+
+ nsCOMPtr<nsIFile> targFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(targFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(targFile);
+ if (!macLocalFile) {
+ NS_ERROR("No Mac local file");
+ return nil;
+ }
+
+ if (!NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)dropDestination))) {
+ NS_ERROR("failed InitWithCFURL");
+ return nil;
+ }
+
+ if (!gDraggedTransferables)
+ return nil;
+
+ uint32_t transferableCount;
+ rv = gDraggedTransferables->GetLength(&transferableCount);
+ if (NS_FAILED(rv))
+ return nil;
+
+ for (uint32_t i = 0; i < transferableCount; i++) {
+ nsCOMPtr<nsITransferable> item = do_QueryElementAt(gDraggedTransferables, i);
+ if (!item) {
+ NS_ERROR("no transferable");
+ return nil;
+ }
+
+ item->SetTransferData(kFilePromiseDirectoryMime, macLocalFile, sizeof(nsIFile*));
+
+ // now request the kFilePromiseMime data, which will invoke the data provider
+ // If successful, the returned data is a reference to the resulting file.
+ nsCOMPtr<nsISupports> fileDataPrimitive;
+ uint32_t dataSize = 0;
+ item->GetTransferData(kFilePromiseMime, getter_AddRefs(fileDataPrimitive), &dataSize);
+ }
+
+ NSPasteboard* generalPboard = [NSPasteboard pasteboardWithName:NSDragPboard];
+ NSData* data = [generalPboard dataForType:@"application/x-moz-file-promise-dest-filename"];
+ NSString* name = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSArray* rslt = [NSArray arrayWithObject:name];
+
+ [name release];
+
+ return rslt;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+#pragma mark -
+
+// Support for the "Services" menu. We currently only support sending strings
+// and HTML to system services.
+
+- (id)validRequestorForSendType:(NSString *)sendType
+ returnType:(NSString *)returnType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // sendType contains the type of data that the service would like this
+ // application to send to it. sendType is nil if the service is not
+ // requesting any data.
+ //
+ // returnType contains the type of data the the service would like to
+ // return to this application (e.g., to overwrite the selection).
+ // returnType is nil if the service will not return any data.
+ //
+ // The following condition thus triggers when the service expects a string
+ // or HTML from us or no data at all AND when the service will either not
+ // send back any data to us or will send a string or HTML back to us.
+
+#define IsSupportedType(typeStr) ([typeStr isEqual:NSStringPboardType] || [typeStr isEqual:NSHTMLPboardType])
+
+ id result = nil;
+
+ if ((!sendType || IsSupportedType(sendType)) &&
+ (!returnType || IsSupportedType(returnType))) {
+ if (mGeckoChild) {
+ // Assume that this object will be able to handle this request.
+ result = self;
+
+ // Keep the ChildView alive during this operation.
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ if (sendType) {
+ // Determine if there is a current selection (chrome/content).
+ if (!nsClipboard::sSelectionCache) {
+ result = nil;
+ }
+ }
+
+ // Determine if we can paste (if receiving data from the service).
+ if (mGeckoChild && returnType) {
+ WidgetContentCommandEvent command(true,
+ eContentCommandPasteTransferable,
+ mGeckoChild, true);
+ // This might possibly destroy our widget (and null out mGeckoChild).
+ mGeckoChild->DispatchWindowEvent(command);
+ if (!mGeckoChild || !command.mSucceeded || !command.mIsEnabled)
+ result = nil;
+ }
+ }
+ }
+
+#undef IsSupportedType
+
+ // Give the superclass a chance if this object will not handle this request.
+ if (!result)
+ result = [super validRequestorForSendType:sendType returnType:returnType];
+
+ return result;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
+ types:(NSArray *)types
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ // Make sure that the service will accept strings or HTML.
+ if ([types containsObject:NSStringPboardType] == NO &&
+ [types containsObject:NSHTMLPboardType] == NO)
+ return NO;
+
+ // Bail out if there is no Gecko object.
+ if (!mGeckoChild)
+ return NO;
+
+ // Transform the transferable to an NSDictionary.
+ NSDictionary* pasteboardOutputDict = nullptr;
+
+ pasteboardOutputDict = nsClipboard::
+ PasteboardDictFromTransferable(nsClipboard::sSelectionCache);
+
+ if (!pasteboardOutputDict)
+ return NO;
+
+ // Declare the pasteboard types.
+ unsigned int typeCount = [pasteboardOutputDict count];
+ NSMutableArray* declaredTypes = [NSMutableArray arrayWithCapacity:typeCount];
+ [declaredTypes addObjectsFromArray:[pasteboardOutputDict allKeys]];
+ [pboard declareTypes:declaredTypes owner:nil];
+
+ // Write the data to the pasteboard.
+ for (unsigned int i = 0; i < typeCount; i++) {
+ NSString* currentKey = [declaredTypes objectAtIndex:i];
+ id currentValue = [pasteboardOutputDict valueForKey:currentKey];
+
+ if (currentKey == NSStringPboardType ||
+ currentKey == kCorePboardType_url ||
+ currentKey == kCorePboardType_urld ||
+ currentKey == kCorePboardType_urln) {
+ [pboard setString:currentValue forType:currentKey];
+ } else if (currentKey == NSHTMLPboardType) {
+ [pboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue)) forType:currentKey];
+ } else if (currentKey == NSTIFFPboardType) {
+ [pboard setData:currentValue forType:currentKey];
+ } else if (currentKey == NSFilesPromisePboardType) {
+ [pboard setPropertyList:currentValue forType:currentKey];
+ }
+ }
+
+ return YES;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// Called if the service wants us to replace the current selection.
+- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
+{
+ nsresult rv;
+ nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
+ if (NS_FAILED(rv))
+ return NO;
+ trans->Init(nullptr);
+
+ trans->AddDataFlavor(kUnicodeMime);
+ trans->AddDataFlavor(kHTMLMime);
+
+ rv = nsClipboard::TransferableFromPasteboard(trans, pboard);
+ if (NS_FAILED(rv))
+ return NO;
+
+ NS_ENSURE_TRUE(mGeckoChild, false);
+
+ WidgetContentCommandEvent command(true,
+ eContentCommandPasteTransferable,
+ mGeckoChild);
+ command.mTransferable = trans;
+ mGeckoChild->DispatchWindowEvent(command);
+
+ return command.mSucceeded && command.mIsEnabled;
+}
+
+NS_IMETHODIMP
+nsChildView::GetSelectionAsPlaintext(nsAString& aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!nsClipboard::sSelectionCache) {
+ MOZ_ASSERT(aResult.IsEmpty());
+ return NS_OK;
+ }
+
+ // Get the current chrome or content selection.
+ NSDictionary* pasteboardOutputDict = nullptr;
+ pasteboardOutputDict = nsClipboard::
+ PasteboardDictFromTransferable(nsClipboard::sSelectionCache);
+
+ if (NS_WARN_IF(!pasteboardOutputDict)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Declare the pasteboard types.
+ unsigned int typeCount = [pasteboardOutputDict count];
+ NSMutableArray* declaredTypes = [NSMutableArray arrayWithCapacity:typeCount];
+ [declaredTypes addObjectsFromArray:[pasteboardOutputDict allKeys]];
+ NSString* currentKey = [declaredTypes objectAtIndex:0];
+ NSString* currentValue = [pasteboardOutputDict valueForKey:currentKey];
+ const char* textSelection = [currentValue UTF8String];
+ aResult = NS_ConvertUTF8toUTF16(textSelection);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+#pragma mark -
+
+#ifdef ACCESSIBILITY
+
+/* Every ChildView has a corresponding mozDocAccessible object that is doing all
+ the heavy lifting. The topmost ChildView corresponds to a mozRootAccessible
+ object.
+
+ All ChildView needs to do is to route all accessibility calls (from the NSAccessibility APIs)
+ down to its object, pretending that they are the same.
+*/
+- (id<mozAccessible>)accessible
+{
+ if (!mGeckoChild)
+ return nil;
+
+ id<mozAccessible> nativeAccessible = nil;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ RefPtr<nsChildView> geckoChild(mGeckoChild);
+ RefPtr<a11y::Accessible> accessible = geckoChild->GetDocumentAccessible();
+ if (!accessible)
+ return nil;
+
+ accessible->GetNativeInterface((void**)&nativeAccessible);
+
+#ifdef DEBUG_hakan
+ NSAssert(![nativeAccessible isExpired], @"native acc is expired!!!");
+#endif
+
+ return nativeAccessible;
+}
+
+/* Implementation of formal mozAccessible formal protocol (enabling mozViews
+ to talk to mozAccessible objects in the accessibility module). */
+
+- (BOOL)hasRepresentedView
+{
+ return YES;
+}
+
+- (id)representedView
+{
+ return self;
+}
+
+- (BOOL)isRoot
+{
+ return [[self accessible] isRoot];
+}
+
+#ifdef DEBUG
+- (void)printHierarchy
+{
+ [[self accessible] printHierarchy];
+}
+#endif
+
+#pragma mark -
+
+// general
+
+- (BOOL)accessibilityIsIgnored
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityIsIgnored];
+
+ return [[self accessible] accessibilityIsIgnored];
+}
+
+- (id)accessibilityHitTest:(NSPoint)point
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityHitTest:point];
+
+ return [[self accessible] accessibilityHitTest:point];
+}
+
+- (id)accessibilityFocusedUIElement
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityFocusedUIElement];
+
+ return [[self accessible] accessibilityFocusedUIElement];
+}
+
+// actions
+
+- (NSArray*)accessibilityActionNames
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityActionNames];
+
+ return [[self accessible] accessibilityActionNames];
+}
+
+- (NSString*)accessibilityActionDescription:(NSString*)action
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityActionDescription:action];
+
+ return [[self accessible] accessibilityActionDescription:action];
+}
+
+- (void)accessibilityPerformAction:(NSString*)action
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityPerformAction:action];
+
+ return [[self accessible] accessibilityPerformAction:action];
+}
+
+// attributes
+
+- (NSArray*)accessibilityAttributeNames
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityAttributeNames];
+
+ return [[self accessible] accessibilityAttributeNames];
+}
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityIsAttributeSettable:attribute];
+
+ return [[self accessible] accessibilityIsAttributeSettable:attribute];
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityAttributeValue:attribute];
+
+ id<mozAccessible> accessible = [self accessible];
+
+ // if we're the root (topmost) accessible, we need to return our native AXParent as we
+ // traverse outside to the hierarchy of whoever embeds us. thus, fall back on NSView's
+ // default implementation for this attribute.
+ if ([attribute isEqualToString:NSAccessibilityParentAttribute] && [accessible isRoot]) {
+ id parentAccessible = [super accessibilityAttributeValue:attribute];
+ return parentAccessible;
+ }
+
+ return [accessible accessibilityAttributeValue:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+#endif /* ACCESSIBILITY */
+
+@end
+
+#pragma mark -
+
+void
+ChildViewMouseTracker::OnDestroyView(ChildView* aView)
+{
+ if (sLastMouseEventView == aView) {
+ sLastMouseEventView = nil;
+ [sLastMouseMoveEvent release];
+ sLastMouseMoveEvent = nil;
+ }
+}
+
+void
+ChildViewMouseTracker::OnDestroyWindow(NSWindow* aWindow)
+{
+ if (sWindowUnderMouse == aWindow) {
+ sWindowUnderMouse = nil;
+ }
+}
+
+void
+ChildViewMouseTracker::MouseEnteredWindow(NSEvent* aEvent)
+{
+ sWindowUnderMouse = [aEvent window];
+ ReEvaluateMouseEnterState(aEvent);
+}
+
+void
+ChildViewMouseTracker::MouseExitedWindow(NSEvent* aEvent)
+{
+ if (sWindowUnderMouse == [aEvent window]) {
+ sWindowUnderMouse = nil;
+ ReEvaluateMouseEnterState(aEvent);
+ }
+}
+
+void
+ChildViewMouseTracker::ReEvaluateMouseEnterState(NSEvent* aEvent, ChildView* aOldView)
+{
+ ChildView* oldView = aOldView ? aOldView : sLastMouseEventView;
+ sLastMouseEventView = ViewForEvent(aEvent);
+ if (sLastMouseEventView != oldView) {
+ // Send enter and / or exit events.
+ WidgetMouseEvent::ExitFrom exitFrom =
+ [sLastMouseEventView window] == [oldView window] ?
+ WidgetMouseEvent::eChild : WidgetMouseEvent::eTopLevel;
+ [oldView sendMouseEnterOrExitEvent:aEvent
+ enter:NO
+ exitFrom:exitFrom];
+ // After the cursor exits the window set it to a visible regular arrow cursor.
+ if (exitFrom == WidgetMouseEvent::eTopLevel) {
+ [[nsCursorManager sharedInstance] setCursor:eCursor_standard];
+ }
+ [sLastMouseEventView sendMouseEnterOrExitEvent:aEvent
+ enter:YES
+ exitFrom:exitFrom];
+ }
+}
+
+void
+ChildViewMouseTracker::ResendLastMouseMoveEvent()
+{
+ if (sLastMouseMoveEvent) {
+ MouseMoved(sLastMouseMoveEvent);
+ }
+}
+
+void
+ChildViewMouseTracker::MouseMoved(NSEvent* aEvent)
+{
+ MouseEnteredWindow(aEvent);
+ [sLastMouseEventView handleMouseMoved:aEvent];
+ if (sLastMouseMoveEvent != aEvent) {
+ [sLastMouseMoveEvent release];
+ sLastMouseMoveEvent = [aEvent retain];
+ }
+}
+
+void
+ChildViewMouseTracker::MouseScrolled(NSEvent* aEvent)
+{
+ if (!nsCocoaUtils::IsMomentumScrollEvent(aEvent)) {
+ // Store the position so we can pin future momentum scroll events.
+ sLastScrollEventScreenLocation = nsCocoaUtils::ScreenLocationForEvent(aEvent);
+ }
+}
+
+ChildView*
+ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent)
+{
+ NSWindow* window = sWindowUnderMouse;
+ if (!window)
+ return nil;
+
+ NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window);
+ NSView* view = [[[window contentView] superview] hitTest:windowEventLocation];
+
+ if (![view isKindOfClass:[ChildView class]])
+ return nil;
+
+ ChildView* childView = (ChildView*)view;
+ // If childView is being destroyed return nil.
+ if (![childView widget])
+ return nil;
+ return WindowAcceptsEvent(window, aEvent, childView) ? childView : nil;
+}
+
+BOOL
+ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent,
+ ChildView* aView, BOOL aIsClickThrough)
+{
+ // Right mouse down events may get through to all windows, even to a top level
+ // window with an open sheet.
+ if (!aWindow || [aEvent type] == NSRightMouseDown)
+ return YES;
+
+ id delegate = [aWindow delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
+ return YES;
+
+ nsIWidget *windowWidget = [(WindowDelegate *)delegate geckoWidget];
+ if (!windowWidget)
+ return YES;
+
+ NSWindow* topLevelWindow = nil;
+
+ switch (windowWidget->WindowType()) {
+ case eWindowType_popup:
+ // If this is a context menu, it won't have a parent. So we'll always
+ // accept mouse move events on context menus even when none of our windows
+ // is active, which is the right thing to do.
+ // For panels, the parent window is the XUL window that owns the panel.
+ return WindowAcceptsEvent([aWindow parentWindow], aEvent, aView, aIsClickThrough);
+
+ case eWindowType_toplevel:
+ case eWindowType_dialog:
+ if ([aWindow attachedSheet])
+ return NO;
+
+ topLevelWindow = aWindow;
+ break;
+ case eWindowType_sheet: {
+ nsIWidget* parentWidget = windowWidget->GetSheetWindowParent();
+ if (!parentWidget)
+ return YES;
+
+ topLevelWindow = (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW);
+ break;
+ }
+
+ default:
+ return YES;
+ }
+
+ if (!topLevelWindow ||
+ ([topLevelWindow isMainWindow] && !aIsClickThrough) ||
+ [aEvent type] == NSOtherMouseDown ||
+ (([aEvent modifierFlags] & NSCommandKeyMask) != 0 &&
+ [aEvent type] != NSMouseMoved))
+ return YES;
+
+ // If we're here then we're dealing with a left click or mouse move on an
+ // inactive window or something similar. Ask Gecko what to do.
+ return [aView inactiveWindowAcceptsMouseEvent:aEvent];
+}
+
+#pragma mark -
+
+@interface EventThreadRunner(Private)
+- (void)runEventThread;
+- (void)shutdownAndReleaseCalledOnEventThread;
+- (void)shutdownAndReleaseCalledOnAnyThread;
+- (void)handleEvent:(CGEventRef)cgEvent type:(CGEventType)type;
+@end
+
+static EventThreadRunner* sEventThreadRunner = nil;
+
+@implementation EventThreadRunner
+
++ (void)start
+{
+ sEventThreadRunner = [[EventThreadRunner alloc] init];
+}
+
++ (void)stop
+{
+ if (sEventThreadRunner) {
+ [sEventThreadRunner shutdownAndReleaseCalledOnAnyThread];
+ sEventThreadRunner = nil;
+ }
+}
+
+- (id)init
+{
+ if ((self = [super init])) {
+ mThread = nil;
+ [NSThread detachNewThreadSelector:@selector(runEventThread)
+ toTarget:self
+ withObject:nil];
+ }
+ return self;
+}
+
+static CGEventRef
+HandleEvent(CGEventTapProxy aProxy, CGEventType aType,
+ CGEventRef aEvent, void* aClosure)
+{
+ [(EventThreadRunner*)aClosure handleEvent:aEvent type:aType];
+ return aEvent;
+}
+
+- (void)runEventThread
+{
+ char aLocal;
+ profiler_register_thread("APZC Event Thread", &aLocal);
+ PR_SetCurrentThreadName("APZC Event Thread");
+
+ mThread = [NSThread currentThread];
+ ProcessSerialNumber currentProcess;
+ GetCurrentProcess(&currentProcess);
+ CFMachPortRef eventPort =
+ CGEventTapCreateForPSN(&currentProcess,
+ kCGHeadInsertEventTap,
+ kCGEventTapOptionListenOnly,
+ CGEventMaskBit(kCGEventScrollWheel),
+ HandleEvent,
+ self);
+ CFRunLoopSourceRef eventPortSource =
+ CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, eventPort, 0);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), eventPortSource, kCFRunLoopCommonModes);
+ CFRunLoopRun();
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventPortSource, kCFRunLoopCommonModes);
+ CFRelease(eventPortSource);
+ CFRelease(eventPort);
+ [self release];
+}
+
+- (void)shutdownAndReleaseCalledOnEventThread
+{
+ CFRunLoopStop(CFRunLoopGetCurrent());
+}
+
+- (void)shutdownAndReleaseCalledOnAnyThread
+{
+ [self performSelector:@selector(shutdownAndReleaseCalledOnEventThread) onThread:mThread withObject:nil waitUntilDone:NO];
+}
+
+static const CGEventField kCGWindowNumberField = (const CGEventField) 51;
+
+// Called on scroll thread
+- (void)handleEvent:(CGEventRef)cgEvent type:(CGEventType)type
+{
+ if (type != kCGEventScrollWheel) {
+ return;
+ }
+
+ int windowNumber = CGEventGetIntegerValueField(cgEvent, kCGWindowNumberField);
+ NSWindow* window = [NSApp windowWithWindowNumber:windowNumber];
+ if (!window || ![window isKindOfClass:[BaseWindow class]]) {
+ return;
+ }
+
+ ChildView* childView = [(BaseWindow*)window mainChildView];
+ [childView handleAsyncScrollEvent:cgEvent ofType:type];
+}
+
+@end
+
+@interface NSView (MethodSwizzling)
+- (BOOL)nsChildView_NSView_mouseDownCanMoveWindow;
+@end
+
+@implementation NSView (MethodSwizzling)
+
+// All top-level browser windows belong to the ToolbarWindow class and have
+// NSTexturedBackgroundWindowMask turned on in their "style" (see particularly
+// [ToolbarWindow initWithContentRect:...] in nsCocoaWindow.mm). This style
+// normally means the window "may be moved by clicking and dragging anywhere
+// in the window background", but we've suppressed this by giving the
+// ChildView class a mouseDownCanMoveWindow method that always returns NO.
+// Normally a ToolbarWindow's contentView (not a ChildView) returns YES when
+// NSTexturedBackgroundWindowMask is turned on. But normally this makes no
+// difference. However, under some (probably very unusual) circumstances
+// (and only on Leopard) it *does* make a difference -- for example it
+// triggers bmo bugs 431902 and 476393. So here we make sure that a
+// ToolbarWindow's contentView always returns NO from the
+// mouseDownCanMoveWindow method.
+- (BOOL)nsChildView_NSView_mouseDownCanMoveWindow
+{
+ NSWindow *ourWindow = [self window];
+ NSView *contentView = [ourWindow contentView];
+ if ([ourWindow isKindOfClass:[ToolbarWindow class]] && (self == contentView))
+ return [ourWindow isMovableByWindowBackground];
+ return [self nsChildView_NSView_mouseDownCanMoveWindow];
+}
+
+@end
+
+#ifdef __LP64__
+// When using blocks, at least on OS X 10.7, the OS sometimes calls
+// +[NSEvent removeMonitor:] more than once on a single event monitor, which
+// causes crashes. See bug 678607. We hook these methods to work around
+// the problem.
+@interface NSEvent (MethodSwizzling)
++ (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block;
++ (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor;
+@end
+
+// This is a local copy of the AppKit frameworks sEventObservers hashtable.
+// It only stores "local monitors". We use it to ensure that +[NSEvent
+// removeMonitor:] is never called more than once on the same local monitor.
+static NSHashTable *sLocalEventObservers = nil;
+
+@implementation NSEvent (MethodSwizzling)
+
++ (id)nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:(unsigned long long)mask handler:(id)block
+{
+ if (!sLocalEventObservers) {
+ sLocalEventObservers = [[NSHashTable hashTableWithOptions:
+ NSHashTableStrongMemory | NSHashTableObjectPointerPersonality] retain];
+ }
+ id retval =
+ [self nsChildView_NSEvent_addLocalMonitorForEventsMatchingMask:mask handler:block];
+ if (sLocalEventObservers && retval && ![sLocalEventObservers containsObject:retval]) {
+ [sLocalEventObservers addObject:retval];
+ }
+ return retval;
+}
+
++ (void)nsChildView_NSEvent_removeMonitor:(id)eventMonitor
+{
+ if (sLocalEventObservers && [eventMonitor isKindOfClass: ::NSClassFromString(@"_NSLocalEventObserver")]) {
+ if (![sLocalEventObservers containsObject:eventMonitor]) {
+ return;
+ }
+ [sLocalEventObservers removeObject:eventMonitor];
+ }
+ [self nsChildView_NSEvent_removeMonitor:eventMonitor];
+}
+
+@end
+#endif // #ifdef __LP64__
diff --git a/widget/cocoa/nsClipboard.h b/widget/cocoa/nsClipboard.h
new file mode 100644
index 000000000..45871efe1
--- /dev/null
+++ b/widget/cocoa/nsClipboard.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsClipboard_h_
+#define nsClipboard_h_
+
+#include "nsIClipboard.h"
+#include "nsXPIDLString.h"
+#include "mozilla/StaticPtr.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsITransferable;
+
+class nsClipboard : public nsIClipboard
+{
+
+public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ // On macOS, cache the transferable of the current selection (chrome/content)
+ // in the parent process. This is needed for the services menu which
+ // requires synchronous access to the current selection.
+ static mozilla::StaticRefPtr<nsITransferable> sSelectionCache;
+
+ // Helper methods, used also by nsDragService
+ static NSDictionary* PasteboardDictFromTransferable(nsITransferable *aTransferable);
+ static bool IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType);
+ static NSString* WrapHtmlForSystemPasteboard(NSString* aString);
+ static nsresult TransferableFromPasteboard(nsITransferable *aTransferable, NSPasteboard *pboard);
+
+protected:
+
+ // impelement the native clipboard behavior
+ NS_IMETHOD SetNativeClipboardData(int32_t aWhichClipboard);
+ NS_IMETHOD GetNativeClipboardData(nsITransferable * aTransferable, int32_t aWhichClipboard);
+ void ClearSelectionCache();
+ void SetSelectionCache(nsITransferable* aTransferable);
+
+private:
+ virtual ~nsClipboard();
+ int32_t mCachedClipboard;
+ int32_t mChangeCount; // Set to the native change count after any modification of the clipboard.
+
+ bool mIgnoreEmptyNotification;
+ nsCOMPtr<nsIClipboardOwner> mClipboardOwner;
+ nsCOMPtr<nsITransferable> mTransferable;
+};
+
+#endif // nsClipboard_h_
diff --git a/widget/cocoa/nsClipboard.mm b/widget/cocoa/nsClipboard.mm
new file mode 100644
index 000000000..4146f1785
--- /dev/null
+++ b/widget/cocoa/nsClipboard.mm
@@ -0,0 +1,775 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include "mozilla/Unused.h"
+
+#include "gfxPlatform.h"
+#include "nsArrayUtils.h"
+#include "nsCOMPtr.h"
+#include "nsClipboard.h"
+#include "nsString.h"
+#include "nsISupportsPrimitives.h"
+#include "nsXPIDLString.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsMemory.h"
+#include "nsIFile.h"
+#include "nsStringStream.h"
+#include "nsDragService.h"
+#include "nsEscape.h"
+#include "nsPrintfCString.h"
+#include "nsObjCExceptions.h"
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::SourceSurface;
+using mozilla::LogLevel;
+
+// Screenshots use the (undocumented) png pasteboard type.
+#define IMAGE_PASTEBOARD_TYPES NSTIFFPboardType, @"Apple PNG pasteboard type", nil
+
+extern PRLogModuleInfo* sCocoaLog;
+
+extern void EnsureLogInitialized();
+
+mozilla::StaticRefPtr<nsITransferable> nsClipboard::sSelectionCache;
+
+nsClipboard::nsClipboard()
+ : mCachedClipboard(-1)
+ , mChangeCount(0)
+ , mIgnoreEmptyNotification(false)
+{
+ EnsureLogInitialized();
+}
+
+nsClipboard::~nsClipboard()
+{
+ EmptyClipboard(kGlobalClipboard);
+ EmptyClipboard(kFindClipboard);
+ ClearSelectionCache();
+}
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
+
+// We separate this into its own function because after an @try, all local
+// variables within that function get marked as volatile, and our C++ type
+// system doesn't like volatile things.
+static NSData*
+GetDataFromPasteboard(NSPasteboard* aPasteboard, NSString* aType)
+{
+ NSData *data = nil;
+ @try {
+ data = [aPasteboard dataForType:aType];
+ } @catch (NSException* e) {
+ NS_WARNING(nsPrintfCString("Exception raised while getting data from the pasteboard: \"%s - %s\"",
+ [[e name] UTF8String], [[e reason] UTF8String]).get());
+ mozilla::Unused << e;
+ }
+ return data;
+}
+
+void
+nsClipboard::SetSelectionCache(nsITransferable *aTransferable)
+{
+ sSelectionCache = aTransferable;
+}
+
+void
+nsClipboard::ClearSelectionCache()
+{
+ sSelectionCache = nullptr;
+}
+
+NS_IMETHODIMP
+nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !mTransferable)
+ return NS_ERROR_FAILURE;
+
+ mIgnoreEmptyNotification = true;
+
+ NSDictionary* pasteboardOutputDict = PasteboardDictFromTransferable(mTransferable);
+ if (!pasteboardOutputDict)
+ return NS_ERROR_FAILURE;
+
+ unsigned int outputCount = [pasteboardOutputDict count];
+ NSArray* outputKeys = [pasteboardOutputDict allKeys];
+ NSPasteboard* cocoaPasteboard;
+ if (aWhichClipboard == kFindClipboard) {
+ cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
+ [cocoaPasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
+ } else {
+ // Write everything else out to the general pasteboard.
+ cocoaPasteboard = [NSPasteboard generalPasteboard];
+ [cocoaPasteboard declareTypes:outputKeys owner:nil];
+ }
+
+ for (unsigned int i = 0; i < outputCount; i++) {
+ NSString* currentKey = [outputKeys objectAtIndex:i];
+ id currentValue = [pasteboardOutputDict valueForKey:currentKey];
+ if (aWhichClipboard == kFindClipboard) {
+ if (currentKey == NSStringPboardType)
+ [cocoaPasteboard setString:currentValue forType:currentKey];
+ } else {
+ if (currentKey == NSStringPboardType ||
+ currentKey == kCorePboardType_url ||
+ currentKey == kCorePboardType_urld ||
+ currentKey == kCorePboardType_urln) {
+ [cocoaPasteboard setString:currentValue forType:currentKey];
+ } else if (currentKey == NSHTMLPboardType) {
+ [cocoaPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
+ forType:currentKey];
+ } else {
+ [cocoaPasteboard setData:currentValue forType:currentKey];
+ }
+ }
+ }
+
+ mCachedClipboard = aWhichClipboard;
+ mChangeCount = [cocoaPasteboard changeCount];
+
+ mIgnoreEmptyNotification = false;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsClipboard::TransferableFromPasteboard(nsITransferable *aTransferable, NSPasteboard *cocoaPasteboard)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+
+ for (uint32_t i = 0; i < flavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr)); // i has a flavr
+
+ // printf("looking for clipboard data of type %s\n", flavorStr.get());
+
+ NSString *pboardType = nil;
+ if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
+ NSString* pString = [cocoaPasteboard stringForType:pboardType];
+ if (!pString)
+ continue;
+
+ NSData* stringData;
+ if ([pboardType isEqualToString:NSRTFPboardType]) {
+ stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
+ } else {
+ stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
+ }
+ unsigned int dataLength = [stringData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ [stringData getBytes:clipboardDataPtr];
+
+ // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
+ int32_t signedDataLength = dataLength;
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
+ dataLength = signedDataLength;
+
+ // skip BOM (Byte Order Mark to distinguish little or big endian)
+ char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
+ if ((dataLength > 2) &&
+ ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
+ (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
+ dataLength -= sizeof(char16_t);
+ clipboardDataPtrNoBOM += 1;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
+ free(clipboardDataPtr);
+ break;
+ }
+ else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ NSString* type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]];
+ if (!type) {
+ continue;
+ }
+
+ NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
+ if (!pasteboardData) {
+ continue;
+ }
+
+ unsigned int dataLength = [pasteboardData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ [pasteboardData getBytes:clipboardDataPtr];
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
+ getter_AddRefs(genericDataWrapper));
+
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
+ free(clipboardDataPtr);
+ }
+ else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ // Figure out if there's data on the pasteboard we can grab (sanity check)
+ NSString *type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
+ if (!type)
+ continue;
+
+ // Read data off the clipboard
+ NSData *pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
+ if (!pasteboardData)
+ continue;
+
+ // Figure out what type we're converting to
+ CFStringRef outputType = NULL;
+ if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime))
+ outputType = CFSTR("public.jpeg");
+ else if (flavorStr.EqualsLiteral(kPNGImageMime))
+ outputType = CFSTR("public.png");
+ else if (flavorStr.EqualsLiteral(kGIFImageMime))
+ outputType = CFSTR("com.compuserve.gif");
+ else
+ continue;
+
+ // Use ImageIO to interpret the data on the clipboard and transcode.
+ // Note that ImageIO, like all CF APIs, allows NULLs to propagate freely
+ // and safely in most cases (like ObjC). A notable exception is CFRelease.
+ NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
+ (NSNumber*)kCFBooleanTrue, kCGImageSourceShouldAllowFloat,
+ (type == NSTIFFPboardType ? @"public.tiff" : @"public.png"),
+ kCGImageSourceTypeIdentifierHint, nil];
+
+ CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)pasteboardData,
+ (CFDictionaryRef)options);
+ NSMutableData *encodedData = [NSMutableData data];
+ CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)encodedData,
+ outputType,
+ 1, NULL);
+ CGImageDestinationAddImageFromSource(dest, source, 0, NULL);
+ bool successfullyConverted = CGImageDestinationFinalize(dest);
+
+ if (successfullyConverted) {
+ // Put the converted data in a form Gecko can understand
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream), (const char*)[encodedData bytes],
+ [encodedData length], NS_ASSIGNMENT_COPY);
+
+ aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
+ }
+
+ if (dest)
+ CFRelease(dest);
+ if (source)
+ CFRelease(source);
+
+ if (successfullyConverted)
+ break;
+ else
+ continue;
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhichClipboard)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !aTransferable)
+ return NS_ERROR_FAILURE;
+
+ NSPasteboard* cocoaPasteboard;
+ if (aWhichClipboard == kFindClipboard) {
+ cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
+ } else {
+ cocoaPasteboard = [NSPasteboard generalPasteboard];
+ }
+ if (!cocoaPasteboard)
+ return NS_ERROR_FAILURE;
+
+ // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+
+ // If we were the last ones to put something on the pasteboard, then just use the cached
+ // transferable. Otherwise clear it because it isn't relevant any more.
+ if (mCachedClipboard == aWhichClipboard &&
+ mChangeCount == [cocoaPasteboard changeCount]) {
+ if (mTransferable) {
+ for (uint32_t i = 0; i < flavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ nsCOMPtr<nsISupports> dataSupports;
+ uint32_t dataSize = 0;
+ rv = mTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
+ if (NS_SUCCEEDED(rv)) {
+ aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
+ return NS_OK; // maybe try to fill in more types? Is there a point?
+ }
+ }
+ }
+ } else {
+ EmptyClipboard(aWhichClipboard);
+ }
+
+ // at this point we can't satisfy the request from cache data so let's look
+ // for things other people put on the system clipboard
+
+ return nsClipboard::TransferableFromPasteboard(aTransferable, cocoaPasteboard);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// returns true if we have *any* of the passed in flavors available for pasting
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
+ int32_t aWhichClipboard, bool* outResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *outResult = false;
+
+ if ((aWhichClipboard != kGlobalClipboard) || !aFlavorList)
+ return NS_OK;
+
+ // first see if we have data for this in our cached transferable
+ if (mTransferable) {
+ nsCOMPtr<nsIArray> transferableFlavorList;
+ nsresult rv = mTransferable->FlavorsTransferableCanImport(getter_AddRefs(transferableFlavorList));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t transferableFlavorCount;
+ transferableFlavorList->GetLength(&transferableFlavorCount);
+ for (uint32_t j = 0; j < transferableFlavorCount; j++) {
+ nsCOMPtr<nsISupportsCString> currentTransferableFlavor =
+ do_QueryElementAt(transferableFlavorList, j);
+ if (!currentTransferableFlavor)
+ continue;
+ nsXPIDLCString transferableFlavorStr;
+ currentTransferableFlavor->ToString(getter_Copies(transferableFlavorStr));
+
+ for (uint32_t k = 0; k < aLength; k++) {
+ if (transferableFlavorStr.Equals(aFlavorList[k])) {
+ *outResult = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+
+ NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
+
+ for (uint32_t i = 0; i < aLength; i++) {
+ nsDependentCString mimeType(aFlavorList[i]);
+ NSString *pboardType = nil;
+
+ if (nsClipboard::IsStringType(mimeType, &pboardType)) {
+ NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
+ if (availableType && [availableType isEqualToString:pboardType]) {
+ *outResult = true;
+ break;
+ }
+ } else if (!strcmp(aFlavorList[i], kCustomTypesMime)) {
+ NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]];
+ if (availableType) {
+ *outResult = true;
+ break;
+ }
+ } else if (!strcmp(aFlavorList[i], kJPEGImageMime) ||
+ !strcmp(aFlavorList[i], kJPGImageMime) ||
+ !strcmp(aFlavorList[i], kPNGImageMime) ||
+ !strcmp(aFlavorList[i], kGIFImageMime)) {
+ NSString* availableType = [generalPBoard availableTypeFromArray:
+ [NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
+ if (availableType) {
+ *outResult = true;
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = true;
+ return NS_OK;
+}
+
+// This function converts anything that other applications might understand into the system format
+// and puts it into a dictionary which it returns.
+// static
+NSDictionary*
+nsClipboard::PasteboardDictFromTransferable(nsITransferable* aTransferable)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!aTransferable)
+ return nil;
+
+ NSMutableDictionary* pasteboardOutputDict = [NSMutableDictionary dictionary];
+
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return nil;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+ for (uint32_t i = 0; i < flavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("writing out clipboard data of type %s (%d)\n", flavorStr.get(), i));
+
+ NSString *pboardType = nil;
+
+ if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
+ void* data = nullptr;
+ uint32_t dataSize = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &dataSize);
+ nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
+
+ NSString* nativeString;
+ if (data)
+ nativeString = [NSString stringWithCharacters:(const unichar*)data length:(dataSize / sizeof(char16_t))];
+ else
+ nativeString = [NSString string];
+
+ // be nice to Carbon apps, normalize the receiver's contents using Form C.
+ nativeString = [nativeString precomposedStringWithCanonicalMapping];
+
+ [pasteboardOutputDict setObject:nativeString forKey:pboardType];
+
+ free(data);
+ }
+ else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ void* data = nullptr;
+ uint32_t dataSize = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &dataSize);
+ nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
+
+ if (data) {
+ NSData* nativeData = [NSData dataWithBytes:data length:dataSize];
+
+ [pasteboardOutputDict setObject:nativeData forKey:kCustomTypesPboardType];
+ free(data);
+ }
+ }
+ else if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime) ||
+ flavorStr.EqualsLiteral(kNativeImageMime)) {
+ uint32_t dataSize = 0;
+ nsCOMPtr<nsISupports> transferSupports;
+ aTransferable->GetTransferData(flavorStr, getter_AddRefs(transferSupports), &dataSize);
+ nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive(do_QueryInterface(transferSupports));
+ if (!ptrPrimitive)
+ continue;
+
+ nsCOMPtr<nsISupports> primitiveData;
+ ptrPrimitive->GetData(getter_AddRefs(primitiveData));
+
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
+ if (!image) {
+ NS_WARNING("Image isn't an imgIContainer in transferable");
+ continue;
+ }
+
+ RefPtr<SourceSurface> surface =
+ image->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ continue;
+ }
+ CGImageRef imageRef = NULL;
+ rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
+ if (NS_FAILED(rv) || !imageRef) {
+ continue;
+ }
+
+ // Convert the CGImageRef to TIFF data.
+ CFMutableDataRef tiffData = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ CGImageDestinationRef destRef = CGImageDestinationCreateWithData(tiffData,
+ CFSTR("public.tiff"),
+ 1,
+ NULL);
+ CGImageDestinationAddImage(destRef, imageRef, NULL);
+ bool successfullyConverted = CGImageDestinationFinalize(destRef);
+
+ CGImageRelease(imageRef);
+ if (destRef)
+ CFRelease(destRef);
+
+ if (!successfullyConverted) {
+ if (tiffData)
+ CFRelease(tiffData);
+ continue;
+ }
+
+ [pasteboardOutputDict setObject:(NSMutableData*)tiffData forKey:NSTIFFPboardType];
+ if (tiffData)
+ CFRelease(tiffData);
+ }
+ else if (flavorStr.EqualsLiteral(kFileMime)) {
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericFile;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericFile), &len);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> file(do_QueryInterface(genericFile));
+ if (!file) {
+ nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericFile));
+
+ if (ptr) {
+ ptr->GetData(getter_AddRefs(genericFile));
+ file = do_QueryInterface(genericFile);
+ }
+ }
+
+ if (!file) {
+ continue;
+ }
+
+ nsAutoString fileURI;
+ rv = file->GetPath(fileURI);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ NSString* str = nsCocoaUtils::ToNSString(fileURI);
+ NSArray* fileList = [NSArray arrayWithObjects:str, nil];
+ [pasteboardOutputDict setObject:fileList forKey:NSFilenamesPboardType];
+ }
+ else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] forKey:NSFilesPromisePboardType];
+ }
+ else if (flavorStr.EqualsLiteral(kURLMime)) {
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericURL;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericURL), &len);
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+
+ nsAutoString url;
+ urlObject->GetData(url);
+
+ // A newline embedded in the URL means that the form is actually URL + title.
+ int32_t newlinePos = url.FindChar(char16_t('\n'));
+ if (newlinePos >= 0) {
+ url.Truncate(newlinePos);
+
+ nsAutoString urlTitle;
+ urlObject->GetData(urlTitle);
+ urlTitle.Mid(urlTitle, newlinePos + 1, len - (newlinePos + 1));
+
+ NSString *nativeTitle = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(urlTitle.get())
+ length:urlTitle.Length()];
+ // be nice to Carbon apps, normalize the receiver's contents using Form C.
+ [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urln];
+ // Also put the title out as 'urld', since some recipients will look for that.
+ [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urld];
+ [nativeTitle release];
+ }
+
+ // The Finder doesn't like getting random binary data aka
+ // Unicode, so change it into an escaped URL containing only
+ // ASCII.
+ nsAutoCString utf8Data = NS_ConvertUTF16toUTF8(url.get(), url.Length());
+ nsAutoCString escData;
+ NS_EscapeURL(utf8Data.get(), utf8Data.Length(), esc_OnlyNonASCII|esc_AlwaysCopy, escData);
+
+ // printf("Escaped url is %s, length %d\n", escData.get(), escData.Length());
+
+ NSString *nativeURL = [NSString stringWithUTF8String:escData.get()];
+ [pasteboardOutputDict setObject:nativeURL forKey:kCorePboardType_url];
+ }
+ // If it wasn't a type that we recognize as exportable we don't put it on the system
+ // clipboard. We'll just access it from our cached transferable when we need it.
+ }
+
+ return pasteboardOutputDict;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool nsClipboard::IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType)
+{
+ if (aMIMEType.EqualsLiteral(kUnicodeMime)) {
+ *aPasteboardType = NSStringPboardType;
+ return true;
+ } else if (aMIMEType.EqualsLiteral(kRTFMime)) {
+ *aPasteboardType = NSRTFPboardType;
+ return true;
+ } else if (aMIMEType.EqualsLiteral(kHTMLMime)) {
+ *aPasteboardType = NSHTMLPboardType;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+NSString* nsClipboard::WrapHtmlForSystemPasteboard(NSString* aString)
+{
+ NSString* wrapped =
+ [NSString stringWithFormat:
+ @"<html>"
+ "<head>"
+ "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"
+ "</head>"
+ "<body>"
+ "%@"
+ "</body>"
+ "</html>", aString];
+ return wrapped;
+}
+
+/**
+ * Sets the transferable object
+ *
+ */
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* anOwner,
+ int32_t aWhichClipboard)
+{
+ NS_ASSERTION (aTransferable, "clipboard given a null transferable");
+
+ if (aWhichClipboard == kSelectionCache) {
+ if (aTransferable) {
+ SetSelectionCache(aTransferable);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aTransferable == mTransferable && anOwner == mClipboardOwner) {
+ return NS_OK;
+ }
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EmptyClipboard(aWhichClipboard);
+
+ mClipboardOwner = anOwner;
+ mTransferable = aTransferable;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (mTransferable) {
+ rv = SetNativeClipboardData(aWhichClipboard);
+ }
+
+ return rv;
+}
+
+/**
+ * Gets the transferable object
+ *
+ */
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard)
+{
+ NS_ASSERTION (aTransferable, "clipboard given a null transferable");
+
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard)
+ return NS_ERROR_FAILURE;
+
+ if (aTransferable) {
+ return GetNativeClipboardData(aTransferable, aWhichClipboard);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard == kSelectionCache) {
+ ClearSelectionCache();
+ return NS_OK;
+ }
+
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mIgnoreEmptyNotification) {
+ return NS_OK;
+ }
+
+ if (mClipboardOwner) {
+ mClipboardOwner->LosingOwnership(mTransferable);
+ mClipboardOwner = nullptr;
+ }
+
+ mTransferable = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool* _retval)
+{
+ *_retval = false; // we don't support the selection clipboard by default.
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsCocoaDebugUtils.h b/widget/cocoa/nsCocoaDebugUtils.h
new file mode 100644
index 000000000..814f06087
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCocoaDebugUtils_h_
+#define nsCocoaDebugUtils_h_
+
+#include <CoreServices/CoreServices.h>
+
+// Definitions and declarations of stuff used by us from the CoreSymbolication
+// framework. This is an undocumented, private framework available on OS X
+// 10.6 and up. It's used by Apple utilities like dtrace, atos, ReportCrash
+// and crashreporterd.
+
+typedef struct _CSTypeRef {
+ unsigned long type;
+ void* contents;
+} CSTypeRef;
+
+typedef CSTypeRef CSSymbolicatorRef;
+typedef CSTypeRef CSSymbolOwnerRef;
+typedef CSTypeRef CSSymbolRef;
+typedef CSTypeRef CSSourceInfoRef;
+
+typedef struct _CSRange {
+ unsigned long long location;
+ unsigned long long length;
+} CSRange;
+
+typedef unsigned long long CSArchitecture;
+
+#define kCSNow LONG_MAX
+
+extern "C" {
+
+CSSymbolicatorRef
+CSSymbolicatorCreateWithPid(pid_t pid);
+
+CSSymbolicatorRef
+CSSymbolicatorCreateWithPidFlagsAndNotification(pid_t pid,
+ uint32_t flags,
+ uint32_t notification);
+
+CSArchitecture
+CSSymbolicatorGetArchitecture(CSSymbolicatorRef symbolicator);
+
+CSSymbolOwnerRef
+CSSymbolicatorGetSymbolOwnerWithAddressAtTime(CSSymbolicatorRef symbolicator,
+ unsigned long long address,
+ long time);
+
+const char*
+CSSymbolOwnerGetName(CSSymbolOwnerRef owner);
+
+unsigned long long
+CSSymbolOwnerGetBaseAddress(CSSymbolOwnerRef owner);
+
+CSSymbolRef
+CSSymbolOwnerGetSymbolWithAddress(CSSymbolOwnerRef owner,
+ unsigned long long address);
+
+CSSourceInfoRef
+CSSymbolOwnerGetSourceInfoWithAddress(CSSymbolOwnerRef owner,
+ unsigned long long address);
+
+const char*
+CSSymbolGetName(CSSymbolRef symbol);
+
+CSRange
+CSSymbolGetRange(CSSymbolRef symbol);
+
+const char*
+CSSourceInfoGetFilename(CSSourceInfoRef info);
+
+uint32_t
+CSSourceInfoGetLineNumber(CSSourceInfoRef info);
+
+CSTypeRef
+CSRetain(CSTypeRef);
+
+void
+CSRelease(CSTypeRef);
+
+bool
+CSIsNull(CSTypeRef);
+
+void
+CSShow(CSTypeRef);
+
+const char*
+CSArchitectureGetFamilyName(CSArchitecture);
+
+} // extern "C"
+
+class nsCocoaDebugUtils
+{
+public:
+ // Like NSLog() but records more information (for example the full path to
+ // the executable and the "thread name"). Like NSLog(), writes to both
+ // stdout and the system log.
+ static void DebugLog(const char* aFormat, ...);
+
+ // Logs a stack trace of the current point of execution, to both stdout and
+ // the system log.
+ static void PrintStackTrace();
+
+ // Returns the name of the module that "owns" aAddress. This must be
+ // free()ed by the caller.
+ static char* GetOwnerName(void* aAddress);
+
+ // Returns a symbolicated representation of aAddress. This must be
+ // free()ed by the caller.
+ static char* GetAddressString(void* aAddress);
+
+private:
+ static void DebugLogInt(bool aDecorate, const char* aFormat, ...);
+ static void DebugLogV(bool aDecorate, CFStringRef aFormat, va_list aArgs);
+
+ static void PrintAddress(void* aAddress);
+
+ // The values returned by GetOwnerNameInt() and GetAddressStringInt() must
+ // be free()ed by the caller.
+ static char* GetOwnerNameInt(void* aAddress,
+ CSTypeRef aOwner = sInitializer);
+ static char* GetAddressStringInt(void* aAddress,
+ CSTypeRef aOwner = sInitializer);
+
+ static CSSymbolicatorRef GetSymbolicatorRef();
+ static void ReleaseSymbolicator();
+
+ static CSTypeRef sInitializer;
+ static CSSymbolicatorRef sSymbolicator;
+};
+
+#endif // nsCocoaDebugUtils_h_
diff --git a/widget/cocoa/nsCocoaDebugUtils.mm b/widget/cocoa/nsCocoaDebugUtils.mm
new file mode 100644
index 000000000..35896dc40
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.mm
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCocoaDebugUtils.h"
+
+#include <pthread.h>
+#include <libproc.h>
+#include <stdarg.h>
+#include <time.h>
+#include <execinfo.h>
+#include <asl.h>
+
+static char gProcPath[PROC_PIDPATHINFO_MAXSIZE] = {0};
+static char gBundleID[MAXPATHLEN] = {0};
+
+static void MaybeGetPathAndID()
+{
+ if (!gProcPath[0]) {
+ proc_pidpath(getpid(), gProcPath, sizeof(gProcPath));
+ }
+ if (!gBundleID[0]) {
+ // Apple's CFLog() uses "com.apple.console" (in its call to asl_open()) if
+ // it can't find the bundle id.
+ CFStringRef bundleID = NULL;
+ CFBundleRef mainBundle = CFBundleGetMainBundle();
+ if (mainBundle) {
+ bundleID = CFBundleGetIdentifier(mainBundle);
+ }
+ if (!bundleID) {
+ strcpy(gBundleID, "com.apple.console");
+ } else {
+ CFStringGetCString(bundleID, gBundleID, sizeof(gBundleID),
+ kCFStringEncodingUTF8);
+ }
+ }
+}
+
+static void GetThreadName(char* aName, size_t aSize)
+{
+ pthread_getname_np(pthread_self(), aName, aSize);
+}
+
+void
+nsCocoaDebugUtils::DebugLog(const char* aFormat, ...)
+{
+ va_list args;
+ va_start(args, aFormat);
+ CFStringRef formatCFSTR =
+ CFStringCreateWithCString(kCFAllocatorDefault, aFormat,
+ kCFStringEncodingUTF8);
+ DebugLogV(true, formatCFSTR, args);
+ CFRelease(formatCFSTR);
+ va_end(args);
+}
+
+void
+nsCocoaDebugUtils::DebugLogInt(bool aDecorate, const char* aFormat, ...)
+{
+ va_list args;
+ va_start(args, aFormat);
+ CFStringRef formatCFSTR =
+ CFStringCreateWithCString(kCFAllocatorDefault, aFormat,
+ kCFStringEncodingUTF8);
+ DebugLogV(aDecorate, formatCFSTR, args);
+ CFRelease(formatCFSTR);
+ va_end(args);
+}
+
+void
+nsCocoaDebugUtils::DebugLogV(bool aDecorate, CFStringRef aFormat,
+ va_list aArgs)
+{
+ MaybeGetPathAndID();
+
+ CFStringRef message =
+ CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL,
+ aFormat, aArgs);
+
+ int msgLength =
+ CFStringGetMaximumSizeForEncoding(CFStringGetLength(message),
+ kCFStringEncodingUTF8);
+ char* msgUTF8 = (char*) calloc(msgLength, 1);
+ CFStringGetCString(message, msgUTF8, msgLength, kCFStringEncodingUTF8);
+ CFRelease(message);
+
+ int finishedLength = msgLength + PROC_PIDPATHINFO_MAXSIZE;
+ char* finished = (char*) calloc(finishedLength, 1);
+ const time_t currentTime = time(NULL);
+ char timestamp[30] = {0};
+ ctime_r(&currentTime, timestamp);
+ if (aDecorate) {
+ char threadName[MAXPATHLEN] = {0};
+ GetThreadName(threadName, sizeof(threadName));
+ snprintf(finished, finishedLength, "(%s) %s[%u] %s[%p] %s\n",
+ timestamp, gProcPath, getpid(), threadName, pthread_self(), msgUTF8);
+ } else {
+ snprintf(finished, finishedLength, "%s\n", msgUTF8);
+ }
+ free(msgUTF8);
+
+ fputs(finished, stdout);
+
+ // Use the Apple System Log facility, as NSLog and CFLog do.
+ aslclient asl = asl_open(NULL, gBundleID, ASL_OPT_NO_DELAY);
+ aslmsg msg = asl_new(ASL_TYPE_MSG);
+ asl_set(msg, ASL_KEY_LEVEL, "4"); // kCFLogLevelWarning, used by NSLog()
+ asl_set(msg, ASL_KEY_MSG, finished);
+ asl_send(asl, msg);
+ asl_free(msg);
+ asl_close(asl);
+
+ free(finished);
+}
+
+CSTypeRef
+nsCocoaDebugUtils::sInitializer = {0};
+
+CSSymbolicatorRef
+nsCocoaDebugUtils::sSymbolicator = {0};
+
+#define STACK_MAX 256
+
+void
+nsCocoaDebugUtils::PrintStackTrace()
+{
+ void** addresses = (void**) calloc(STACK_MAX, sizeof(void*));
+ if (!addresses) {
+ return;
+ }
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ if (CSIsNull(symbolicator)) {
+ free(addresses);
+ return;
+ }
+
+ uint32_t count = backtrace(addresses, STACK_MAX);
+ for (uint32_t i = 0; i < count; ++i) {
+ PrintAddress(addresses[i]);
+ }
+
+ ReleaseSymbolicator();
+ free(addresses);
+}
+
+void
+nsCocoaDebugUtils::PrintAddress(void* aAddress)
+{
+ const char* ownerName = "unknown";
+ const char* addressString = "unknown + 0";
+
+ char* allocatedOwnerName = nullptr;
+ char* allocatedAddressString = nullptr;
+
+ CSSymbolOwnerRef owner = {0};
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+
+ if (!CSIsNull(symbolicator)) {
+ owner =
+ CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long) aAddress,
+ kCSNow);
+ }
+ if (!CSIsNull(owner)) {
+ ownerName = allocatedOwnerName = GetOwnerNameInt(aAddress, owner);
+ addressString = allocatedAddressString = GetAddressStringInt(aAddress, owner);
+ }
+ DebugLogInt(false, " (%s) %s", ownerName, addressString);
+
+ free(allocatedOwnerName);
+ free(allocatedAddressString);
+
+ ReleaseSymbolicator();
+}
+
+char*
+nsCocoaDebugUtils::GetOwnerName(void* aAddress)
+{
+ return GetOwnerNameInt(aAddress);
+}
+
+char*
+nsCocoaDebugUtils::GetOwnerNameInt(void* aAddress, CSTypeRef aOwner)
+{
+ char* retval = (char*) calloc(MAXPATHLEN, 1);
+
+ const char* ownerName = "unknown";
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ CSTypeRef owner = aOwner;
+
+ if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+ owner =
+ CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long) aAddress,
+ kCSNow);
+ }
+
+ if (!CSIsNull(owner)) {
+ ownerName = CSSymbolOwnerGetName(owner);
+ }
+
+ snprintf(retval, MAXPATHLEN, "%s", ownerName);
+ ReleaseSymbolicator();
+
+ return retval;
+}
+
+char*
+nsCocoaDebugUtils::GetAddressString(void* aAddress)
+{
+ return GetAddressStringInt(aAddress);
+}
+
+char*
+nsCocoaDebugUtils::GetAddressStringInt(void* aAddress, CSTypeRef aOwner)
+{
+ char* retval = (char*) calloc(MAXPATHLEN, 1);
+
+ const char* addressName = "unknown";
+ unsigned long long addressOffset = 0;
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ CSTypeRef owner = aOwner;
+
+ if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+ owner =
+ CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long) aAddress,
+ kCSNow);
+ }
+
+ if (!CSIsNull(owner)) {
+ CSSymbolRef symbol =
+ CSSymbolOwnerGetSymbolWithAddress(owner,
+ (unsigned long long) aAddress);
+ if (!CSIsNull(symbol)) {
+ addressName = CSSymbolGetName(symbol);
+ CSRange range = CSSymbolGetRange(symbol);
+ addressOffset = (unsigned long long) aAddress - range.location;
+ } else {
+ addressOffset = (unsigned long long)
+ aAddress - CSSymbolOwnerGetBaseAddress(owner);
+ }
+ }
+
+ snprintf(retval, MAXPATHLEN, "%s + 0x%llx",
+ addressName, addressOffset);
+ ReleaseSymbolicator();
+
+ return retval;
+}
+
+CSSymbolicatorRef
+nsCocoaDebugUtils::GetSymbolicatorRef()
+{
+ if (CSIsNull(sSymbolicator)) {
+ // 0x40e0000 is the value returned by
+ // uint32_t CSSymbolicatorGetFlagsForNListOnlyData(void). We don't use
+ // this method directly because it doesn't exist on OS X 10.6. Unless
+ // we limit ourselves to NList data, it will take too long to get a
+ // stack trace where Dwarf debugging info is available (about 15 seconds
+ // with Firefox). This means we won't be able to get a CSSourceInfoRef,
+ // or line number information. Oh well.
+ sSymbolicator =
+ CSSymbolicatorCreateWithPidFlagsAndNotification(getpid(),
+ 0x40e0000, 0);
+ }
+ // Retaining just after creation prevents crashes when calling symbolicator
+ // code (for example from PrintStackTrace()) as Firefox is quitting. Not
+ // sure why. Doing this may mean that we leak sSymbolicator on quitting
+ // (if we ever created it). No particular harm in that, though.
+ return CSRetain(sSymbolicator);
+}
+
+void
+nsCocoaDebugUtils::ReleaseSymbolicator()
+{
+ if (!CSIsNull(sSymbolicator)) {
+ CSRelease(sSymbolicator);
+ }
+}
diff --git a/widget/cocoa/nsCocoaFeatures.h b/widget/cocoa/nsCocoaFeatures.h
new file mode 100644
index 000000000..597aff611
--- /dev/null
+++ b/widget/cocoa/nsCocoaFeatures.h
@@ -0,0 +1,42 @@
+/* -*- 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 OSXVersion();
+ static int32_t OSXVersionMajor();
+ static int32_t OSXVersionMinor();
+ static int32_t OSXVersionBugFix();
+ static bool OnYosemiteOrLater();
+ static bool OnElCapitanOrLater();
+ static bool OnSierraOrLater();
+
+ static bool IsAtLeastVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix=0);
+
+ // These are utilities that do not change or depend on the value of mOSXVersion
+ // and instead just encapsulate the encoding algorithm. Note that GetVersion
+ // actually adjusts to the lowest supported OS, so it will always return
+ // a "supported" version. GetSystemVersion does not make any modifications.
+ static void GetSystemVersion(int &aMajor, int &aMinor, int &aBugFix);
+ static int32_t GetVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix);
+ static int32_t ExtractMajorVersion(int32_t aVersion);
+ static int32_t ExtractMinorVersion(int32_t aVersion);
+ static int32_t ExtractBugFixVersion(int32_t aVersion);
+
+private:
+ static void InitializeVersionNumbers();
+
+ static int32_t mOSXVersion;
+};
+#endif // nsCocoaFeatures_h_
diff --git a/widget/cocoa/nsCocoaFeatures.mm b/widget/cocoa/nsCocoaFeatures.mm
new file mode 100644
index 000000000..5a5c16fa1
--- /dev/null
+++ b/widget/cocoa/nsCocoaFeatures.mm
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file makes some assumptions about the versions of OS X.
+// We are assuming that the minor and bugfix versions are less than 16.
+// There are MOZ_ASSERTs for that.
+
+// The formula for the version integer based on OS X version 10.minor.bugfix is
+// 0x1000 + (minor << 4) + bugifix. See AssembleVersion() below for major > 10.
+// Major version < 10 is not allowed.
+
+#define MAC_OS_X_VERSION_MASK 0x0000FFFF
+#define MAC_OS_X_VERSION_10_0_HEX 0x00001000
+#define MAC_OS_X_VERSION_10_7_HEX 0x00001070
+#define MAC_OS_X_VERSION_10_8_HEX 0x00001080
+#define MAC_OS_X_VERSION_10_9_HEX 0x00001090
+#define MAC_OS_X_VERSION_10_10_HEX 0x000010A0
+#define MAC_OS_X_VERSION_10_11_HEX 0x000010B0
+#define MAC_OS_X_VERSION_10_12_HEX 0x000010C0
+
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsDebug.h"
+#include "nsObjCExceptions.h"
+
+#import <Cocoa/Cocoa.h>
+
+int32_t nsCocoaFeatures::mOSXVersion = 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 MAC_OS_X_VERSION_10_0_HEX + (aMajor-10) * 0x100 + (aMinor << 4) + aBugFix;
+}
+
+int32_t nsCocoaFeatures::ExtractMajorVersion(int32_t aVersion)
+{
+ MOZ_ASSERT((aVersion & MAC_OS_X_VERSION_MASK) == aVersion);
+ return ((aVersion & 0xFF00) - 0x1000) / 0x100 + 10;
+}
+
+int32_t nsCocoaFeatures::ExtractMinorVersion(int32_t aVersion)
+{
+ MOZ_ASSERT((aVersion & MAC_OS_X_VERSION_MASK) == aVersion);
+ return (aVersion & 0xF0) >> 4;
+}
+
+int32_t nsCocoaFeatures::ExtractBugFixVersion(int32_t aVersion)
+{
+ MOZ_ASSERT((aVersion & MAC_OS_X_VERSION_MASK) == aVersion);
+ return aVersion & 0x0F;
+}
+
+static int intAtStringIndex(NSArray *array, int index)
+{
+ return [(NSString *)[array objectAtIndex:index] integerValue];
+}
+
+void nsCocoaFeatures::GetSystemVersion(int &major, int &minor, int &bugfix)
+{
+ major = minor = bugfix = 0;
+
+ NSString* versionString = [[NSDictionary dictionaryWithContentsOfFile:
+ @"/System/Library/CoreServices/SystemVersion.plist"] objectForKey:@"ProductVersion"];
+ NSArray* versions = [versionString componentsSeparatedByString:@"."];
+ NSUInteger count = [versions count];
+ if (count > 0) {
+ major = intAtStringIndex(versions, 0);
+ if (count > 1) {
+ minor = intAtStringIndex(versions, 1);
+ if (count > 2) {
+ bugfix = intAtStringIndex(versions, 2);
+ }
+ }
+ }
+}
+
+int32_t nsCocoaFeatures::GetVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix)
+{
+ int32_t osxVersion;
+ if (aMajor < 10) {
+ aMajor = 10;
+ NS_ERROR("Couldn't determine OS X version, assuming 10.7");
+ osxVersion = MAC_OS_X_VERSION_10_7_HEX;
+ } else if (aMinor < 7) {
+ aMinor = 7;
+ NS_ERROR("OS X version too old, assuming 10.7");
+ osxVersion = MAC_OS_X_VERSION_10_7_HEX;
+ } else {
+ MOZ_ASSERT(aMajor == 10); // For now, even though we're ready...
+ MOZ_ASSERT(aMinor < 16);
+ MOZ_ASSERT(aBugFix >= 0);
+ MOZ_ASSERT(aBugFix < 16);
+ osxVersion = AssembleVersion(aMajor, aMinor, aBugFix);
+ }
+ MOZ_ASSERT(aMajor == ExtractMajorVersion(osxVersion));
+ MOZ_ASSERT(aMinor == ExtractMinorVersion(osxVersion));
+ MOZ_ASSERT(aBugFix == ExtractBugFixVersion(osxVersion));
+ return osxVersion;
+}
+
+/*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);
+ mOSXVersion = GetVersion(major, minor, bugfix);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+/* static */ int32_t
+nsCocoaFeatures::OSXVersion()
+{
+ // Don't let this be called while we're first setting the value...
+ MOZ_ASSERT((mOSXVersion & MAC_OS_X_VERSION_MASK) >= 0);
+ if (!mOSXVersion) {
+ mOSXVersion = -1;
+ InitializeVersionNumbers();
+ }
+ return mOSXVersion;
+}
+
+/* static */ int32_t
+nsCocoaFeatures::OSXVersionMajor()
+{
+ MOZ_ASSERT((OSXVersion() & MAC_OS_X_VERSION_10_0_HEX) == MAC_OS_X_VERSION_10_0_HEX);
+ return 10;
+}
+
+/* static */ int32_t
+nsCocoaFeatures::OSXVersionMinor()
+{
+ return ExtractMinorVersion(OSXVersion());
+}
+
+/* static */ int32_t
+nsCocoaFeatures::OSXVersionBugFix()
+{
+ return ExtractBugFixVersion(OSXVersion());
+}
+
+/* static */ bool
+nsCocoaFeatures::OnYosemiteOrLater()
+{
+ return (OSXVersion() >= MAC_OS_X_VERSION_10_10_HEX);
+}
+
+/* static */ bool
+nsCocoaFeatures::OnElCapitanOrLater()
+{
+ return (OSXVersion() >= MAC_OS_X_VERSION_10_11_HEX);
+}
+
+/* static */ bool
+nsCocoaFeatures::OnSierraOrLater()
+{
+ return (OSXVersion() >= MAC_OS_X_VERSION_10_12_HEX);
+}
+
+/* static */ bool
+nsCocoaFeatures::IsAtLeastVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix)
+{
+ return OSXVersion() >= GetVersion(aMajor, aMinor, aBugFix);
+}
diff --git a/widget/cocoa/nsCocoaUtils.h b/widget/cocoa/nsCocoaUtils.h
new file mode 100644
index 000000000..139e76b4a
--- /dev/null
+++ b/widget/cocoa/nsCocoaUtils.h
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCocoaUtils_h_
+#define nsCocoaUtils_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsRect.h"
+#include "imgIContainer.h"
+#include "npapi.h"
+#include "nsTArray.h"
+#include "Units.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+#include "mozilla/EventForwards.h"
+
+// Declare the backingScaleFactor method that we want to call
+// on NSView/Window/Screen objects, if they recognize it.
+@interface NSObject (BackingScaleFactorCategory)
+- (CGFloat)backingScaleFactor;
+@end
+
+#if !defined(MAC_OS_X_VERSION_10_8) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
+enum {
+ NSEventPhaseMayBegin = 0x1 << 5
+};
+#endif
+
+class nsIWidget;
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+} // namespace gfx
+} // namespace mozilla
+
+// Used to retain a Cocoa object for the remainder of a method's execution.
+class nsAutoRetainCocoaObject {
+public:
+explicit nsAutoRetainCocoaObject(id anObject)
+{
+ mObject = NS_OBJC_TRY_EXPR_ABORT([anObject retain]);
+}
+~nsAutoRetainCocoaObject()
+{
+ NS_OBJC_TRY_ABORT([mObject release]);
+}
+private:
+ id mObject; // [STRONG]
+};
+
+// Provide a local autorelease pool for the remainder of a method's execution.
+class nsAutoreleasePool {
+public:
+ nsAutoreleasePool()
+ {
+ mLocalPool = [[NSAutoreleasePool alloc] init];
+ }
+ ~nsAutoreleasePool()
+ {
+ [mLocalPool release];
+ }
+private:
+ NSAutoreleasePool *mLocalPool;
+};
+
+@interface NSApplication (Undocumented)
+
+// Present in all versions of OS X from (at least) 10.2.8 through 10.5.
+- (BOOL)_isRunningModal;
+- (BOOL)_isRunningAppModal;
+
+// It's sometimes necessary to explicitly remove a window from the "window
+// cache" in order to deactivate it. The "window cache" is an undocumented
+// subsystem, all of whose methods are included in the NSWindowCache category
+// of the NSApplication class (in header files generated using class-dump).
+// Present in all versions of OS X from (at least) 10.2.8 through 10.5.
+- (void)_removeWindowFromCache:(NSWindow *)aWindow;
+
+// Send an event to the current Cocoa app-modal session. Present in all
+// versions of OS X from (at least) 10.2.8 through 10.5.
+- (void)_modalSession:(NSModalSession)aSession sendEvent:(NSEvent *)theEvent;
+
+@end
+
+struct KeyBindingsCommand
+{
+ SEL selector;
+ id data;
+};
+
+@interface NativeKeyBindingsRecorder : NSResponder
+{
+@private
+ nsTArray<KeyBindingsCommand>* mCommands;
+}
+
+- (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands;
+
+- (void)doCommandBySelector:(SEL)aSelector;
+
+- (void)insertText:(id)aString;
+
+@end // NativeKeyBindingsRecorder
+
+class nsCocoaUtils
+{
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+
+public:
+
+ // Get the backing scale factor from an object that supports this selector
+ // (NSView/Window/Screen, on 10.7 or later), returning 1.0 if not supported
+ static CGFloat
+ GetBackingScaleFactor(id aObject)
+ {
+ if (HiDPIEnabled() &&
+ [aObject respondsToSelector:@selector(backingScaleFactor)]) {
+ return [aObject backingScaleFactor];
+ }
+ return 1.0;
+ }
+
+ // Conversions between Cocoa points and device pixels, given the backing
+ // scale factor from a view/window/screen.
+ static int32_t
+ CocoaPointsToDevPixels(CGFloat aPts, CGFloat aBackingScale)
+ {
+ return NSToIntRound(aPts * aBackingScale);
+ }
+
+ static LayoutDeviceIntPoint
+ CocoaPointsToDevPixels(const NSPoint& aPt, CGFloat aBackingScale)
+ {
+ return LayoutDeviceIntPoint(NSToIntRound(aPt.x * aBackingScale),
+ NSToIntRound(aPt.y * aBackingScale));
+ }
+
+ static LayoutDeviceIntPoint
+ CocoaPointsToDevPixelsRoundDown(const NSPoint& aPt, CGFloat aBackingScale)
+ {
+ return LayoutDeviceIntPoint(NSToIntFloor(aPt.x * aBackingScale),
+ NSToIntFloor(aPt.y * aBackingScale));
+ }
+
+ static LayoutDeviceIntRect
+ CocoaPointsToDevPixels(const NSRect& aRect, CGFloat aBackingScale)
+ {
+ return LayoutDeviceIntRect(NSToIntRound(aRect.origin.x * aBackingScale),
+ NSToIntRound(aRect.origin.y * aBackingScale),
+ NSToIntRound(aRect.size.width * aBackingScale),
+ NSToIntRound(aRect.size.height * aBackingScale));
+ }
+
+ static CGFloat
+ DevPixelsToCocoaPoints(int32_t aPixels, CGFloat aBackingScale)
+ {
+ return (CGFloat)aPixels / aBackingScale;
+ }
+
+ static NSPoint
+ DevPixelsToCocoaPoints(const mozilla::LayoutDeviceIntPoint& aPt,
+ CGFloat aBackingScale)
+ {
+ return NSMakePoint((CGFloat)aPt.x / aBackingScale,
+ (CGFloat)aPt.y / aBackingScale);
+ }
+
+ // Implements an NSPoint equivalent of -[NSWindow convertRectFromScreen:].
+ static NSPoint
+ ConvertPointFromScreen(NSWindow* aWindow, const NSPoint& aPt)
+ {
+ return [aWindow convertRectFromScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin;
+ }
+
+ // Implements an NSPoint equivalent of -[NSWindow convertRectToScreen:].
+ static NSPoint
+ ConvertPointToScreen(NSWindow* aWindow, const NSPoint& aPt)
+ {
+ return [aWindow convertRectToScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin;
+ }
+
+ static NSRect
+ DevPixelsToCocoaPoints(const LayoutDeviceIntRect& aRect,
+ CGFloat aBackingScale)
+ {
+ return NSMakeRect((CGFloat)aRect.x / aBackingScale,
+ (CGFloat)aRect.y / aBackingScale,
+ (CGFloat)aRect.width / aBackingScale,
+ (CGFloat)aRect.height / aBackingScale);
+ }
+
+ // Returns the given y coordinate, which must be in screen coordinates,
+ // flipped from Gecko to Cocoa or Cocoa to Gecko.
+ static float FlippedScreenY(float y);
+
+ // The following functions come in "DevPix" variants that work with
+ // backing-store (device pixel) coordinates, as well as the original
+ // versions that expect coordinates in Cocoa points/CSS pixels.
+ // The difference becomes important in HiDPI display modes, where Cocoa
+ // points and backing-store pixels are no longer 1:1.
+
+ // Gecko rects (nsRect) contain an origin (x,y) in a coordinate
+ // system with (0,0) in the top-left of the primary screen. Cocoa rects
+ // (NSRect) contain an origin (x,y) in a coordinate system with (0,0)
+ // in the bottom-left of the primary screen. Both nsRect and NSRect
+ // contain width/height info, with no difference in their use.
+ // This function does no scaling, so the Gecko coordinates are
+ // expected to be desktop pixels, which are equal to Cocoa points
+ // (by definition).
+ static NSRect GeckoRectToCocoaRect(const mozilla::DesktopIntRect &geckoRect);
+
+ // Converts aGeckoRect in dev pixels to points in Cocoa coordinates
+ static NSRect
+ GeckoRectToCocoaRectDevPix(const mozilla::LayoutDeviceIntRect &aGeckoRect,
+ CGFloat aBackingScale);
+
+ // See explanation for geckoRectToCocoaRect, guess what this does...
+ static mozilla::DesktopIntRect CocoaRectToGeckoRect(const NSRect &cocoaRect);
+
+ static mozilla::LayoutDeviceIntRect CocoaRectToGeckoRectDevPix(
+ const NSRect& aCocoaRect, CGFloat aBackingScale);
+
+ // Gives the location for the event in screen coordinates. Do not call this
+ // unless the window the event was originally targeted at is still alive!
+ // anEvent may be nil -- in that case the current mouse location is returned.
+ static NSPoint ScreenLocationForEvent(NSEvent* anEvent);
+
+ // Determines if an event happened over a window, whether or not the event
+ // is for the window. Does not take window z-order into account.
+ static BOOL IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow);
+
+ // Events are set up so that their coordinates refer to the window to which they
+ // were originally sent. If we reroute the event somewhere else, we'll have
+ // to get the window coordinates this way. Do not call this unless the window
+ // the event was originally targeted at is still alive!
+ static NSPoint EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow);
+
+ // Compatibility wrappers for the -[NSEvent phase], -[NSEvent momentumPhase],
+ // -[NSEvent hasPreciseScrollingDeltas] and -[NSEvent scrollingDeltaX/Y] APIs
+ // that became availaible starting with the 10.7 SDK.
+ // All of these can be removed once we drop support for 10.6.
+ static NSEventPhase EventPhase(NSEvent* aEvent);
+ static NSEventPhase EventMomentumPhase(NSEvent* aEvent);
+ static BOOL IsMomentumScrollEvent(NSEvent* aEvent);
+ static BOOL HasPreciseScrollingDeltas(NSEvent* aEvent);
+ static void GetScrollingDeltas(NSEvent* aEvent, CGFloat* aOutDeltaX, CGFloat* aOutDeltaY);
+ static BOOL EventHasPhaseInformation(NSEvent* aEvent);
+
+ // Hides the Menu bar and the Dock. Multiple hide/show requests can be nested.
+ static void HideOSChromeOnScreen(bool aShouldHide);
+
+ static nsIWidget* GetHiddenWindowWidget();
+
+ static void PrepareForNativeAppModalDialog();
+ static void CleanUpAfterNativeAppModalDialog();
+
+ // 3 utility functions to go from a frame of imgIContainer to CGImage and then to NSImage
+ // Convert imgIContainer -> CGImageRef, caller owns result
+
+ /** Creates a <code>CGImageRef</code> from a frame contained in an <code>imgIContainer</code>.
+ Copies the pixel data from the indicated frame of the <code>imgIContainer</code> into a new <code>CGImageRef</code>.
+ The caller owns the <code>CGImageRef</code>.
+ @param aFrame the frame to convert
+ @param aResult the resulting CGImageRef
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateCGImageFromSurface(SourceSurface* aSurface,
+ CGImageRef* aResult);
+
+ /** Creates a Cocoa <code>NSImage</code> from a <code>CGImageRef</code>.
+ Copies the pixel data from the <code>CGImageRef</code> into a new <code>NSImage</code>.
+ The caller owns the <code>NSImage</code>.
+ @param aInputImage the image to convert
+ @param aResult the resulting NSImage
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult);
+
+ /** Creates a Cocoa <code>NSImage</code> from a frame of an <code>imgIContainer</code>.
+ Combines the two methods above. The caller owns the <code>NSImage</code>.
+ @param aImage the image to extract a frame from
+ @param aWhichFrame the frame to extract (see imgIContainer FRAME_*)
+ @param aResult the resulting NSImage
+ @param scaleFactor the desired scale factor of the NSImage (2 for a retina display)
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor);
+
+ /**
+ * Returns nsAString for aSrc.
+ */
+ static void GetStringForNSString(const NSString *aSrc, nsAString& aDist);
+
+ /**
+ * Makes NSString instance for aString.
+ */
+ static NSString* ToNSString(const nsAString& aString);
+
+ /**
+ * Returns NSRect for aGeckoRect.
+ * Just copies values between the two types; it does no coordinate-system
+ * conversion, so both rects must have the same coordinate origin/direction.
+ */
+ static void GeckoRectToNSRect(const nsIntRect& aGeckoRect,
+ NSRect& aOutCocoaRect);
+
+ /**
+ * Returns Gecko rect for aCocoaRect.
+ * Just copies values between the two types; it does no coordinate-system
+ * conversion, so both rects must have the same coordinate origin/direction.
+ */
+ static void NSRectToGeckoRect(const NSRect& aCocoaRect,
+ nsIntRect& aOutGeckoRect);
+
+ /**
+ * Makes NSEvent instance for aEventTytpe and aEvent.
+ */
+ static NSEvent* MakeNewCocoaEventWithType(NSEventType aEventType,
+ NSEvent *aEvent);
+
+ /**
+ * Initializes aNPCocoaEvent.
+ */
+ static void InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent);
+
+ /**
+ * Initializes WidgetInputEvent for aNativeEvent or aModifiers.
+ */
+ static void InitInputEvent(mozilla::WidgetInputEvent &aInputEvent,
+ NSEvent* aNativeEvent);
+
+ /**
+ * Converts the native modifiers from aNativeEvent into WidgetMouseEvent
+ * Modifiers. aNativeEvent can be null.
+ */
+ static mozilla::Modifiers ModifiersForEvent(NSEvent* aNativeEvent);
+
+ /**
+ * ConvertToCarbonModifier() returns carbon modifier flags for the cocoa
+ * modifier flags.
+ * NOTE: The result never includes right*Key.
+ */
+ static UInt32 ConvertToCarbonModifier(NSUInteger aCocoaModifier);
+
+ /**
+ * Whether to support HiDPI rendering. For testing purposes, to be removed
+ * once we're comfortable with the HiDPI behavior.
+ */
+ static bool HiDPIEnabled();
+
+ /**
+ * Keys can optionally be bound by system or user key bindings to one or more
+ * commands based on selectors. This collects any such commands in the
+ * provided array.
+ */
+ static void GetCommandsFromKeyEvent(NSEvent* aEvent,
+ nsTArray<KeyBindingsCommand>& aCommands);
+
+ /**
+ * Converts the string name of a Gecko key (like "VK_HOME") to the
+ * corresponding Cocoa Unicode character.
+ */
+ static uint32_t ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName);
+
+ /**
+ * Converts a Gecko key code (like NS_VK_HOME) to the corresponding Cocoa
+ * Unicode character.
+ */
+ static uint32_t ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode);
+
+ /**
+ * Convert string with font attribute to NSMutableAttributedString
+ */
+ static NSMutableAttributedString* GetNSMutableAttributedString(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRanges,
+ const bool aIsVertical,
+ const CGFloat aBackingScaleFactor);
+};
+
+#endif // nsCocoaUtils_h_
diff --git a/widget/cocoa/nsCocoaUtils.mm b/widget/cocoa/nsCocoaUtils.mm
new file mode 100644
index 000000000..3138245aa
--- /dev/null
+++ b/widget/cocoa/nsCocoaUtils.mm
@@ -0,0 +1,1022 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cmath>
+
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "ImageRegion.h"
+#include "nsCocoaUtils.h"
+#include "nsChildView.h"
+#include "nsMenuBarX.h"
+#include "nsCocoaWindow.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIAppShellService.h"
+#include "nsIXULWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsIServiceManager.h"
+#include "nsMenuUtilsX.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+#include "SVGImageContext.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+using mozilla::gfx::BackendType;
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::DrawTarget;
+using mozilla::gfx::Factory;
+using mozilla::gfx::SamplingFilter;
+using mozilla::gfx::IntPoint;
+using mozilla::gfx::IntRect;
+using mozilla::gfx::IntSize;
+using mozilla::gfx::SurfaceFormat;
+using mozilla::gfx::SourceSurface;
+using mozilla::image::ImageRegion;
+using std::ceil;
+
+static float
+MenuBarScreenHeight()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSArray* allScreens = [NSScreen screens];
+ if ([allScreens count]) {
+ return [[allScreens objectAtIndex:0] frame].size.height;
+ }
+
+ return 0.0;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0.0);
+}
+
+float
+nsCocoaUtils::FlippedScreenY(float y)
+{
+ return MenuBarScreenHeight() - y;
+}
+
+NSRect nsCocoaUtils::GeckoRectToCocoaRect(const DesktopIntRect &geckoRect)
+{
+ // We only need to change the Y coordinate by starting with the primary screen
+ // height and subtracting the gecko Y coordinate of the bottom of the rect.
+ return NSMakeRect(geckoRect.x,
+ MenuBarScreenHeight() - geckoRect.YMost(),
+ geckoRect.width,
+ geckoRect.height);
+}
+
+NSRect
+nsCocoaUtils::GeckoRectToCocoaRectDevPix(const LayoutDeviceIntRect &aGeckoRect,
+ CGFloat aBackingScale)
+{
+ return NSMakeRect(aGeckoRect.x / aBackingScale,
+ MenuBarScreenHeight() - aGeckoRect.YMost() / aBackingScale,
+ aGeckoRect.width / aBackingScale,
+ aGeckoRect.height / aBackingScale);
+}
+
+DesktopIntRect nsCocoaUtils::CocoaRectToGeckoRect(const NSRect &cocoaRect)
+{
+ // We only need to change the Y coordinate by starting with the primary screen
+ // height and subtracting both the cocoa y origin and the height of the
+ // cocoa rect.
+ DesktopIntRect rect;
+ rect.x = NSToIntRound(cocoaRect.origin.x);
+ rect.y = NSToIntRound(FlippedScreenY(cocoaRect.origin.y + cocoaRect.size.height));
+ rect.width = NSToIntRound(cocoaRect.origin.x + cocoaRect.size.width) - rect.x;
+ rect.height = NSToIntRound(FlippedScreenY(cocoaRect.origin.y)) - rect.y;
+ return rect;
+}
+
+LayoutDeviceIntRect nsCocoaUtils::CocoaRectToGeckoRectDevPix(
+ const NSRect& aCocoaRect, CGFloat aBackingScale)
+{
+ LayoutDeviceIntRect rect;
+ rect.x = NSToIntRound(aCocoaRect.origin.x * aBackingScale);
+ rect.y = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y + aCocoaRect.size.height) * aBackingScale);
+ rect.width = NSToIntRound((aCocoaRect.origin.x + aCocoaRect.size.width) * aBackingScale) - rect.x;
+ rect.height = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y) * aBackingScale) - rect.y;
+ return rect;
+}
+
+NSPoint nsCocoaUtils::ScreenLocationForEvent(NSEvent* anEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // Don't trust mouse locations of mouse move events, see bug 443178.
+ if (!anEvent || [anEvent type] == NSMouseMoved)
+ return [NSEvent mouseLocation];
+
+ // Pin momentum scroll events to the location of the last user-controlled
+ // scroll event.
+ if (IsMomentumScrollEvent(anEvent))
+ return ChildViewMouseTracker::sLastScrollEventScreenLocation;
+
+ return nsCocoaUtils::ConvertPointToScreen([anEvent window], [anEvent locationInWindow]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
+}
+
+BOOL nsCocoaUtils::IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return NSPointInRect(ScreenLocationForEvent(anEvent), [aWindow frame]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+NSPoint nsCocoaUtils::EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return nsCocoaUtils::ConvertPointFromScreen(aWindow, ScreenLocationForEvent(anEvent));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
+}
+
+@interface NSEvent (ScrollPhase)
+// 10.5 and 10.6
+- (long long)_scrollPhase;
+// 10.7 and above
+- (NSEventPhase)phase;
+- (NSEventPhase)momentumPhase;
+@end
+
+NSEventPhase nsCocoaUtils::EventPhase(NSEvent* aEvent)
+{
+ if ([aEvent respondsToSelector:@selector(phase)]) {
+ return [aEvent phase];
+ }
+ return NSEventPhaseNone;
+}
+
+NSEventPhase nsCocoaUtils::EventMomentumPhase(NSEvent* aEvent)
+{
+ if ([aEvent respondsToSelector:@selector(momentumPhase)]) {
+ return [aEvent momentumPhase];
+ }
+ if ([aEvent respondsToSelector:@selector(_scrollPhase)]) {
+ switch ([aEvent _scrollPhase]) {
+ case 1: return NSEventPhaseBegan;
+ case 2: return NSEventPhaseChanged;
+ case 3: return NSEventPhaseEnded;
+ default: return NSEventPhaseNone;
+ }
+ }
+ return NSEventPhaseNone;
+}
+
+BOOL nsCocoaUtils::IsMomentumScrollEvent(NSEvent* aEvent)
+{
+ return [aEvent type] == NSScrollWheel &&
+ EventMomentumPhase(aEvent) != NSEventPhaseNone;
+}
+
+@interface NSEvent (HasPreciseScrollingDeltas)
+// 10.7 and above
+- (BOOL)hasPreciseScrollingDeltas;
+// For 10.6 and below, see the comment in nsChildView.h about _eventRef
+- (EventRef)_eventRef;
+@end
+
+BOOL nsCocoaUtils::HasPreciseScrollingDeltas(NSEvent* aEvent)
+{
+ if ([aEvent respondsToSelector:@selector(hasPreciseScrollingDeltas)]) {
+ return [aEvent hasPreciseScrollingDeltas];
+ }
+
+ // For events that don't contain pixel scrolling information, the event
+ // kind of their underlaying carbon event is kEventMouseWheelMoved instead
+ // of kEventMouseScroll.
+ EventRef carbonEvent = [aEvent _eventRef];
+ return carbonEvent && ::GetEventKind(carbonEvent) == kEventMouseScroll;
+}
+
+@interface NSEvent (ScrollingDeltas)
+// 10.6 and below
+- (CGFloat)deviceDeltaX;
+- (CGFloat)deviceDeltaY;
+// 10.7 and above
+- (CGFloat)scrollingDeltaX;
+- (CGFloat)scrollingDeltaY;
+@end
+
+void nsCocoaUtils::GetScrollingDeltas(NSEvent* aEvent, CGFloat* aOutDeltaX, CGFloat* aOutDeltaY)
+{
+ if ([aEvent respondsToSelector:@selector(scrollingDeltaX)]) {
+ *aOutDeltaX = [aEvent scrollingDeltaX];
+ *aOutDeltaY = [aEvent scrollingDeltaY];
+ return;
+ }
+ if ([aEvent respondsToSelector:@selector(deviceDeltaX)] &&
+ HasPreciseScrollingDeltas(aEvent)) {
+ // Calling deviceDeltaX/Y on those events that do not contain pixel
+ // scrolling information triggers a Cocoa assertion and an
+ // Objective-C NSInternalInconsistencyException.
+ *aOutDeltaX = [aEvent deviceDeltaX];
+ *aOutDeltaY = [aEvent deviceDeltaY];
+ return;
+ }
+
+ // This is only hit pre-10.7 when we are called on a scroll event that does
+ // not contain pixel scrolling information.
+ CGFloat lineDeltaPixels = 12;
+ *aOutDeltaX = [aEvent deltaX] * lineDeltaPixels;
+ *aOutDeltaY = [aEvent deltaY] * lineDeltaPixels;
+}
+
+BOOL nsCocoaUtils::EventHasPhaseInformation(NSEvent* aEvent)
+{
+ if (![aEvent respondsToSelector:@selector(phase)]) {
+ return NO;
+ }
+ return EventPhase(aEvent) != NSEventPhaseNone ||
+ EventMomentumPhase(aEvent) != NSEventPhaseNone;
+}
+
+void nsCocoaUtils::HideOSChromeOnScreen(bool aShouldHide)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Keep track of how many hiding requests have been made, so that they can
+ // be nested.
+ static int sHiddenCount = 0;
+
+ sHiddenCount += aShouldHide ? 1 : -1;
+ NS_ASSERTION(sHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls");
+
+ NSApplicationPresentationOptions options =
+ sHiddenCount <= 0 ? NSApplicationPresentationDefault :
+ NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
+ [NSApp setPresentationOptions:options];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
+nsIWidget* nsCocoaUtils::GetHiddenWindowWidget()
+{
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ if (!appShell) {
+ NS_WARNING("Couldn't get AppShellService in order to get hidden window ref");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIXULWindow> hiddenWindow;
+ appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
+ if (!hiddenWindow) {
+ // Don't warn, this happens during shutdown, bug 358607.
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIBaseWindow> baseHiddenWindow;
+ baseHiddenWindow = do_GetInterface(hiddenWindow);
+ if (!baseHiddenWindow) {
+ NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIXULWindow)");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIWidget> hiddenWindowWidget;
+ if (NS_FAILED(baseHiddenWindow->GetMainWidget(getter_AddRefs(hiddenWindowWidget)))) {
+ NS_WARNING("Couldn't get nsIWidget from hidden window (nsIBaseWindow)");
+ return nullptr;
+ }
+
+ return hiddenWindowWidget;
+}
+
+void nsCocoaUtils::PrepareForNativeAppModalDialog()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Don't do anything if this is embedding. We'll assume that if there is no hidden
+ // window we shouldn't do anything, and that should cover the embedding case.
+ nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (!hiddenWindowMenuBar)
+ return;
+
+ // First put up the hidden window menu bar so that app menu event handling is correct.
+ hiddenWindowMenuBar->Paint();
+
+ NSMenu* mainMenu = [NSApp mainMenu];
+ NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+
+ // Create new menu bar for use with modal dialog
+ NSMenu* newMenuBar = [[NSMenu alloc] initWithTitle:@""];
+
+ // Swap in our app menu. Note that the event target is whatever window is up when
+ // the app modal dialog goes up.
+ NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
+ [mainMenu removeItemAtIndex:0];
+ [newMenuBar insertItem:firstMenuItem atIndex:0];
+ [firstMenuItem release];
+
+ // Add standard edit menu
+ [newMenuBar addItem:nsMenuUtilsX::GetStandardEditMenuItem()];
+
+ // Show the new menu bar
+ [NSApp setMainMenu:newMenuBar];
+ [newMenuBar release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaUtils::CleanUpAfterNativeAppModalDialog()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Don't do anything if this is embedding. We'll assume that if there is no hidden
+ // window we shouldn't do anything, and that should cover the embedding case.
+ nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (!hiddenWindowMenuBar)
+ return;
+
+ NSWindow* mainWindow = [NSApp mainWindow];
+ if (!mainWindow)
+ hiddenWindowMenuBar->Paint();
+ else
+ [WindowDelegate paintMenubarForWindow:mainWindow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void data_ss_release_callback(void *aDataSourceSurface,
+ const void *data,
+ size_t size)
+{
+ if (aDataSourceSurface) {
+ static_cast<DataSourceSurface*>(aDataSourceSurface)->Unmap();
+ static_cast<DataSourceSurface*>(aDataSourceSurface)->Release();
+ }
+}
+
+nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface,
+ CGImageRef* aResult)
+{
+ RefPtr<DataSourceSurface> dataSurface;
+
+ if (aSurface->GetFormat() == SurfaceFormat::B8G8R8A8) {
+ dataSurface = aSurface->GetDataSurface();
+ } else {
+ // CGImageCreate only supports 16- and 32-bit bit-depth
+ // Convert format to SurfaceFormat::B8G8R8A8
+ dataSurface = gfxUtils::
+ CopySurfaceToDataSourceSurfaceWithFormat(aSurface,
+ SurfaceFormat::B8G8R8A8);
+ }
+
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+ if (height < 1 || width < 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+ // The Unmap() call happens in data_ss_release_callback
+
+ // Create a CGImageRef with the bits from the image, taking into account
+ // the alpha ordering and endianness of the machine so we don't have to
+ // touch the bits ourselves.
+ CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(dataSurface.forget().take(),
+ map.mData,
+ map.mStride * height,
+ data_ss_release_callback);
+ CGColorSpaceRef colorSpace = ::CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
+ *aResult = ::CGImageCreate(width,
+ height,
+ 8,
+ 32,
+ map.mStride,
+ colorSpace,
+ kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst,
+ dataProvider,
+ NULL,
+ 0,
+ kCGRenderingIntentDefault);
+ ::CGColorSpaceRelease(colorSpace);
+ ::CGDataProviderRelease(dataProvider);
+ return *aResult ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsCocoaUtils::CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Be very careful when creating the NSImage that the backing NSImageRep is
+ // exactly 1:1 with the input image. On a retina display, both [NSImage
+ // lockFocus] and [NSImage initWithCGImage:size:] will create an image with a
+ // 2x backing NSImageRep. This prevents NSCursor from recognizing a retina
+ // cursor, which only occurs if pixelsWide and pixelsHigh are exactly 2x the
+ // size of the NSImage.
+ //
+ // For example, if a 32x32 SVG cursor is rendered on a retina display, then
+ // aInputImage will be 64x64. The resulting NSImage will be scaled back down
+ // to 32x32 so it stays the correct size on the screen by changing its size
+ // (resizing a NSImage only scales the image and doesn't resample the data).
+ // If aInputImage is converted using [NSImage initWithCGImage:size:] then the
+ // bitmap will be 128x128 and NSCursor won't recognize a retina cursor, since
+ // it will expect a 64x64 bitmap.
+
+ int32_t width = ::CGImageGetWidth(aInputImage);
+ int32_t height = ::CGImageGetHeight(aInputImage);
+ NSRect imageRect = ::NSMakeRect(0.0, 0.0, width, height);
+
+ NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:NULL
+ pixelsWide:width
+ pixelsHigh:height
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bitmapFormat:NSAlphaFirstBitmapFormat
+ bytesPerRow:0
+ bitsPerPixel:0];
+
+ NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:context];
+
+ // Get the Quartz context and draw.
+ CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ ::CGContextDrawImage(imageContext, *(CGRect*)&imageRect, aInputImage);
+
+ [NSGraphicsContext restoreGraphicsState];
+
+ *aResult = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
+ [*aResult addRepresentation:offscreenRep];
+ [offscreenRep release];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor)
+{
+ RefPtr<SourceSurface> surface;
+ int32_t width = 0, height = 0;
+ aImage->GetWidth(&width);
+ aImage->GetHeight(&height);
+
+ // Render a vector image at the correct resolution on a retina display
+ if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) {
+ IntSize scaledSize = IntSize::Ceil(width * scaleFactor, height * scaleFactor);
+
+ RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()->
+ CreateOffscreenContentDrawTarget(scaledSize, SurfaceFormat::B8G8R8A8);
+ if (!drawTarget || !drawTarget->IsValid()) {
+ NS_ERROR("Failed to create valid DrawTarget");
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(drawTarget);
+ MOZ_ASSERT(context);
+
+ mozilla::image::DrawResult res =
+ aImage->Draw(context, scaledSize, ImageRegion::Create(scaledSize),
+ aWhichFrame, SamplingFilter::POINT,
+ /* no SVGImageContext */ Nothing(),
+ imgIContainer::FLAG_SYNC_DECODE);
+
+ if (res != mozilla::image::DrawResult::SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ surface = drawTarget->Snapshot();
+ } else {
+ surface = aImage->GetFrame(aWhichFrame, imgIContainer::FLAG_SYNC_DECODE);
+ }
+
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ CGImageRef imageRef = NULL;
+ nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
+ if (NS_FAILED(rv) || !imageRef) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = nsCocoaUtils::CreateNSImageFromCGImage(imageRef, aResult);
+ if (NS_FAILED(rv) || !aResult) {
+ return NS_ERROR_FAILURE;
+ }
+ ::CGImageRelease(imageRef);
+
+ // Ensure the image will be rendered the correct size on a retina display
+ NSSize size = NSMakeSize(width, height);
+ [*aResult setSize:size];
+ [[[*aResult representations] objectAtIndex:0] setSize:size];
+ return NS_OK;
+}
+
+// static
+void
+nsCocoaUtils::GetStringForNSString(const NSString *aSrc, nsAString& aDist)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aSrc) {
+ aDist.Truncate();
+ return;
+ }
+
+ aDist.SetLength([aSrc length]);
+ [aSrc getCharacters: reinterpret_cast<unichar*>(aDist.BeginWriting())];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// static
+NSString*
+nsCocoaUtils::ToNSString(const nsAString& aString)
+{
+ if (aString.IsEmpty()) {
+ return [NSString string];
+ }
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aString.BeginReading())
+ length:aString.Length()];
+}
+
+// static
+void
+nsCocoaUtils::GeckoRectToNSRect(const nsIntRect& aGeckoRect,
+ NSRect& aOutCocoaRect)
+{
+ aOutCocoaRect.origin.x = aGeckoRect.x;
+ aOutCocoaRect.origin.y = aGeckoRect.y;
+ aOutCocoaRect.size.width = aGeckoRect.width;
+ aOutCocoaRect.size.height = aGeckoRect.height;
+}
+
+// static
+void
+nsCocoaUtils::NSRectToGeckoRect(const NSRect& aCocoaRect,
+ nsIntRect& aOutGeckoRect)
+{
+ aOutGeckoRect.x = NSToIntRound(aCocoaRect.origin.x);
+ aOutGeckoRect.y = NSToIntRound(aCocoaRect.origin.y);
+ aOutGeckoRect.width = NSToIntRound(aCocoaRect.origin.x + aCocoaRect.size.width) - aOutGeckoRect.x;
+ aOutGeckoRect.height = NSToIntRound(aCocoaRect.origin.y + aCocoaRect.size.height) - aOutGeckoRect.y;
+}
+
+// static
+NSEvent*
+nsCocoaUtils::MakeNewCocoaEventWithType(NSEventType aEventType, NSEvent *aEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSEvent* newEvent =
+ [NSEvent keyEventWithType:aEventType
+ location:[aEvent locationInWindow]
+ modifierFlags:[aEvent modifierFlags]
+ timestamp:[aEvent timestamp]
+ windowNumber:[aEvent windowNumber]
+ context:[aEvent context]
+ characters:[aEvent characters]
+ charactersIgnoringModifiers:[aEvent charactersIgnoringModifiers]
+ isARepeat:[aEvent isARepeat]
+ keyCode:[aEvent keyCode]];
+ return newEvent;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// static
+void
+nsCocoaUtils::InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent)
+{
+ memset(aNPCocoaEvent, 0, sizeof(NPCocoaEvent));
+}
+
+// static
+void
+nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent,
+ NSEvent* aNativeEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ aInputEvent.mModifiers = ModifiersForEvent(aNativeEvent);
+ aInputEvent.mTime = PR_IntervalNow();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// static
+Modifiers
+nsCocoaUtils::ModifiersForEvent(NSEvent* aNativeEvent)
+{
+ NSUInteger modifiers =
+ aNativeEvent ? [aNativeEvent modifierFlags] : [NSEvent modifierFlags];
+ Modifiers result = 0;
+ if (modifiers & NSShiftKeyMask) {
+ result |= MODIFIER_SHIFT;
+ }
+ if (modifiers & NSControlKeyMask) {
+ result |= MODIFIER_CONTROL;
+ }
+ if (modifiers & NSAlternateKeyMask) {
+ result |= MODIFIER_ALT;
+ // Mac's option key is similar to other platforms' AltGr key.
+ // Let's set AltGr flag when option key is pressed for consistency with
+ // other platforms.
+ result |= MODIFIER_ALTGRAPH;
+ }
+ if (modifiers & NSCommandKeyMask) {
+ result |= MODIFIER_META;
+ }
+
+ if (modifiers & NSAlphaShiftKeyMask) {
+ result |= MODIFIER_CAPSLOCK;
+ }
+ // Mac doesn't have NumLock key. We can assume that NumLock is always locked
+ // if user is using a keyboard which has numpad. Otherwise, if user is using
+ // a keyboard which doesn't have numpad, e.g., MacBook's keyboard, we can
+ // assume that NumLock is always unlocked.
+ // Unfortunately, we cannot know whether current keyboard has numpad or not.
+ // We should notify locked state only when keys in numpad are pressed.
+ // By this, web applications may not be confused by unexpected numpad key's
+ // key event with unlocked state.
+ if (modifiers & NSNumericPadKeyMask) {
+ result |= MODIFIER_NUMLOCK;
+ }
+
+ // Be aware, NSFunctionKeyMask is included when arrow keys, home key or some
+ // other keys are pressed. We cannot check whether 'fn' key is pressed or
+ // not by the flag.
+
+ return result;
+}
+
+// static
+UInt32
+nsCocoaUtils::ConvertToCarbonModifier(NSUInteger aCocoaModifier)
+{
+ UInt32 carbonModifier = 0;
+ if (aCocoaModifier & NSAlphaShiftKeyMask) {
+ carbonModifier |= alphaLock;
+ }
+ if (aCocoaModifier & NSControlKeyMask) {
+ carbonModifier |= controlKey;
+ }
+ if (aCocoaModifier & NSAlternateKeyMask) {
+ carbonModifier |= optionKey;
+ }
+ if (aCocoaModifier & NSShiftKeyMask) {
+ carbonModifier |= shiftKey;
+ }
+ if (aCocoaModifier & NSCommandKeyMask) {
+ carbonModifier |= cmdKey;
+ }
+ if (aCocoaModifier & NSNumericPadKeyMask) {
+ carbonModifier |= kEventKeyModifierNumLockMask;
+ }
+ if (aCocoaModifier & NSFunctionKeyMask) {
+ carbonModifier |= kEventKeyModifierFnMask;
+ }
+ return carbonModifier;
+}
+
+// While HiDPI support is not 100% complete and tested, we'll have a pref
+// to allow it to be turned off in case of problems (or for testing purposes).
+
+// gfx.hidpi.enabled is an integer with the meaning:
+// <= 0 : HiDPI support is disabled
+// 1 : HiDPI enabled provided all screens have the same backing resolution
+// > 1 : HiDPI enabled even if there are a mixture of screen modes
+
+// All the following code is to be removed once HiDPI work is more complete.
+
+static bool sHiDPIEnabled = false;
+static bool sHiDPIPrefInitialized = false;
+
+// static
+bool
+nsCocoaUtils::HiDPIEnabled()
+{
+ if (!sHiDPIPrefInitialized) {
+ sHiDPIPrefInitialized = true;
+
+ int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1);
+ if (prefSetting <= 0) {
+ return false;
+ }
+
+ // prefSetting is at least 1, need to check attached screens...
+
+ int scaleFactors = 0; // used as a bitset to track the screen types found
+ NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+ while (NSScreen *screen = [screenEnum nextObject]) {
+ NSDictionary *desc = [screen deviceDescription];
+ if ([desc objectForKey:NSDeviceIsScreen] == nil) {
+ continue;
+ }
+ CGFloat scale =
+ [screen respondsToSelector:@selector(backingScaleFactor)] ?
+ [screen backingScaleFactor] : 1.0;
+ // Currently, we only care about differentiating "1.0" and "2.0",
+ // so we set one of the two low bits to record which.
+ if (scale > 1.0) {
+ scaleFactors |= 2;
+ } else {
+ scaleFactors |= 1;
+ }
+ }
+
+ // Now scaleFactors will be:
+ // 0 if no screens (supporting backingScaleFactor) found
+ // 1 if only lo-DPI screens
+ // 2 if only hi-DPI screens
+ // 3 if both lo- and hi-DPI screens
+ // We'll enable HiDPI support if there's only a single screen type,
+ // OR if the pref setting is explicitly greater than 1.
+ sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1);
+ }
+
+ return sHiDPIEnabled;
+}
+
+void
+nsCocoaUtils::GetCommandsFromKeyEvent(NSEvent* aEvent,
+ nsTArray<KeyBindingsCommand>& aCommands)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aEvent);
+
+ static NativeKeyBindingsRecorder* sNativeKeyBindingsRecorder;
+ if (!sNativeKeyBindingsRecorder) {
+ sNativeKeyBindingsRecorder = [NativeKeyBindingsRecorder new];
+ }
+
+ [sNativeKeyBindingsRecorder startRecording:aCommands];
+
+ // This will trigger 0 - N calls to doCommandBySelector: and insertText:
+ [sNativeKeyBindingsRecorder
+ interpretKeyEvents:[NSArray arrayWithObject:aEvent]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@implementation NativeKeyBindingsRecorder
+
+- (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands
+{
+ mCommands = &aCommands;
+ mCommands->Clear();
+}
+
+- (void)doCommandBySelector:(SEL)aSelector
+{
+ KeyBindingsCommand command = {
+ aSelector,
+ nil
+ };
+
+ mCommands->AppendElement(command);
+}
+
+- (void)insertText:(id)aString
+{
+ KeyBindingsCommand command = {
+ @selector(insertText:),
+ aString
+ };
+
+ mCommands->AppendElement(command);
+}
+
+@end // NativeKeyBindingsRecorder
+
+struct KeyConversionData
+{
+ const char* str;
+ size_t strLength;
+ uint32_t geckoKeyCode;
+ uint32_t charCode;
+};
+
+static const KeyConversionData gKeyConversions[] = {
+
+#define KEYCODE_ENTRY(aStr, aCode) \
+ {#aStr, sizeof(#aStr) - 1, NS_##aStr, aCode}
+
+// Some keycodes may have different name in nsIDOMKeyEvent from its key name.
+#define KEYCODE_ENTRY2(aStr, aNSName, aCode) \
+ {#aStr, sizeof(#aStr) - 1, NS_##aNSName, aCode}
+
+ KEYCODE_ENTRY(VK_CANCEL, 0x001B),
+ KEYCODE_ENTRY(VK_DELETE, NSDeleteFunctionKey),
+ KEYCODE_ENTRY(VK_BACK, NSBackspaceCharacter),
+ KEYCODE_ENTRY2(VK_BACK_SPACE, VK_BACK, NSBackspaceCharacter),
+ KEYCODE_ENTRY(VK_TAB, NSTabCharacter),
+ KEYCODE_ENTRY(VK_CLEAR, NSClearLineFunctionKey),
+ KEYCODE_ENTRY(VK_RETURN, NSEnterCharacter),
+ KEYCODE_ENTRY(VK_SHIFT, 0),
+ KEYCODE_ENTRY(VK_CONTROL, 0),
+ KEYCODE_ENTRY(VK_ALT, 0),
+ KEYCODE_ENTRY(VK_PAUSE, NSPauseFunctionKey),
+ KEYCODE_ENTRY(VK_CAPS_LOCK, 0),
+ KEYCODE_ENTRY(VK_ESCAPE, 0),
+ KEYCODE_ENTRY(VK_SPACE, ' '),
+ KEYCODE_ENTRY(VK_PAGE_UP, NSPageUpFunctionKey),
+ KEYCODE_ENTRY(VK_PAGE_DOWN, NSPageDownFunctionKey),
+ KEYCODE_ENTRY(VK_END, NSEndFunctionKey),
+ KEYCODE_ENTRY(VK_HOME, NSHomeFunctionKey),
+ KEYCODE_ENTRY(VK_LEFT, NSLeftArrowFunctionKey),
+ KEYCODE_ENTRY(VK_UP, NSUpArrowFunctionKey),
+ KEYCODE_ENTRY(VK_RIGHT, NSRightArrowFunctionKey),
+ KEYCODE_ENTRY(VK_DOWN, NSDownArrowFunctionKey),
+ KEYCODE_ENTRY(VK_PRINTSCREEN, NSPrintScreenFunctionKey),
+ KEYCODE_ENTRY(VK_INSERT, NSInsertFunctionKey),
+ KEYCODE_ENTRY(VK_HELP, NSHelpFunctionKey),
+ KEYCODE_ENTRY(VK_0, '0'),
+ KEYCODE_ENTRY(VK_1, '1'),
+ KEYCODE_ENTRY(VK_2, '2'),
+ KEYCODE_ENTRY(VK_3, '3'),
+ KEYCODE_ENTRY(VK_4, '4'),
+ KEYCODE_ENTRY(VK_5, '5'),
+ KEYCODE_ENTRY(VK_6, '6'),
+ KEYCODE_ENTRY(VK_7, '7'),
+ KEYCODE_ENTRY(VK_8, '8'),
+ KEYCODE_ENTRY(VK_9, '9'),
+ KEYCODE_ENTRY(VK_SEMICOLON, ':'),
+ KEYCODE_ENTRY(VK_EQUALS, '='),
+ KEYCODE_ENTRY(VK_A, 'A'),
+ KEYCODE_ENTRY(VK_B, 'B'),
+ KEYCODE_ENTRY(VK_C, 'C'),
+ KEYCODE_ENTRY(VK_D, 'D'),
+ KEYCODE_ENTRY(VK_E, 'E'),
+ KEYCODE_ENTRY(VK_F, 'F'),
+ KEYCODE_ENTRY(VK_G, 'G'),
+ KEYCODE_ENTRY(VK_H, 'H'),
+ KEYCODE_ENTRY(VK_I, 'I'),
+ KEYCODE_ENTRY(VK_J, 'J'),
+ KEYCODE_ENTRY(VK_K, 'K'),
+ KEYCODE_ENTRY(VK_L, 'L'),
+ KEYCODE_ENTRY(VK_M, 'M'),
+ KEYCODE_ENTRY(VK_N, 'N'),
+ KEYCODE_ENTRY(VK_O, 'O'),
+ KEYCODE_ENTRY(VK_P, 'P'),
+ KEYCODE_ENTRY(VK_Q, 'Q'),
+ KEYCODE_ENTRY(VK_R, 'R'),
+ KEYCODE_ENTRY(VK_S, 'S'),
+ KEYCODE_ENTRY(VK_T, 'T'),
+ KEYCODE_ENTRY(VK_U, 'U'),
+ KEYCODE_ENTRY(VK_V, 'V'),
+ KEYCODE_ENTRY(VK_W, 'W'),
+ KEYCODE_ENTRY(VK_X, 'X'),
+ KEYCODE_ENTRY(VK_Y, 'Y'),
+ KEYCODE_ENTRY(VK_Z, 'Z'),
+ KEYCODE_ENTRY(VK_CONTEXT_MENU, NSMenuFunctionKey),
+ KEYCODE_ENTRY(VK_NUMPAD0, '0'),
+ KEYCODE_ENTRY(VK_NUMPAD1, '1'),
+ KEYCODE_ENTRY(VK_NUMPAD2, '2'),
+ KEYCODE_ENTRY(VK_NUMPAD3, '3'),
+ KEYCODE_ENTRY(VK_NUMPAD4, '4'),
+ KEYCODE_ENTRY(VK_NUMPAD5, '5'),
+ KEYCODE_ENTRY(VK_NUMPAD6, '6'),
+ KEYCODE_ENTRY(VK_NUMPAD7, '7'),
+ KEYCODE_ENTRY(VK_NUMPAD8, '8'),
+ KEYCODE_ENTRY(VK_NUMPAD9, '9'),
+ KEYCODE_ENTRY(VK_MULTIPLY, '*'),
+ KEYCODE_ENTRY(VK_ADD, '+'),
+ KEYCODE_ENTRY(VK_SEPARATOR, 0),
+ KEYCODE_ENTRY(VK_SUBTRACT, '-'),
+ KEYCODE_ENTRY(VK_DECIMAL, '.'),
+ KEYCODE_ENTRY(VK_DIVIDE, '/'),
+ KEYCODE_ENTRY(VK_F1, NSF1FunctionKey),
+ KEYCODE_ENTRY(VK_F2, NSF2FunctionKey),
+ KEYCODE_ENTRY(VK_F3, NSF3FunctionKey),
+ KEYCODE_ENTRY(VK_F4, NSF4FunctionKey),
+ KEYCODE_ENTRY(VK_F5, NSF5FunctionKey),
+ KEYCODE_ENTRY(VK_F6, NSF6FunctionKey),
+ KEYCODE_ENTRY(VK_F7, NSF7FunctionKey),
+ KEYCODE_ENTRY(VK_F8, NSF8FunctionKey),
+ KEYCODE_ENTRY(VK_F9, NSF9FunctionKey),
+ KEYCODE_ENTRY(VK_F10, NSF10FunctionKey),
+ KEYCODE_ENTRY(VK_F11, NSF11FunctionKey),
+ KEYCODE_ENTRY(VK_F12, NSF12FunctionKey),
+ KEYCODE_ENTRY(VK_F13, NSF13FunctionKey),
+ KEYCODE_ENTRY(VK_F14, NSF14FunctionKey),
+ KEYCODE_ENTRY(VK_F15, NSF15FunctionKey),
+ KEYCODE_ENTRY(VK_F16, NSF16FunctionKey),
+ KEYCODE_ENTRY(VK_F17, NSF17FunctionKey),
+ KEYCODE_ENTRY(VK_F18, NSF18FunctionKey),
+ KEYCODE_ENTRY(VK_F19, NSF19FunctionKey),
+ KEYCODE_ENTRY(VK_F20, NSF20FunctionKey),
+ KEYCODE_ENTRY(VK_F21, NSF21FunctionKey),
+ KEYCODE_ENTRY(VK_F22, NSF22FunctionKey),
+ KEYCODE_ENTRY(VK_F23, NSF23FunctionKey),
+ KEYCODE_ENTRY(VK_F24, NSF24FunctionKey),
+ KEYCODE_ENTRY(VK_NUM_LOCK, NSClearLineFunctionKey),
+ KEYCODE_ENTRY(VK_SCROLL_LOCK, NSScrollLockFunctionKey),
+ KEYCODE_ENTRY(VK_COMMA, ','),
+ KEYCODE_ENTRY(VK_PERIOD, '.'),
+ KEYCODE_ENTRY(VK_SLASH, '/'),
+ KEYCODE_ENTRY(VK_BACK_QUOTE, '`'),
+ KEYCODE_ENTRY(VK_OPEN_BRACKET, '['),
+ KEYCODE_ENTRY(VK_BACK_SLASH, '\\'),
+ KEYCODE_ENTRY(VK_CLOSE_BRACKET, ']'),
+ KEYCODE_ENTRY(VK_QUOTE, '\'')
+
+#undef KEYCODE_ENTRY
+
+};
+
+uint32_t
+nsCocoaUtils::ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName)
+{
+ if (aKeyCodeName.IsEmpty()) {
+ return 0;
+ }
+
+ nsAutoCString keyCodeName;
+ keyCodeName.AssignWithConversion(aKeyCodeName);
+ // We want case-insensitive comparison with data stored as uppercase.
+ ToUpperCase(keyCodeName);
+
+ uint32_t keyCodeNameLength = keyCodeName.Length();
+ const char* keyCodeNameStr = keyCodeName.get();
+ for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
+ if (keyCodeNameLength == gKeyConversions[i].strLength &&
+ nsCRT::strcmp(gKeyConversions[i].str, keyCodeNameStr) == 0) {
+ return gKeyConversions[i].charCode;
+ }
+ }
+
+ return 0;
+}
+
+uint32_t
+nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode)
+{
+ if (!aKeyCode) {
+ return 0;
+ }
+
+ for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
+ if (gKeyConversions[i].geckoKeyCode == aKeyCode) {
+ return gKeyConversions[i].charCode;
+ }
+ }
+
+ return 0;
+}
+
+NSMutableAttributedString*
+nsCocoaUtils::GetNSMutableAttributedString(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRanges,
+ const bool aIsVertical,
+ const CGFloat aBackingScaleFactor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL
+
+ NSString* nsstr = nsCocoaUtils::ToNSString(aText);
+ NSMutableAttributedString* attrStr =
+ [[[NSMutableAttributedString alloc] initWithString:nsstr
+ attributes:nil] autorelease];
+
+ int32_t lastOffset = aText.Length();
+ for (auto i = aFontRanges.Length(); i > 0; --i) {
+ const FontRange& fontRange = aFontRanges[i - 1];
+ NSString* fontName = nsCocoaUtils::ToNSString(fontRange.mFontName);
+ CGFloat fontSize = fontRange.mFontSize / aBackingScaleFactor;
+ NSFont* font = [NSFont fontWithName:fontName size:fontSize];
+ if (!font) {
+ font = [NSFont systemFontOfSize:fontSize];
+ }
+
+ NSDictionary* attrs = @{ NSFontAttributeName: font };
+ NSRange range = NSMakeRange(fontRange.mStartOffset,
+ lastOffset - fontRange.mStartOffset);
+ [attrStr setAttributes:attrs range:range];
+ lastOffset = fontRange.mStartOffset;
+ }
+
+ if (aIsVertical) {
+ [attrStr addAttribute:NSVerticalGlyphFormAttributeName
+ value:[NSNumber numberWithInt: 1]
+ range:NSMakeRange(0, [attrStr length])];
+ }
+
+ return attrStr;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL
+}
diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h
new file mode 100644
index 000000000..6338f474d
--- /dev/null
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -0,0 +1,423 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCocoaWindow_h_
+#define nsCocoaWindow_h_
+
+#undef DARWIN
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsBaseWidget.h"
+#include "nsPIWidgetCocoa.h"
+#include "nsCocoaUtils.h"
+
+class nsCocoaWindow;
+class nsChildView;
+class nsMenuBarX;
+@class ChildView;
+
+typedef struct _nsCocoaWindowList {
+ _nsCocoaWindowList() : prev(nullptr), window(nullptr) {}
+ struct _nsCocoaWindowList *prev;
+ nsCocoaWindow *window; // Weak
+} nsCocoaWindowList;
+
+// NSWindow subclass that is the base class for all of our own window classes.
+// Among other things, this class handles the storage of those settings that
+// need to be persisted across window destruction and reconstruction, i.e. when
+// switching to and from fullscreen mode.
+// We don't save shadow, transparency mode or background color because it's not
+// worth the hassle - Gecko will reset them anyway as soon as the window is
+// resized.
+@interface BaseWindow : NSWindow
+{
+ // Data Storage
+ NSMutableDictionary* mState;
+ BOOL mDrawsIntoWindowFrame;
+ NSColor* mActiveTitlebarColor;
+ NSColor* mInactiveTitlebarColor;
+
+ // Shadow
+ BOOL mScheduledShadowInvalidation;
+
+ // Invalidation disabling
+ BOOL mDisabledNeedsDisplay;
+
+ // DPI cache. Getting the physical screen size (CGDisplayScreenSize)
+ // is ridiculously slow, so we cache it in the toplevel window for all
+ // descendants to use.
+ float mDPI;
+
+ NSTrackingArea* mTrackingArea;
+
+ NSRect mDirtyRect;
+
+ BOOL mBeingShown;
+ BOOL mDrawTitle;
+ BOOL mBrightTitlebarForeground;
+ BOOL mUseMenuStyle;
+}
+
+- (void)importState:(NSDictionary*)aState;
+- (NSMutableDictionary*)exportState;
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
+- (BOOL)drawsContentsIntoWindowFrame;
+- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive;
+- (NSColor*)titlebarColorForActiveWindow:(BOOL)aActive;
+
+- (void)deferredInvalidateShadow;
+- (void)invalidateShadow;
+- (float)getDPI;
+
+- (void)mouseEntered:(NSEvent*)aEvent;
+- (void)mouseExited:(NSEvent*)aEvent;
+- (void)mouseMoved:(NSEvent*)aEvent;
+- (void)updateTrackingArea;
+- (NSView*)trackingAreaView;
+
+- (void)setBeingShown:(BOOL)aValue;
+- (BOOL)isBeingShown;
+- (BOOL)isVisibleOrBeingShown;
+
+- (ChildView*)mainChildView;
+
+- (NSArray*)titlebarControls;
+
+- (void)setWantsTitleDrawn:(BOOL)aDrawTitle;
+- (BOOL)wantsTitleDrawn;
+
+- (void)setUseBrightTitlebarForeground:(BOOL)aBrightForeground;
+- (BOOL)useBrightTitlebarForeground;
+
+- (void)disableSetNeedsDisplay;
+- (void)enableSetNeedsDisplay;
+
+- (NSRect)getAndResetNativeDirtyRect;
+
+- (void)setUseMenuStyle:(BOOL)aValue;
+
+@end
+
+@interface NSWindow (Undocumented)
+
+// If a window has been explicitly removed from the "window cache" (to
+// deactivate it), it's sometimes necessary to "reset" it to reactivate it
+// (and put it back in the "window cache"). One way to do this, which Apple
+// often uses, is to set the "window number" to '-1' and then back to its
+// original value.
+- (void)_setWindowNumber:(NSInteger)aNumber;
+
+// If we set the window's stylemask to be textured, the corners on the bottom of
+// the window are rounded by default. We use this private method to make
+// the corners square again, a la Safari. Starting with 10.7, all windows have
+// rounded bottom corners, so this call doesn't have any effect there.
+- (void)setBottomCornerRounded:(BOOL)rounded;
+- (BOOL)bottomCornerRounded;
+
+// Present in the same form on OS X since at least OS X 10.5.
+- (NSRect)contentRectForFrameRect:(NSRect)windowFrame styleMask:(NSUInteger)windowStyle;
+- (NSRect)frameRectForContentRect:(NSRect)windowContentRect styleMask:(NSUInteger)windowStyle;
+
+// Present since at least OS X 10.5. The OS calls this method on NSWindow
+// (and its subclasses) to find out which NSFrameView subclass to instantiate
+// to create its "frame view".
++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask;
+
+@end
+
+@interface PopupWindow : BaseWindow
+{
+@private
+ BOOL mIsContextMenu;
+}
+
+- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
+ backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation;
+- (BOOL)isContextMenu;
+- (void)setIsContextMenu:(BOOL)flag;
+- (BOOL)canBecomeMainWindow;
+
+@end
+
+@interface BorderlessWindow : BaseWindow
+{
+}
+
+- (BOOL)canBecomeKeyWindow;
+- (BOOL)canBecomeMainWindow;
+
+@end
+
+@interface WindowDelegate : NSObject <NSWindowDelegate>
+{
+ nsCocoaWindow* mGeckoWindow; // [WEAK] (we are owned by the window)
+ // Used to avoid duplication when we send NS_ACTIVATE and
+ // NS_DEACTIVATE to Gecko for toplevel widgets. Starts out
+ // false.
+ bool mToplevelActiveState;
+ BOOL mHasEverBeenZoomed;
+}
++ (void)paintMenubarForWindow:(NSWindow*)aWindow;
+- (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind;
+- (void)windowDidResize:(NSNotification*)aNotification;
+- (nsCocoaWindow*)geckoWidget;
+- (bool)toplevelActiveState;
+- (void)sendToplevelActivateEvents;
+- (void)sendToplevelDeactivateEvents;
+@end
+
+@class ToolbarWindow;
+
+// NSColor subclass that allows us to draw separate colors both in the titlebar
+// and for background of the window.
+@interface TitlebarAndBackgroundColor : NSColor
+{
+ ToolbarWindow *mWindow; // [WEAK] (we are owned by the window)
+}
+
+- (id)initWithWindow:(ToolbarWindow*)aWindow;
+
+@end
+
+// NSWindow subclass for handling windows with toolbars.
+@interface ToolbarWindow : BaseWindow
+{
+ TitlebarAndBackgroundColor *mColor; // strong
+ CGFloat mUnifiedToolbarHeight;
+ NSColor *mBackgroundColor; // strong
+ NSView *mTitlebarView; // strong
+ NSRect mWindowButtonsRect;
+ NSRect mFullScreenButtonRect;
+}
+// Pass nil here to get the default appearance.
+- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive;
+- (void)setUnifiedToolbarHeight:(CGFloat)aHeight;
+- (CGFloat)unifiedToolbarHeight;
+- (CGFloat)titlebarHeight;
+- (NSRect)titlebarRect;
+- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync;
+- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect;
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
+- (void)setSheetAttachmentPosition:(CGFloat)aY;
+- (void)placeWindowButtons:(NSRect)aRect;
+- (void)placeFullScreenButton:(NSRect)aRect;
+- (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition;
+- (NSPoint)fullScreenButtonPositionWithDefaultPosition:(NSPoint)aDefaultPosition;
+- (void)setTemporaryBackgroundColor;
+- (void)restoreBackgroundColor;
+@end
+
+class nsCocoaWindow : public nsBaseWidget, public nsPIWidgetCocoa
+{
+private:
+ typedef nsBaseWidget Inherited;
+
+public:
+
+ nsCocoaWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSPIWIDGETCOCOA
+
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const DesktopIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+
+ virtual void Destroy() override;
+
+ NS_IMETHOD Show(bool aState) override;
+ virtual nsIWidget* GetSheetWindowParent(void) override;
+ NS_IMETHOD Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ virtual void SetModal(bool aState) override;
+ virtual void SetFakeModal(bool aState) override;
+ virtual bool IsRunningAppModal() override;
+ virtual bool IsVisible() const override;
+ NS_IMETHOD SetFocus(bool aState=false) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ virtual LayoutDeviceIntSize
+ ClientToWindowSize(const LayoutDeviceIntSize& aClientSize) override;
+
+ virtual void* GetNativeData(uint32_t aDataType) override;
+
+ virtual void ConstrainPosition(bool aAllowSlop,
+ int32_t *aX, int32_t *aY) override;
+ virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+ NS_IMETHOD Move(double aX, double aY) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
+
+ void EnteredFullScreen(bool aFullScreen, bool aNativeMode = true);
+ virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
+ virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ virtual nsresult MakeFullScreen(
+ bool aFullScreen, nsIScreen* aTargetScreen = nullptr) override final;
+ NS_IMETHOD MakeFullScreenWithNativeTransition(
+ bool aFullScreen, nsIScreen* aTargetScreen = nullptr) override final;
+ NSAnimation* FullscreenTransitionAnimation() const { return mFullscreenTransitionAnimation; }
+ void ReleaseFullscreenTransitionAnimation()
+ {
+ MOZ_ASSERT(mFullscreenTransitionAnimation,
+ "Should only be called when there is animation");
+ [mFullscreenTransitionAnimation release];
+ mFullscreenTransitionAnimation = nil;
+ }
+
+ NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ void ReportMoveEvent();
+ void ReportSizeEvent();
+ NS_IMETHOD SetCursor(nsCursor aCursor) override;
+ NS_IMETHOD SetCursor(imgIContainer* aCursor, uint32_t aHotspotX, uint32_t aHotspotY) override;
+
+ CGFloat BackingScaleFactor();
+ void BackingScaleFactorChanged();
+ virtual double GetDefaultScaleInternal() override;
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final {
+ return mozilla::DesktopToLayoutDeviceScale(BackingScaleFactor());
+ }
+
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override;
+
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+ NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+ virtual void CaptureRollupEvents(nsIRollupListener * aListener,
+ bool aDoCapture) override;
+ NS_IMETHOD GetAttention(int32_t aCycleCount) override;
+ virtual bool HasPendingInputEvent() override;
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+ virtual void SetWindowShadowStyle(int32_t aStyle) override;
+ virtual void SetShowsToolbarButton(bool aShow) override;
+ virtual void SetShowsFullScreenButton(bool aShow) override;
+ virtual void SetWindowAnimationType(WindowAnimationType aType) override;
+ virtual void SetDrawsTitle(bool aDrawTitle) override;
+ virtual void SetUseBrightTitlebarForeground(bool aBrightForeground) override;
+ NS_IMETHOD SetNonClientMargins(LayoutDeviceIntMargin& aMargins) override;
+ virtual void SetWindowTitlebarColor(nscolor aColor, bool aActive) override;
+ virtual void SetDrawsInTitlebar(bool aState) override;
+ virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) override;
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ void DispatchSizeModeEvent();
+
+ // be notified that a some form of drag event needs to go into Gecko
+ virtual bool DragEvent(unsigned int aMessage, mozilla::gfx::Point aMouseGlobal, UInt16 aKeyModifiers);
+
+ bool HasModalDescendents() { return mNumModalDescendents > 0; }
+ NSWindow *GetCocoaWindow() { return mWindow; }
+
+ void SetMenuBar(nsMenuBarX* aMenuBar);
+ nsMenuBarX *GetMenuBar();
+
+ NS_IMETHOD_(void) SetInputContext(
+ const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override
+ {
+ return mInputContext;
+ }
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) override;
+
+ void SetPopupWindowLevel();
+
+protected:
+ virtual ~nsCocoaWindow();
+
+ nsresult CreateNativeWindow(const NSRect &aRect,
+ nsBorderStyle aBorderStyle,
+ bool aRectIsFrameRect);
+ nsresult CreatePopupContentView(const LayoutDeviceIntRect &aRect);
+ void DestroyNativeWindow();
+ void AdjustWindowShadow();
+ void SetWindowBackgroundBlur();
+ void UpdateBounds();
+
+ nsresult DoResize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint, bool aConstrainToCurrentScreen);
+
+ inline bool ShouldToggleNativeFullscreen(bool aFullScreen,
+ bool aUseSystemTransition);
+ nsresult DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition);
+
+ virtual already_AddRefed<nsIWidget>
+ AllocateChildPopupWidget() override
+ {
+ static NS_DEFINE_IID(kCPopUpCID, NS_POPUP_CID);
+ nsCOMPtr<nsIWidget> widget = do_CreateInstance(kCPopUpCID);
+ return widget.forget();
+ }
+
+ nsIWidget* mParent; // if we're a popup, this is our parent [WEAK]
+ nsIWidget* mAncestorLink; // link to traverse ancestors [WEAK]
+ BaseWindow* mWindow; // our cocoa window [STRONG]
+ WindowDelegate* mDelegate; // our delegate for processing window msgs [STRONG]
+ RefPtr<nsMenuBarX> mMenuBar;
+ NSWindow* mSheetWindowParent; // if this is a sheet, this is the NSWindow it's attached to
+ nsChildView* mPopupContentView; // if this is a popup, this is its content widget
+ // if this is a toplevel window, and there is any ongoing fullscreen
+ // transition, it is the animation object.
+ NSAnimation* mFullscreenTransitionAnimation;
+ int32_t mShadowStyle;
+
+ CGFloat mBackingScaleFactor;
+
+ WindowAnimationType mAnimationType;
+
+ bool mWindowMadeHere; // true if we created the window, false for embedding
+ bool mSheetNeedsShow; // if this is a sheet, are we waiting to be shown?
+ // this is used for sibling sheet contention only
+ bool mInFullScreenMode;
+ bool mInFullScreenTransition; // true from the request to enter/exit fullscreen
+ // (MakeFullScreen() call) to EnteredFullScreen()
+ bool mModal;
+ bool mFakeModal;
+
+ // Only true on 10.7+ if SetShowsFullScreenButton(true) is called.
+ bool mSupportsNativeFullScreen;
+ // Whether we are currently using native fullscreen. It could be false because
+ // we are in the DOM fullscreen where we do not use the native fullscreen.
+ bool mInNativeFullScreenMode;
+
+ bool mIsAnimationSuppressed;
+
+ bool mInReportMoveEvent; // true if in a call to ReportMoveEvent().
+ bool mInResize; // true if in a call to DoResize().
+
+ int32_t mNumModalDescendents;
+ InputContext mInputContext;
+};
+
+#endif // nsCocoaWindow_h_
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
new file mode 100644
index 000000000..db120fbdd
--- /dev/null
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -0,0 +1,3834 @@
+/* -*- 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 "TextInputHandler.h"
+#include "nsObjCExceptions.h"
+#include "nsCOMPtr.h"
+#include "nsWidgetsCID.h"
+#include "nsIRollupListener.h"
+#include "nsChildView.h"
+#include "nsWindowMap.h"
+#include "nsAppShell.h"
+#include "nsIAppShellService.h"
+#include "nsIBaseWindow.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIXULWindow.h"
+#include "nsToolkit.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMElement.h"
+#include "nsThreadUtils.h"
+#include "nsMenuBarX.h"
+#include "nsMenuUtilsX.h"
+#include "nsStyleConsts.h"
+#include "nsNativeThemeColors.h"
+#include "nsNativeThemeCocoa.h"
+#include "nsChildView.h"
+#include "nsCocoaFeatures.h"
+#include "nsIScreenManager.h"
+#include "nsIWidgetListener.h"
+#include "nsIPresShell.h"
+#include "nsScreenCocoa.h"
+
+#include "gfxPlatform.h"
+#include "qcms.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace layers {
+class LayerManager;
+} // namespace layers
+} // namespace mozilla
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla;
+
+int32_t gXULModalLevel = 0;
+
+// In principle there should be only one app-modal window at any given time.
+// But sometimes, despite our best efforts, another window appears above the
+// current app-modal window. So we need to keep a linked list of app-modal
+// windows. (A non-sheet window that appears above an app-modal window is
+// also made app-modal.) See nsCocoaWindow::SetModal().
+nsCocoaWindowList *gGeckoAppModalWindowList = NULL;
+
+// defined in nsMenuBarX.mm
+extern NSMenu* sApplicationMenu; // Application menu shared by all menubars
+
+// defined in nsChildView.mm
+extern BOOL gSomeMenuBarPainted;
+
+#if !defined(MAC_OS_X_VERSION_10_12) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+
+@interface NSWindow(AutomaticWindowTabbing)
++ (void)setAllowsAutomaticWindowTabbing:(BOOL)allow;
+@end
+
+#endif
+
+extern "C" {
+ // CGSPrivate.h
+ typedef NSInteger CGSConnection;
+ typedef NSInteger CGSWindow;
+ typedef NSUInteger CGSWindowFilterRef;
+ extern CGSConnection _CGSDefaultConnection(void);
+ extern CGError CGSSetWindowShadowAndRimParameters(const CGSConnection cid, CGSWindow wid, float standardDeviation, float density, int offsetX, int offsetY, unsigned int flags);
+ extern CGError CGSSetWindowBackgroundBlurRadius(CGSConnection cid, CGSWindow wid, NSUInteger blur);
+}
+
+#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
+
+NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa)
+
+// A note on testing to see if your object is a sheet...
+// |mWindowType == eWindowType_sheet| is true if your gecko nsIWidget is a sheet
+// widget - whether or not the sheet is showing. |[mWindow isSheet]| will return
+// true *only when the sheet is actually showing*. Choose your test wisely.
+
+static void RollUpPopups()
+{
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE_VOID(rollupListener);
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (!rollupWidget)
+ return;
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+}
+
+nsCocoaWindow::nsCocoaWindow()
+: mParent(nullptr)
+, mAncestorLink(nullptr)
+, mWindow(nil)
+, mDelegate(nil)
+, mSheetWindowParent(nil)
+, mPopupContentView(nil)
+, mFullscreenTransitionAnimation(nil)
+, mShadowStyle(NS_STYLE_WINDOW_SHADOW_DEFAULT)
+, mBackingScaleFactor(0.0)
+, mAnimationType(nsIWidget::eGenericWindowAnimation)
+, mWindowMadeHere(false)
+, mSheetNeedsShow(false)
+, mInFullScreenMode(false)
+, mInFullScreenTransition(false)
+, mModal(false)
+, mFakeModal(false)
+, mSupportsNativeFullScreen(false)
+, mInNativeFullScreenMode(false)
+, mIsAnimationSuppressed(false)
+, mInReportMoveEvent(false)
+, mInResize(false)
+, mNumModalDescendents(0)
+{
+ if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)]) {
+ // Disable automatic tabbing on 10.12. We need to do this before we
+ // orderFront any of our windows.
+ [NSWindow setAllowsAutomaticWindowTabbing:NO];
+ }
+}
+
+void nsCocoaWindow::DestroyNativeWindow()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // We want to unhook the delegate here because we don't want events
+ // sent to it after this object has been destroyed.
+ [mWindow setDelegate:nil];
+ [mWindow close];
+ mWindow = nil;
+ [mDelegate autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsCocoaWindow::~nsCocoaWindow()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Notify the children that we're gone. Popup windows (e.g. tooltips) can
+ // have nsChildView children. 'kid' is an nsChildView object if and only if
+ // its 'type' is 'eWindowType_child'.
+ // childView->ResetParent() can change our list of children while it's
+ // being iterated, so the way we iterate the list must allow for this.
+ for (nsIWidget* kid = mLastChild; kid;) {
+ nsWindowType kidType = kid->WindowType();
+ if (kidType == eWindowType_child) {
+ nsChildView* childView = static_cast<nsChildView*>(kid);
+ kid = kid->GetPrevSibling();
+ childView->ResetParent();
+ } else {
+ nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
+ childWindow->mParent = nullptr;
+ childWindow->mAncestorLink = mAncestorLink;
+ kid = kid->GetPrevSibling();
+ }
+ }
+
+ if (mWindow && mWindowMadeHere) {
+ DestroyNativeWindow();
+ }
+
+ NS_IF_RELEASE(mPopupContentView);
+
+ // Deal with the possiblity that we're being destroyed while running modal.
+ if (mModal) {
+ NS_WARNING("Widget destroyed while running modal!");
+ --gXULModalLevel;
+ NS_ASSERTION(gXULModalLevel >= 0, "Weirdness setting modality!");
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Find the screen that overlaps aRect the most,
+// if none are found default to the mainScreen.
+static NSScreen*
+FindTargetScreenForRect(const DesktopIntRect& aRect)
+{
+ NSScreen *targetScreen = [NSScreen mainScreen];
+ NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+ int largestIntersectArea = 0;
+ while (NSScreen *screen = [screenEnum nextObject]) {
+ DesktopIntRect screenRect =
+ nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]);
+ screenRect = screenRect.Intersect(aRect);
+ int area = screenRect.width * screenRect.height;
+ if (area > largestIntersectArea) {
+ largestIntersectArea = area;
+ targetScreen = screen;
+ }
+ }
+ return targetScreen;
+}
+
+// fits the rect to the screen that contains the largest area of it,
+// or to aScreen if a screen is passed in
+// NB: this operates with aRect in desktop pixels
+static void
+FitRectToVisibleAreaForScreen(DesktopIntRect& aRect, NSScreen* aScreen)
+{
+ if (!aScreen) {
+ aScreen = FindTargetScreenForRect(aRect);
+ }
+
+ DesktopIntRect screenBounds =
+ nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame]);
+
+ if (aRect.width > screenBounds.width) {
+ aRect.width = screenBounds.width;
+ }
+ if (aRect.height > screenBounds.height) {
+ aRect.height = screenBounds.height;
+ }
+
+ if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) {
+ aRect.x += screenBounds.width - (aRect.x - screenBounds.x + aRect.width);
+ }
+ if (aRect.y - screenBounds.y + aRect.height > screenBounds.height) {
+ aRect.y += screenBounds.height - (aRect.y - screenBounds.y + aRect.height);
+ }
+
+ // If the left/top edge of the window is off the screen in either direction,
+ // then set the window to start at the left/top edge of the screen.
+ if (aRect.x < screenBounds.x || aRect.x > (screenBounds.x + screenBounds.width)) {
+ aRect.x = screenBounds.x;
+ }
+ if (aRect.y < screenBounds.y || aRect.y > (screenBounds.y + screenBounds.height)) {
+ aRect.y = screenBounds.y;
+ }
+}
+
+// Some applications use native popup windows
+// (native context menus, native tooltips)
+static bool UseNativePopupWindows()
+{
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return true;
+#else
+ return false;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+}
+
+// aRect here is specified in desktop pixels
+nsresult
+nsCocoaWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const DesktopIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Because the hidden window is created outside of an event loop,
+ // we have to provide an autorelease pool (see bug 559075).
+ nsAutoreleasePool localPool;
+
+ DesktopIntRect newBounds = aRect;
+ FitRectToVisibleAreaForScreen(newBounds, nullptr);
+
+ // Set defaults which can be overriden from aInitData in BaseCreate
+ mWindowType = eWindowType_toplevel;
+ mBorderStyle = eBorderStyle_default;
+
+ // Ensure that the toolkit is created.
+ nsToolkit::GetToolkit();
+
+ Inherited::BaseCreate(aParent, aInitData);
+
+ mParent = aParent;
+ mAncestorLink = aParent;
+
+ // Applications that use native popups don't want us to create popup windows.
+ if ((mWindowType == eWindowType_popup) && UseNativePopupWindows())
+ return NS_OK;
+
+ nsresult rv =
+ CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(newBounds),
+ mBorderStyle, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mWindowType == eWindowType_popup) {
+ if (aInitData->mMouseTransparent) {
+ [mWindow setIgnoresMouseEvents:YES];
+ }
+ // 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);
+ }
+
+ mIsAnimationSuppressed = aInitData->mIsAnimationSuppressed;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsCocoaWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ DesktopIntRect desktopRect =
+ RoundedToInt(aRect / GetDesktopToDeviceScale());
+ return Create(aParent, aNativeParent, desktopRect, aInitData);
+}
+
+static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle)
+{
+ bool allOrDefault = (aBorderStyle == eBorderStyle_all ||
+ aBorderStyle == eBorderStyle_default);
+
+ /* Apple's docs on NSWindow styles say that "a window's style mask should
+ * include NSTitledWindowMask if it includes any of the others [besides
+ * NSBorderlessWindowMask]". This implies that a borderless window
+ * shouldn't have any other styles than NSBorderlessWindowMask.
+ */
+ if (!allOrDefault && !(aBorderStyle & eBorderStyle_title))
+ return NSBorderlessWindowMask;
+
+ unsigned int mask = NSTitledWindowMask;
+ if (allOrDefault || aBorderStyle & eBorderStyle_close)
+ mask |= NSClosableWindowMask;
+ if (allOrDefault || aBorderStyle & eBorderStyle_minimize)
+ mask |= NSMiniaturizableWindowMask;
+ if (allOrDefault || aBorderStyle & eBorderStyle_resizeh)
+ mask |= NSResizableWindowMask;
+
+ return mask;
+}
+
+// If aRectIsFrameRect, aRect specifies the frame rect of the new window.
+// Otherwise, aRect.x/y specify the position of the window's frame relative to
+// the bottom of the menubar and aRect.width/height specify the size of the
+// content rect.
+nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect,
+ nsBorderStyle aBorderStyle,
+ bool aRectIsFrameRect)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // We default to NSBorderlessWindowMask, add features if needed.
+ unsigned int features = NSBorderlessWindowMask;
+
+ // Configure the window we will create based on the window type.
+ switch (mWindowType)
+ {
+ case eWindowType_invisible:
+ case eWindowType_child:
+ case eWindowType_plugin:
+ break;
+ case eWindowType_popup:
+ if (aBorderStyle != eBorderStyle_default && mBorderStyle & eBorderStyle_title) {
+ features |= NSTitledWindowMask;
+ if (aBorderStyle & eBorderStyle_close) {
+ features |= NSClosableWindowMask;
+ }
+ }
+ break;
+ case eWindowType_toplevel:
+ case eWindowType_dialog:
+ features = WindowMaskForBorderStyle(aBorderStyle);
+ break;
+ case eWindowType_sheet:
+ if (mParent->WindowType() != eWindowType_invisible &&
+ aBorderStyle & eBorderStyle_resizeh) {
+ features = NSResizableWindowMask;
+ }
+ else {
+ features = NSMiniaturizableWindowMask;
+ }
+ features |= NSTitledWindowMask;
+ break;
+ default:
+ NS_ERROR("Unhandled window type!");
+ return NS_ERROR_FAILURE;
+ }
+
+ NSRect contentRect;
+
+ if (aRectIsFrameRect) {
+ contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
+ } else {
+ /*
+ * We pass a content area rect to initialize the native Cocoa window. The
+ * content rect we give is the same size as the size we're given by gecko.
+ * The origin we're given for non-popup windows is moved down by the height
+ * of the menu bar so that an origin of (0,100) from gecko puts the window
+ * 100 pixels below the top of the available desktop area. We also move the
+ * origin down by the height of a title bar if it exists. This is so the
+ * origin that gecko gives us for the top-left of the window turns out to
+ * be the top-left of the window we create. This is how it was done in
+ * Carbon. If it ought to be different we'll probably need to look at all
+ * the callers.
+ *
+ * Note: This means that if you put a secondary screen on top of your main
+ * screen and open a window in the top screen, it'll be incorrectly shifted
+ * down by the height of the menu bar. Same thing would happen in Carbon.
+ *
+ * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
+ * weird place for some reason. This stops that without breaking popups.
+ */
+ // Compensate for difference between frame and content area height (e.g. title bar).
+ NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect styleMask:features];
+
+ contentRect = aRect;
+ contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
+
+ if (mWindowType != eWindowType_popup)
+ contentRect.origin.y -= [[NSApp mainMenu] menuBarHeight];
+ }
+
+ // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
+ // rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
+
+ Class windowClass = [BaseWindow class];
+ // If we have a titlebar on a top-level window, we want to be able to control the
+ // titlebar color (for unified windows), so use the special ToolbarWindow class.
+ // Note that we need to check the window type because we mark sheets as
+ // having titlebars.
+ if ((mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) &&
+ (features & NSTitledWindowMask))
+ windowClass = [ToolbarWindow class];
+ // If we're a popup window we need to use the PopupWindow class.
+ else if (mWindowType == eWindowType_popup)
+ windowClass = [PopupWindow class];
+ // If we're a non-popup borderless window we need to use the
+ // BorderlessWindow class.
+ else if (features == NSBorderlessWindowMask)
+ windowClass = [BorderlessWindow class];
+
+ // Create the window
+ mWindow = [[windowClass alloc] initWithContentRect:contentRect styleMask:features
+ backing:NSBackingStoreBuffered defer:YES];
+
+ // setup our notification delegate. Note that setDelegate: does NOT retain.
+ mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
+ [mWindow setDelegate:mDelegate];
+
+ // Make sure that the content rect we gave has been honored.
+ NSRect wantedFrame = [mWindow frameRectForContentRect:contentRect];
+ if (!NSEqualRects([mWindow frame], wantedFrame)) {
+ // This can happen when the window is not on the primary screen.
+ [mWindow setFrame:wantedFrame display:NO];
+ }
+ UpdateBounds();
+
+ if (mWindowType == eWindowType_invisible) {
+ [mWindow setLevel:kCGDesktopWindowLevelKey];
+ }
+
+ if (mWindowType == eWindowType_popup) {
+ SetPopupWindowLevel();
+ [mWindow setHasShadow:YES];
+ [mWindow setBackgroundColor:[NSColor clearColor]];
+ [mWindow setOpaque:NO];
+ } else {
+ // Make sure that regular windows are opaque from the start, so that
+ // nsChildView::WidgetTypeSupportsAcceleration returns true for them.
+ [mWindow setOpaque:YES];
+ }
+
+ [mWindow setContentMinSize:NSMakeSize(60, 60)];
+ [mWindow disableCursorRects];
+
+ // Make sure the window starts out not draggable by the background.
+ // We will turn it on as necessary.
+ [mWindow setMovableByWindowBackground:NO];
+
+ [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
+ mWindowMadeHere = true;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsCocoaWindow::CreatePopupContentView(const LayoutDeviceIntRect &aRect)
+{
+ 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,
+ nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ ChildView* newContentView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
+ [mWindow setContentView:newContentView];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsCocoaWindow::Destroy()
+{
+ if (mOnDestroyCalled)
+ return;
+ mOnDestroyCalled = true;
+
+ // SetFakeModal(true) is called for non-modal window opened by modal window.
+ // On Cocoa, it needs corresponding SetFakeModal(false) on destroy to restore
+ // ancestor windows' state.
+ if (mFakeModal) {
+ SetFakeModal(false);
+ }
+
+ // If we don't hide here we run into problems with panels, this is not ideal.
+ // (Bug 891424)
+ Show(false);
+
+ if (mPopupContentView)
+ mPopupContentView->Destroy();
+
+ if (mFullscreenTransitionAnimation) {
+ [mFullscreenTransitionAnimation stopAnimation];
+ ReleaseFullscreenTransitionAnimation();
+ }
+
+ nsBaseWidget::Destroy();
+ // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we
+ // don't implement GetParent(), so we need to do the equivalent here.
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+ nsBaseWidget::OnDestroy();
+
+ if (mInFullScreenMode) {
+ // On Lion we don't have to mess with the OS chrome when in Full Screen
+ // mode. But we do have to destroy the native window here (and not wait
+ // for that to happen in our destructor). We don't switch away from the
+ // native window's space until the window is destroyed, and otherwise this
+ // might not happen for several seconds (because at least one object
+ // holding a reference to ourselves is usually waiting to be garbage-
+ // collected). See bug 757618.
+ if (mInNativeFullScreenMode) {
+ DestroyNativeWindow();
+ } else if (mWindow) {
+ nsCocoaUtils::HideOSChromeOnScreen(false);
+ }
+ }
+}
+
+nsIWidget* nsCocoaWindow::GetSheetWindowParent(void)
+{
+ if (mWindowType != eWindowType_sheet)
+ return nullptr;
+ nsCocoaWindow *parent = static_cast<nsCocoaWindow*>(mParent);
+ while (parent && (parent->mWindowType == eWindowType_sheet))
+ parent = static_cast<nsCocoaWindow*>(parent->mParent);
+ return parent;
+}
+
+void* nsCocoaWindow::GetNativeData(uint32_t aDataType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
+
+ void* retVal = nullptr;
+
+ switch (aDataType) {
+ // to emulate how windows works, we always have to return a NSView
+ // for NS_NATIVE_WIDGET
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_DISPLAY:
+ retVal = [mWindow contentView];
+ break;
+
+ case NS_NATIVE_WINDOW:
+ retVal = mWindow;
+ break;
+
+ case NS_NATIVE_GRAPHIC:
+ // There isn't anything that makes sense to return here,
+ // and it doesn't matter so just return nullptr.
+ NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!");
+ break;
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ retVal = GetPseudoIMEContext();
+ if (retVal) {
+ break;
+ }
+ NSView* view = mWindow ? [mWindow contentView] : nil;
+ if (view) {
+ retVal = [view inputContext];
+ }
+ // If inputContext isn't available on this window, return this window's
+ // pointer instead of nullptr since if this returns nullptr,
+ // IMEStateManager cannot manage composition with TextComposition
+ // instance. Although, this case shouldn't occur.
+ if (NS_WARN_IF(!retVal)) {
+ retVal = this;
+ }
+ break;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+bool nsCocoaWindow::IsVisible() const
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+void
+nsCocoaWindow::SetModal(bool aState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // This is used during startup (outside the event loop) when creating
+ // the add-ons compatibility checking dialog and the profile manager UI;
+ // therefore, it needs to provide an autorelease pool to avoid cocoa
+ // objects leaking.
+ nsAutoreleasePool localPool;
+
+ mModal = aState;
+ nsCocoaWindow *ancestor = static_cast<nsCocoaWindow*>(mAncestorLink);
+ if (aState) {
+ ++gXULModalLevel;
+ // When a non-sheet window gets "set modal", make the window(s) that it
+ // appears over behave as they should. We can't rely on native methods to
+ // do this, for the following reason: The OS runs modal non-sheet windows
+ // in an event loop (using [NSApplication runModalForWindow:] or similar
+ // methods) that's incompatible with the modal event loop in nsXULWindow::
+ // ShowModal() (each of these event loops is "exclusive", and can't run at
+ // the same time as other (similar) event loops).
+ if (mWindowType != eWindowType_sheet) {
+ while (ancestor) {
+ if (ancestor->mNumModalDescendents++ == 0) {
+ NSWindow *aWindow = ancestor->GetCocoaWindow();
+ if (ancestor->mWindowType != eWindowType_invisible) {
+ [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO];
+ [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO];
+ [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO];
+ }
+ }
+ ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
+ }
+ [mWindow setLevel:NSModalPanelWindowLevel];
+ nsCocoaWindowList *windowList = new nsCocoaWindowList;
+ if (windowList) {
+ windowList->window = this; // Don't ADDREF
+ windowList->prev = gGeckoAppModalWindowList;
+ gGeckoAppModalWindowList = windowList;
+ }
+ }
+ }
+ else {
+ --gXULModalLevel;
+ NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!");
+ if (mWindowType != eWindowType_sheet) {
+ while (ancestor) {
+ if (--ancestor->mNumModalDescendents == 0) {
+ NSWindow *aWindow = ancestor->GetCocoaWindow();
+ if (ancestor->mWindowType != eWindowType_invisible) {
+ [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES];
+ [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES];
+ [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES];
+ }
+ }
+ NS_ASSERTION(ancestor->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!");
+ ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
+ }
+ if (gGeckoAppModalWindowList) {
+ NS_ASSERTION(gGeckoAppModalWindowList->window == this, "Widget hierarchy changed while modal!");
+ nsCocoaWindowList *saved = gGeckoAppModalWindowList;
+ gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev;
+ delete saved; // "window" not ADDREFed
+ }
+ if (mWindowType == eWindowType_popup)
+ SetPopupWindowLevel();
+ else
+ [mWindow setLevel:NSNormalWindowLevel];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsCocoaWindow::SetFakeModal(bool aState)
+{
+ mFakeModal = aState;
+ SetModal(aState);
+}
+
+bool
+nsCocoaWindow::IsRunningAppModal()
+{
+ return [NSApp _isRunningAppModal];
+}
+
+// Hide or show this window
+NS_IMETHODIMP nsCocoaWindow::Show(bool bState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow)
+ return NS_OK;
+
+ // We need to re-execute sometimes in order to bring already-visible
+ // windows forward.
+ if (!mSheetNeedsShow && !bState && ![mWindow isVisible])
+ return NS_OK;
+
+ // Protect against re-entering.
+ if (bState && [mWindow isBeingShown])
+ return NS_OK;
+
+ [mWindow setBeingShown:bState];
+
+ nsIWidget* parentWidget = mParent;
+ nsCOMPtr<nsPIWidgetCocoa> piParentWidget(do_QueryInterface(parentWidget));
+ NSWindow* nativeParentWindow = (parentWidget) ?
+ (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW) : nil;
+
+ if (bState && !mBounds.IsEmpty()) {
+ if (mPopupContentView) {
+ // Ensure our content view is visible. We never need to hide it.
+ mPopupContentView->Show(true);
+ }
+
+ if (mWindowType == eWindowType_sheet) {
+ // bail if no parent window (its basically what we do in Carbon)
+ if (!nativeParentWindow || !piParentWidget)
+ return NS_ERROR_FAILURE;
+
+ NSWindow* topNonSheetWindow = nativeParentWindow;
+
+ // If this sheet is the child of another sheet, hide the parent so that
+ // this sheet can be displayed. Leave the parent mSheetNeedsShow alone,
+ // that is only used to handle sibling sheet contention. The parent will
+ // return once there are no more child sheets.
+ bool parentIsSheet = false;
+ if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
+ parentIsSheet) {
+ piParentWidget->GetSheetWindowParent(&topNonSheetWindow);
+ [NSApp endSheet:nativeParentWindow];
+ }
+
+ nsCOMPtr<nsIWidget> sheetShown;
+ if (NS_SUCCEEDED(piParentWidget->GetChildSheet(
+ true, getter_AddRefs(sheetShown))) &&
+ (!sheetShown || sheetShown == this)) {
+ // If this sheet is already the sheet actually being shown, don't
+ // tell it to show again. Otherwise the number of calls to
+ // [NSApp beginSheet...] won't match up with [NSApp endSheet...].
+ if (![mWindow isVisible]) {
+ mSheetNeedsShow = false;
+ mSheetWindowParent = topNonSheetWindow;
+ // Only set contextInfo if our parent isn't a sheet.
+ NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent;
+ [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
+ [NSApp beginSheet:mWindow
+ modalForWindow:mSheetWindowParent
+ modalDelegate:mDelegate
+ didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
+ contextInfo:contextInfo];
+ [TopLevelWindowData activateInWindow:mWindow];
+ SendSetZLevelEvent();
+ }
+ }
+ else {
+ // A sibling of this sheet is active, don't show this sheet yet.
+ // When the active sheet hides, its brothers and sisters that have
+ // mSheetNeedsShow set will have their opportunities to display.
+ mSheetNeedsShow = true;
+ }
+ }
+ else if (mWindowType == eWindowType_popup) {
+ // If a popup window is shown after being hidden, it needs to be "reset"
+ // for it to receive any mouse events aside from mouse-moved events
+ // (because it was removed from the "window cache" when it was hidden
+ // -- see below). Setting the window number to -1 and then back to its
+ // original value seems to accomplish this. The idea was "borrowed"
+ // from the Java Embedding Plugin.
+ NSInteger windowNumber = [mWindow windowNumber];
+ [mWindow _setWindowNumber:-1];
+ [mWindow _setWindowNumber:windowNumber];
+ // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or
+ // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000)
+ // creating CGSWindow", which in turn triggers an internal inconsistency
+ // NSException. These errors shouldn't be fatal. So we need to wrap
+ // calls to ...orderFront: in TRY blocks. See bmo bug 470864.
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ [[mWindow contentView] setNeedsDisplay:YES];
+ [mWindow orderFront:nil];
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+ SendSetZLevelEvent();
+ AdjustWindowShadow();
+ SetWindowBackgroundBlur();
+ // If our popup window is a non-native context menu, tell the OS (and
+ // other programs) that a menu has opened. This is how the OS knows to
+ // close other programs' context menus when ours open.
+ if ([mWindow isKindOfClass:[PopupWindow class]] &&
+ [(PopupWindow*) mWindow isContextMenu]) {
+ [[NSDistributedNotificationCenter defaultCenter]
+ postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
+ object:@"org.mozilla.gecko.PopupWindow"];
+ }
+
+ // If a parent window was supplied and this is a popup at the parent
+ // level, set its child window. This will cause the child window to
+ // appear above the parent and move when the parent does. Setting this
+ // needs to happen after the _setWindowNumber calls above, otherwise the
+ // window doesn't focus properly.
+ if (nativeParentWindow && mPopupLevel == ePopupLevelParent)
+ [nativeParentWindow addChildWindow:mWindow
+ ordered:NSWindowAbove];
+ }
+ else {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ if (mWindowType == eWindowType_toplevel &&
+ [mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
+ NSWindowAnimationBehavior behavior;
+ if (mIsAnimationSuppressed) {
+ behavior = NSWindowAnimationBehaviorNone;
+ } else {
+ switch (mAnimationType) {
+ case nsIWidget::eDocumentWindowAnimation:
+ behavior = NSWindowAnimationBehaviorDocumentWindow;
+ break;
+ default:
+ NS_NOTREACHED("unexpected mAnimationType value");
+ // fall through
+ case nsIWidget::eGenericWindowAnimation:
+ behavior = NSWindowAnimationBehaviorDefault;
+ break;
+ }
+ }
+ [mWindow setAnimationBehavior:behavior];
+ }
+ [mWindow makeKeyAndOrderFront:nil];
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+ SendSetZLevelEvent();
+ }
+ }
+ else {
+ // roll up any popups if a top-level window is going away
+ if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog)
+ RollUpPopups();
+
+ // now get rid of the window/sheet
+ if (mWindowType == eWindowType_sheet) {
+ if (mSheetNeedsShow) {
+ // This is an attempt to hide a sheet that never had a chance to
+ // be shown. There's nothing to do other than make sure that it
+ // won't show.
+ mSheetNeedsShow = false;
+ }
+ else {
+ // get sheet's parent *before* hiding the sheet (which breaks the linkage)
+ NSWindow* sheetParent = mSheetWindowParent;
+
+ // hide the sheet
+ [NSApp endSheet:mWindow];
+
+ [TopLevelWindowData deactivateInWindow:mWindow];
+
+ nsCOMPtr<nsIWidget> siblingSheetToShow;
+ bool parentIsSheet = false;
+
+ if (nativeParentWindow && piParentWidget &&
+ NS_SUCCEEDED(piParentWidget->GetChildSheet(
+ false, getter_AddRefs(siblingSheetToShow))) &&
+ siblingSheetToShow) {
+ // First, give sibling sheets an opportunity to show.
+ siblingSheetToShow->Show(true);
+ }
+ else if (nativeParentWindow && piParentWidget &&
+ NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
+ parentIsSheet) {
+ // Only set contextInfo if the parent of the parent sheet we're about
+ // to restore isn't itself a sheet.
+ NSWindow* contextInfo = sheetParent;
+ nsIWidget* grandparentWidget = nil;
+ if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) && grandparentWidget) {
+ nsCOMPtr<nsPIWidgetCocoa> piGrandparentWidget(do_QueryInterface(grandparentWidget));
+ bool grandparentIsSheet = false;
+ if (piGrandparentWidget && NS_SUCCEEDED(piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) &&
+ grandparentIsSheet) {
+ contextInfo = nil;
+ }
+ }
+ // If there are no sibling sheets, but the parent is a sheet, restore
+ // it. It wasn't sent any deactivate events when it was hidden, so
+ // don't call through Show, just let the OS put it back up.
+ [NSApp beginSheet:nativeParentWindow
+ modalForWindow:sheetParent
+ modalDelegate:[nativeParentWindow delegate]
+ didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
+ contextInfo:contextInfo];
+ }
+ else {
+ // Sheet, that was hard. No more siblings or parents, going back
+ // to a real window.
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ [sheetParent makeKeyAndOrderFront:nil];
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+ }
+ SendSetZLevelEvent();
+ }
+ }
+ else {
+ // If the window is a popup window with a parent window we need to
+ // unhook it here before ordering it out. When you order out the child
+ // of a window it hides the parent window.
+ if (mWindowType == eWindowType_popup && nativeParentWindow)
+ [nativeParentWindow removeChildWindow:mWindow];
+
+ [mWindow orderOut:nil];
+ // Unless it's explicitly removed from NSApp's "window cache", a popup
+ // window will keep receiving mouse-moved events even after it's been
+ // "ordered out" (instead of the browser window that was underneath it,
+ // until you click on that window). This is bmo bug 378645, but it's
+ // surely an Apple bug. The "window cache" is an undocumented subsystem,
+ // all of whose methods are included in the NSWindowCache category of
+ // the NSApplication class (in header files generated using class-dump).
+ // This workaround was "borrowed" from the Java Embedding Plugin (which
+ // uses it for a different purpose).
+ if (mWindowType == eWindowType_popup)
+ [NSApp _removeWindowFromCache:mWindow];
+
+ // If our popup window is a non-native context menu, tell the OS (and
+ // other programs) that a menu has closed.
+ if ([mWindow isKindOfClass:[PopupWindow class]] &&
+ [(PopupWindow*) mWindow isContextMenu]) {
+ [[NSDistributedNotificationCenter defaultCenter]
+ postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification"
+ object:@"org.mozilla.gecko.PopupWindow"];
+ }
+ }
+ }
+
+ [mWindow setBeingShown:NO];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+struct ShadowParams {
+ float standardDeviation;
+ float density;
+ int offsetX;
+ int offsetY;
+ unsigned int flags;
+};
+
+// These numbers have been determined by looking at the results of
+// CGSGetWindowShadowAndRimParameters for native window types.
+static const ShadowParams kWindowShadowParametersPreYosemite[] = {
+ { 0.0f, 0.0f, 0, 0, 0 }, // none
+ { 8.0f, 0.5f, 0, 6, 1 }, // default
+ { 10.0f, 0.44f, 0, 10, 512 }, // menu
+ { 8.0f, 0.5f, 0, 6, 1 }, // tooltip
+ { 4.0f, 0.6f, 0, 4, 512 } // sheet
+};
+
+static const ShadowParams kWindowShadowParametersPostYosemite[] = {
+ { 0.0f, 0.0f, 0, 0, 0 }, // none
+ { 8.0f, 0.5f, 0, 6, 1 }, // default
+ { 9.882353f, 0.3f, 0, 4, 0 }, // menu
+ { 3.294118f, 0.2f, 0, 1, 0 }, // tooltip
+ { 9.882353f, 0.3f, 0, 4, 0 } // sheet
+};
+
+// This method will adjust the window shadow style for popup windows after
+// they have been made visible. Before they're visible, their window number
+// might be -1, which is not useful.
+// We won't attempt to change the shadow for windows that can acquire key state
+// since OS X will reset the shadow whenever that happens.
+void
+nsCocoaWindow::AdjustWindowShadow()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || ![mWindow isVisible] || ![mWindow hasShadow] ||
+ [mWindow canBecomeKeyWindow] || [mWindow windowNumber] == -1)
+ return;
+
+ const ShadowParams& params = nsCocoaFeatures::OnYosemiteOrLater()
+ ? kWindowShadowParametersPostYosemite[mShadowStyle]
+ : kWindowShadowParametersPreYosemite[mShadowStyle];
+ CGSConnection cid = _CGSDefaultConnection();
+ CGSSetWindowShadowAndRimParameters(cid, [mWindow windowNumber],
+ params.standardDeviation, params.density,
+ params.offsetX, params.offsetY,
+ params.flags);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const NSUInteger kWindowBackgroundBlurRadius = 4;
+
+void
+nsCocoaWindow::SetWindowBackgroundBlur()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || ![mWindow isVisible] || [mWindow windowNumber] == -1)
+ return;
+
+ // Only blur the background of menus and fake sheets.
+ if (mShadowStyle != NS_STYLE_WINDOW_SHADOW_MENU &&
+ mShadowStyle != NS_STYLE_WINDOW_SHADOW_SHEET)
+ return;
+
+ CGSConnection cid = _CGSDefaultConnection();
+ CGSSetWindowBackgroundBlurRadius(cid, [mWindow windowNumber], kWindowBackgroundBlurRadius);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult
+nsCocoaWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
+{
+ if (mPopupContentView) {
+ mPopupContentView->ConfigureChildren(aConfigurations);
+ }
+ return NS_OK;
+}
+
+LayerManager*
+nsCocoaWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
+ LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence)
+{
+ if (mPopupContentView) {
+ return mPopupContentView->GetLayerManager(aShadowManager,
+ aBackendHint,
+ aPersistence);
+ }
+ return nullptr;
+}
+
+nsTransparencyMode nsCocoaWindow::GetTransparencyMode()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return (!mWindow || [mWindow isOpaque]) ? eTransparencyOpaque : eTransparencyTransparent;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque);
+}
+
+// This is called from nsMenuPopupFrame when making a popup transparent, or
+// from nsChildView::SetTransparencyMode for other window types.
+void nsCocoaWindow::SetTransparencyMode(nsTransparencyMode aMode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // Transparent windows are only supported on popups.
+ BOOL isTransparent = aMode == eTransparencyTransparent &&
+ mWindowType == eWindowType_popup;
+ BOOL currentTransparency = ![mWindow isOpaque];
+ if (isTransparent != currentTransparency) {
+ [mWindow setOpaque:!isTransparent];
+ [mWindow setBackgroundColor:(isTransparent ? [NSColor clearColor] : [NSColor whiteColor])];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::Enable(bool aState)
+{
+ return NS_OK;
+}
+
+bool nsCocoaWindow::IsEnabled() const
+{
+ return true;
+}
+
+#define kWindowPositionSlop 20
+
+void
+nsCocoaWindow::ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || ![mWindow screen]) {
+ return;
+ }
+
+ nsIntRect screenBounds;
+
+ int32_t width, height;
+
+ NSRect frame = [mWindow frame];
+
+ // zero size rects confuse the screen manager
+ width = std::max<int32_t>(frame.size.width, 1);
+ height = std::max<int32_t>(frame.size.height, 1);
+
+ nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (screenMgr) {
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->ScreenForRect(*aX, *aY, width, height, getter_AddRefs(screen));
+
+ if (screen) {
+ screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y),
+ &(screenBounds.width), &(screenBounds.height));
+ }
+ }
+
+ if (aAllowSlop) {
+ if (*aX < screenBounds.x - width + kWindowPositionSlop) {
+ *aX = screenBounds.x - width + kWindowPositionSlop;
+ } else if (*aX >= screenBounds.x + screenBounds.width - kWindowPositionSlop) {
+ *aX = screenBounds.x + screenBounds.width - kWindowPositionSlop;
+ }
+
+ if (*aY < screenBounds.y - height + kWindowPositionSlop) {
+ *aY = screenBounds.y - height + kWindowPositionSlop;
+ } else if (*aY >= screenBounds.y + screenBounds.height - kWindowPositionSlop) {
+ *aY = screenBounds.y + screenBounds.height - kWindowPositionSlop;
+ }
+ } else {
+ if (*aX < screenBounds.x) {
+ *aX = screenBounds.x;
+ } else if (*aX >= screenBounds.x + screenBounds.width - width) {
+ *aX = screenBounds.x + screenBounds.width - width;
+ }
+
+ if (*aY < screenBounds.y) {
+ *aY = screenBounds.y;
+ } else if (*aY >= screenBounds.y + screenBounds.height - height) {
+ *aY = screenBounds.y + screenBounds.height - height;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Popups can be smaller than (60, 60)
+ NSRect rect =
+ (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 60, 60);
+ rect = [mWindow frameRectForContentRect:rect];
+
+ CGFloat scaleFactor = BackingScaleFactor();
+
+ SizeConstraints c = aConstraints;
+ c.mMinSize.width =
+ std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, scaleFactor),
+ c.mMinSize.width);
+ c.mMinSize.height =
+ std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, scaleFactor),
+ c.mMinSize.height);
+
+ NSSize minSize = {
+ nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, scaleFactor),
+ nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, scaleFactor)
+ };
+ [mWindow setMinSize:minSize];
+
+ NSSize maxSize = {
+ c.mMaxSize.width == NS_MAXSIZE ?
+ FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.width, scaleFactor),
+ c.mMaxSize.height == NS_MAXSIZE ?
+ FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.height, scaleFactor)
+ };
+ [mWindow setMaxSize:maxSize];
+
+ nsBaseWidget::SetSizeConstraints(c);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Coordinates are desktop pixels
+NS_IMETHODIMP nsCocoaWindow::Move(double aX, double aY)
+{
+ if (!mWindow) {
+ return NS_OK;
+ }
+
+ // The point we have is in Gecko coordinates (origin top-left). Convert
+ // it to Cocoa ones (origin bottom-left).
+ NSPoint coord = {
+ static_cast<float>(aX),
+ static_cast<float>(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY)))
+ };
+
+ NSRect frame = [mWindow frame];
+ if (frame.origin.x != coord.x ||
+ frame.origin.y + frame.size.height != coord.y) {
+ [mWindow setFrameTopLeftPoint:coord];
+ }
+
+ return NS_OK;
+}
+
+void
+nsCocoaWindow::SetSizeMode(nsSizeMode aMode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // mSizeMode will be updated in DispatchSizeModeEvent, which will be called
+ // from a delegate method that handles the state change during one of the
+ // calls below.
+ nsSizeMode previousMode = mSizeMode;
+
+ if (aMode == nsSizeMode_Normal) {
+ if ([mWindow isMiniaturized])
+ [mWindow deminiaturize:nil];
+ else if (previousMode == nsSizeMode_Maximized && [mWindow isZoomed])
+ [mWindow zoom:nil];
+ }
+ else if (aMode == nsSizeMode_Minimized) {
+ if (![mWindow isMiniaturized])
+ [mWindow miniaturize:nil];
+ }
+ else if (aMode == nsSizeMode_Maximized) {
+ if ([mWindow isMiniaturized])
+ [mWindow deminiaturize:nil];
+ if (![mWindow isZoomed])
+ [mWindow zoom:nil];
+ }
+ else if (aMode == nsSizeMode_Fullscreen) {
+ if (!mInFullScreenMode)
+ MakeFullScreen(true);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// This has to preserve the window's frame bounds.
+// This method requires (as does the Windows impl.) that you call Resize shortly
+// after calling HideWindowChrome. See bug 498835 for fixing this.
+NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow || !mWindowMadeHere ||
+ (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog))
+ return NS_ERROR_FAILURE;
+
+ BOOL isVisible = [mWindow isVisible];
+
+ // Remove child windows.
+ NSArray* childWindows = [mWindow childWindows];
+ NSEnumerator* enumerator = [childWindows objectEnumerator];
+ NSWindow* child = nil;
+ while ((child = [enumerator nextObject])) {
+ [mWindow removeChildWindow:child];
+ }
+
+ // Remove the content view.
+ NSView* contentView = [mWindow contentView];
+ [contentView retain];
+ [contentView removeFromSuperviewWithoutNeedingDisplay];
+
+ // Save state (like window title).
+ NSMutableDictionary* state = [mWindow exportState];
+
+ // Recreate the window with the right border style.
+ NSRect frameRect = [mWindow frame];
+ DestroyNativeWindow();
+ nsresult rv = CreateNativeWindow(frameRect, aShouldHide ? eBorderStyle_none : mBorderStyle, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Re-import state.
+ [mWindow importState:state];
+
+ // Reparent the content view.
+ [mWindow setContentView:contentView];
+ [contentView release];
+
+ // Reparent child windows.
+ enumerator = [childWindows objectEnumerator];
+ while ((child = [enumerator nextObject])) {
+ [mWindow addChildWindow:child ordered:NSWindowAbove];
+ }
+
+ // Show the new window.
+ if (isVisible) {
+ bool wasAnimationSuppressed = mIsAnimationSuppressed;
+ mIsAnimationSuppressed = true;
+ rv = Show(true);
+ mIsAnimationSuppressed = wasAnimationSuppressed;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+class FullscreenTransitionData : public nsISupports
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit FullscreenTransitionData(NSWindow* aWindow)
+ : mTransitionWindow(aWindow) { }
+
+ NSWindow* mTransitionWindow;
+
+private:
+ virtual ~FullscreenTransitionData()
+ {
+ [mTransitionWindow close];
+ }
+};
+
+NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
+
+@interface FullscreenTransitionDelegate : NSObject <NSAnimationDelegate>
+{
+@public
+ nsCocoaWindow* mWindow;
+ nsIRunnable* mCallback;
+}
+@end
+
+@implementation FullscreenTransitionDelegate
+- (void)cleanupAndDispatch:(NSAnimation* )animation
+{
+ [animation setDelegate:nil];
+ [self autorelease];
+ // The caller should have added ref for us.
+ NS_DispatchToMainThread(already_AddRefed<nsIRunnable>(mCallback));
+}
+
+- (void)animationDidEnd:(NSAnimation *)animation
+{
+ MOZ_ASSERT(animation == mWindow->FullscreenTransitionAnimation(),
+ "Should be handling the only animation on the window");
+ mWindow->ReleaseFullscreenTransitionAnimation();
+ [self cleanupAndDispatch:animation];
+}
+
+- (void)animationDidStop:(NSAnimation *)animation
+{
+ [self cleanupAndDispatch:animation];
+}
+@end
+
+/* virtual */ bool
+nsCocoaWindow::PrepareForFullscreenTransition(nsISupports** aData)
+{
+ nsCOMPtr<nsIScreen> widgetScreen = GetWidgetScreen();
+ nsScreenCocoa* screen = static_cast<nsScreenCocoa*>(widgetScreen.get());
+ NSScreen* cocoaScreen = screen->CocoaScreen();
+
+ NSWindow* win =
+ [[NSWindow alloc] initWithContentRect:[cocoaScreen frame]
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:YES];
+ [win setBackgroundColor:[NSColor blackColor]];
+ [win setAlphaValue:0];
+ [win setIgnoresMouseEvents:YES];
+ [win setLevel:NSScreenSaverWindowLevel];
+ [win makeKeyAndOrderFront:nil];
+
+ auto data = new FullscreenTransitionData(win);
+ *aData = data;
+ NS_ADDREF(data);
+ return true;
+}
+
+/* virtual */ void
+nsCocoaWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback)
+{
+ auto data = static_cast<FullscreenTransitionData*>(aData);
+ FullscreenTransitionDelegate* delegate =
+ [[FullscreenTransitionDelegate alloc] init];
+ delegate->mWindow = this;
+ // Storing already_AddRefed directly could cause static checking fail.
+ delegate->mCallback = nsCOMPtr<nsIRunnable>(aCallback).forget().take();
+
+ if (mFullscreenTransitionAnimation) {
+ [mFullscreenTransitionAnimation stopAnimation];
+ ReleaseFullscreenTransitionAnimation();
+ }
+
+ NSDictionary* dict = @{
+ NSViewAnimationTargetKey: data->mTransitionWindow,
+ NSViewAnimationEffectKey: aStage == eBeforeFullscreenToggle ?
+ NSViewAnimationFadeInEffect : NSViewAnimationFadeOutEffect
+ };
+ mFullscreenTransitionAnimation =
+ [[NSViewAnimation alloc] initWithViewAnimations:@[dict]];
+ [mFullscreenTransitionAnimation setDelegate:delegate];
+ [mFullscreenTransitionAnimation setDuration:aDuration / 1000.0];
+ [mFullscreenTransitionAnimation startAnimation];
+}
+
+void nsCocoaWindow::EnteredFullScreen(bool aFullScreen, bool aNativeMode)
+{
+ mInFullScreenTransition = false;
+ bool wasInFullscreen = mInFullScreenMode;
+ mInFullScreenMode = aFullScreen;
+ if (aNativeMode || mInNativeFullScreenMode) {
+ mInNativeFullScreenMode = aFullScreen;
+ }
+ DispatchSizeModeEvent();
+ if (mWidgetListener && wasInFullscreen != aFullScreen) {
+ mWidgetListener->FullscreenChanged(aFullScreen);
+ }
+}
+
+inline bool
+nsCocoaWindow::ShouldToggleNativeFullscreen(bool aFullScreen,
+ bool aUseSystemTransition)
+{
+ if (!mSupportsNativeFullScreen) {
+ // If we cannot use native fullscreen, don't touch it.
+ return false;
+ }
+ if (mInNativeFullScreenMode) {
+ // If we are using native fullscreen, go ahead to exit it.
+ return true;
+ }
+ if (!aUseSystemTransition) {
+ // If we do not want the system fullscreen transition,
+ // don't use the native fullscreen.
+ return false;
+ }
+ // If we are using native fullscreen, we should have returned earlier.
+ return aFullScreen;
+}
+
+nsresult
+nsCocoaWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen)
+{
+ return DoMakeFullScreen(aFullScreen, false);
+}
+
+NS_IMETHODIMP
+nsCocoaWindow::MakeFullScreenWithNativeTransition(bool aFullScreen,
+ nsIScreen* aTargetScreen)
+{
+ return DoMakeFullScreen(aFullScreen, true);
+}
+
+nsresult
+nsCocoaWindow::DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow) {
+ return NS_OK;
+ }
+
+ // We will call into MakeFullScreen redundantly when entering/exiting
+ // fullscreen mode via OS X controls. When that happens we should just handle
+ // it gracefully - no need to ASSERT.
+ if (mInFullScreenMode == aFullScreen) {
+ return NS_OK;
+ }
+
+ mInFullScreenTransition = true;
+
+ if (ShouldToggleNativeFullscreen(aFullScreen, aUseSystemTransition)) {
+ // If we're using native fullscreen mode and our native window is invisible,
+ // our attempt to go into fullscreen mode will fail with an assertion in
+ // system code, without [WindowDelegate windowDidFailToEnterFullScreen:]
+ // ever getting called. To pre-empt this we bail here. See bug 752294.
+ if (aFullScreen && ![mWindow isVisible]) {
+ EnteredFullScreen(false);
+ return NS_OK;
+ }
+ MOZ_ASSERT(mInNativeFullScreenMode != aFullScreen,
+ "We shouldn't have been in native fullscreen.");
+ // Calling toggleFullScreen will result in windowDid(FailTo)?(Enter|Exit)FullScreen
+ // to be called from the OS. We will call EnteredFullScreen from those methods,
+ // where mInFullScreenMode will be set and a sizemode event will be dispatched.
+ [mWindow toggleFullScreen:nil];
+ } else {
+ NSDisableScreenUpdates();
+ // The order here matters. When we exit full screen mode, we need to show the
+ // Dock first, otherwise the newly-created window won't have its minimize
+ // button enabled. See bug 526282.
+ nsCocoaUtils::HideOSChromeOnScreen(aFullScreen);
+ nsBaseWidget::InfallibleMakeFullScreen(aFullScreen);
+ NSEnableScreenUpdates();
+ EnteredFullScreen(aFullScreen, /* aNativeMode */ false);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Coordinates are desktop pixels
+nsresult nsCocoaWindow::DoResize(double aX, double aY,
+ double aWidth, double aHeight,
+ bool aRepaint,
+ bool aConstrainToCurrentScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow || mInResize) {
+ return NS_OK;
+ }
+
+ AutoRestore<bool> reentrantResizeGuard(mInResize);
+ mInResize = true;
+
+ // ConstrainSize operates in device pixels, so we need to convert using
+ // the backing scale factor here
+ CGFloat scale = BackingScaleFactor();
+ int32_t width = NSToIntRound(aWidth * scale);
+ int32_t height = NSToIntRound(aHeight * scale);
+ ConstrainSize(&width, &height);
+
+ DesktopIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY),
+ NSToIntRound(width / scale),
+ NSToIntRound(height / scale));
+
+ // constrain to the screen that contains the largest area of the new rect
+ FitRectToVisibleAreaForScreen(newBounds, aConstrainToCurrentScreen ?
+ [mWindow screen] : nullptr);
+
+ // convert requested bounds into Cocoa coordinate system
+ NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
+
+ NSRect frame = [mWindow frame];
+ BOOL isMoving = newFrame.origin.x != frame.origin.x ||
+ newFrame.origin.y != frame.origin.y;
+ BOOL isResizing = newFrame.size.width != frame.size.width ||
+ newFrame.size.height != frame.size.height;
+
+ if (!isMoving && !isResizing) {
+ return NS_OK;
+ }
+
+ // We ignore aRepaint -- we have to call display:YES, otherwise the
+ // title bar doesn't immediately get repainted and is displayed in
+ // the wrong place, leading to a visual jump.
+ [mWindow setFrame:newFrame display:YES];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Coordinates are desktop pixels
+NS_IMETHODIMP nsCocoaWindow::Resize(double aX, double aY,
+ double aWidth, double aHeight,
+ bool aRepaint)
+{
+ return DoResize(aX, aY, aWidth, aHeight, aRepaint, false);
+}
+
+// Coordinates are desktop pixels
+NS_IMETHODIMP nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint)
+{
+ double invScale = 1.0 / BackingScaleFactor();
+ return DoResize(mBounds.x * invScale, mBounds.y * invScale,
+ aWidth, aHeight, aRepaint, true);
+}
+
+LayoutDeviceIntRect
+nsCocoaWindow::GetClientBounds()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ CGFloat scaleFactor = BackingScaleFactor();
+ if (!mWindow) {
+ return nsCocoaUtils::CocoaRectToGeckoRectDevPix(NSZeroRect, scaleFactor);
+ }
+
+ NSRect r;
+ if ([mWindow isKindOfClass:[ToolbarWindow class]] &&
+ [(ToolbarWindow*)mWindow drawsContentsIntoWindowFrame]) {
+ r = [mWindow frame];
+ } else {
+ r = [mWindow contentRectForFrameRect:[mWindow frame]];
+ }
+
+ return nsCocoaUtils::CocoaRectToGeckoRectDevPix(r, scaleFactor);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
+}
+
+void
+nsCocoaWindow::UpdateBounds()
+{
+ NSRect frame = NSZeroRect;
+ if (mWindow) {
+ frame = [mWindow frame];
+ }
+ mBounds =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
+}
+
+LayoutDeviceIntRect
+nsCocoaWindow::GetScreenBounds()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+#ifdef DEBUG
+ LayoutDeviceIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix([mWindow frame], BackingScaleFactor());
+ NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
+#endif
+
+ return mBounds;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
+}
+
+double
+nsCocoaWindow::GetDefaultScaleInternal()
+{
+ return BackingScaleFactor();
+}
+
+static CGFloat
+GetBackingScaleFactor(NSWindow* aWindow)
+{
+ NSRect frame = [aWindow frame];
+ if (frame.size.width > 0 && frame.size.height > 0) {
+ return nsCocoaUtils::GetBackingScaleFactor(aWindow);
+ }
+
+ // For windows with zero width or height, the backingScaleFactor method
+ // is broken - it will always return 2 on a retina macbook, even when
+ // the window position implies it's on a non-hidpi external display
+ // (to the extent that a zero-area window can be said to be "on" a
+ // display at all!)
+ // And to make matters worse, Cocoa even fires a
+ // windowDidChangeBackingProperties notification with the
+ // NSBackingPropertyOldScaleFactorKey key when a window on an
+ // external display is resized to/from zero height, even though it hasn't
+ // really changed screens.
+
+ // This causes us to handle popup window sizing incorrectly when the
+ // popup is resized to zero height (bug 820327) - nsXULPopupManager
+ // becomes (incorrectly) convinced the popup has been explicitly forced
+ // to a non-default size and needs to have size attributes attached.
+
+ // Workaround: instead of asking the window, we'll find the screen it is on
+ // and ask that for *its* backing scale factor.
+
+ // (See bug 853252 and additional comments in windowDidChangeScreen: below
+ // for further complications this causes.)
+
+ // First, expand the rect so that it actually has a measurable area,
+ // for FindTargetScreenForRect to use.
+ if (frame.size.width == 0) {
+ frame.size.width = 1;
+ }
+ if (frame.size.height == 0) {
+ frame.size.height = 1;
+ }
+
+ // Then identify the screen it belongs to, and return its scale factor.
+ NSScreen *screen =
+ FindTargetScreenForRect(nsCocoaUtils::CocoaRectToGeckoRect(frame));
+ return nsCocoaUtils::GetBackingScaleFactor(screen);
+}
+
+CGFloat
+nsCocoaWindow::BackingScaleFactor()
+{
+ if (mBackingScaleFactor > 0.0) {
+ return mBackingScaleFactor;
+ }
+ if (!mWindow) {
+ return 1.0;
+ }
+ mBackingScaleFactor = GetBackingScaleFactor(mWindow);
+ return mBackingScaleFactor;
+}
+
+void
+nsCocoaWindow::BackingScaleFactorChanged()
+{
+ CGFloat newScale = GetBackingScaleFactor(mWindow);
+
+ // ignore notification if it hasn't really changed (or maybe we have
+ // disabled HiDPI mode via prefs)
+ if (mBackingScaleFactor == newScale) {
+ return;
+ }
+
+ if (mBackingScaleFactor > 0.0) {
+ // convert size constraints to the new device pixel coordinate space
+ double scaleFactor = newScale / mBackingScaleFactor;
+ mSizeConstraints.mMinSize.width =
+ NSToIntRound(mSizeConstraints.mMinSize.width * scaleFactor);
+ mSizeConstraints.mMinSize.height =
+ NSToIntRound(mSizeConstraints.mMinSize.height * scaleFactor);
+ if (mSizeConstraints.mMaxSize.width < NS_MAXSIZE) {
+ mSizeConstraints.mMaxSize.width =
+ std::min(NS_MAXSIZE,
+ NSToIntRound(mSizeConstraints.mMaxSize.width * scaleFactor));
+ }
+ if (mSizeConstraints.mMaxSize.height < NS_MAXSIZE) {
+ mSizeConstraints.mMaxSize.height =
+ std::min(NS_MAXSIZE,
+ NSToIntRound(mSizeConstraints.mMaxSize.height * scaleFactor));
+ }
+ }
+
+ mBackingScaleFactor = newScale;
+
+ if (!mWidgetListener || mWidgetListener->GetXULWindow()) {
+ return;
+ }
+
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->BackingScaleFactorChanged();
+ }
+}
+
+int32_t
+nsCocoaWindow::RoundsWidgetCoordinatesTo()
+{
+ if (BackingScaleFactor() == 2.0) {
+ return 2;
+ }
+ return 1;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetCursor(nsCursor aCursor)
+{
+ if (mPopupContentView)
+ return mPopupContentView->SetCursor(aCursor);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY)
+{
+ if (mPopupContentView)
+ return mPopupContentView->SetCursor(aCursor, aHotspotX, aHotspotY);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetTitle(const nsAString& aTitle)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow)
+ return NS_OK;
+
+ const nsString& strTitle = PromiseFlatString(aTitle);
+ NSString* title = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(strTitle.get())
+ length:strTitle.Length()];
+
+ if ([mWindow drawsContentsIntoWindowFrame] && ![mWindow wantsTitleDrawn]) {
+ // Don't cause invalidations.
+ [mWindow disableSetNeedsDisplay];
+ [mWindow setTitle:title];
+ [mWindow enableSetNeedsDisplay];
+ } else {
+ [mWindow setTitle:title];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsCocoaWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ if (mPopupContentView) {
+ return mPopupContentView->Invalidate(aRect);
+ }
+
+ return NS_OK;
+}
+
+// Pass notification of some drag event to Gecko
+//
+// The drag manager has let us know that something related to a drag has
+// occurred in this window. It could be any number of things, ranging from
+// a drop, to a drag enter/leave, or a drag over event. The actual event
+// is passed in |aMessage| and is passed along to our event hanlder so Gecko
+// knows about it.
+bool nsCocoaWindow::DragEvent(unsigned int aMessage, mozilla::gfx::Point aMouseGlobal, UInt16 aKeyModifiers)
+{
+ return false;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent()
+{
+ nsWindowZ placement = nsWindowZTop;
+ nsCOMPtr<nsIWidget> actualBelow;
+ if (mWidgetListener)
+ mWidgetListener->ZLevelChanged(true, &placement, nullptr, getter_AddRefs(actualBelow));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsIWidget** _retval)
+{
+ nsIWidget* child = GetFirstChild();
+
+ while (child) {
+ if (child->WindowType() == eWindowType_sheet) {
+ // if it's a sheet, it must be an nsCocoaWindow
+ nsCocoaWindow* cocoaWindow = static_cast<nsCocoaWindow*>(child);
+ if (cocoaWindow->mWindow &&
+ ((aShown && [cocoaWindow->mWindow isVisible]) ||
+ (!aShown && cocoaWindow->mSheetNeedsShow))) {
+ nsCOMPtr<nsIWidget> widget = cocoaWindow;
+ widget.forget(_retval);
+ return NS_OK;
+ }
+ }
+ child = child->GetNextSibling();
+ }
+
+ *_retval = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent)
+{
+ *parent = mParent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet)
+{
+ mWindowType == eWindowType_sheet ? *isSheet = true : *isSheet = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(NSWindow** sheetWindowParent)
+{
+ *sheetWindowParent = mSheetWindowParent;
+ return NS_OK;
+}
+
+// Invokes callback and ProcessEvent methods on Event Listener object
+NS_IMETHODIMP
+nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus)
+{
+ aStatus = nsEventStatus_eIgnore;
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(event->mWidget);
+ mozilla::Unused << kungFuDeathGrip; // Not used within this function
+
+ if (mWidgetListener)
+ aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
+
+ return NS_OK;
+}
+
+// aFullScreen should be the window's mInFullScreenMode. We don't have access to that
+// from here, so we need to pass it in. mInFullScreenMode should be the canonical
+// indicator that a window is currently full screen and it makes sense to keep
+// all sizemode logic here.
+static nsSizeMode
+GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) {
+ if (aFullScreen)
+ return nsSizeMode_Fullscreen;
+ if ([aWindow isMiniaturized])
+ return nsSizeMode_Minimized;
+ if (([aWindow styleMask] & NSResizableWindowMask) && [aWindow isZoomed])
+ return nsSizeMode_Maximized;
+ return nsSizeMode_Normal;
+}
+
+void
+nsCocoaWindow::ReportMoveEvent()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Prevent recursion, which can become infinite (see bug 708278). This
+ // can happen when the call to [NSWindow setFrameTopLeftPoint:] in
+ // nsCocoaWindow::Move() triggers an immediate NSWindowDidMove notification
+ // (and a call to [WindowDelegate windowDidMove:]).
+ if (mInReportMoveEvent) {
+ return;
+ }
+ mInReportMoveEvent = true;
+
+ UpdateBounds();
+
+ // Dispatch the move event to Gecko
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+
+ mInReportMoveEvent = false;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsCocoaWindow::DispatchSizeModeEvent()
+{
+ if (!mWindow) {
+ return;
+ }
+
+ nsSizeMode newMode = GetWindowSizeMode(mWindow, mInFullScreenMode);
+
+ // Don't dispatch a sizemode event if:
+ // 1. the window is transitioning to fullscreen
+ // 2. the new sizemode is the same as the current sizemode
+ if (mInFullScreenTransition || mSizeMode == newMode) {
+ return;
+ }
+
+ mSizeMode = newMode;
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(newMode);
+ }
+}
+
+void
+nsCocoaWindow::ReportSizeEvent()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ UpdateBounds();
+
+ if (mWidgetListener) {
+ LayoutDeviceIntRect innerBounds = GetClientBounds();
+ mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetMenuBar(nsMenuBarX *aMenuBar)
+{
+ if (mMenuBar)
+ mMenuBar->SetParent(nullptr);
+ if (!mWindow) {
+ mMenuBar = nullptr;
+ return;
+ }
+ mMenuBar = aMenuBar;
+
+ // Only paint for active windows, or paint the hidden window menu bar if no
+ // other menu bar has been painted yet so that some reasonable menu bar is
+ // displayed when the app starts up.
+ id windowDelegate = [mWindow delegate];
+ if (mMenuBar &&
+ ((!gSomeMenuBarPainted && nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) ||
+ (windowDelegate && [windowDelegate toplevelActiveState])))
+ mMenuBar->Paint();
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetFocus(bool aState)
+{
+ if (!mWindow)
+ return NS_OK;
+
+ if (mPopupContentView) {
+ mPopupContentView->SetFocus(aState);
+ }
+ else if (aState && ([mWindow isVisible] || [mWindow isMiniaturized])) {
+ if ([mWindow isMiniaturized]) {
+ [mWindow deminiaturize:nil];
+ }
+
+ [mWindow makeKeyAndOrderFront:nil];
+ SendSetZLevelEvent();
+ }
+
+ return NS_OK;
+}
+
+LayoutDeviceIntPoint nsCocoaWindow::WidgetToScreenOffset()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSRect rect = NSZeroRect;
+ LayoutDeviceIntRect r;
+ if (mWindow) {
+ rect = [mWindow contentRectForFrameRect:[mWindow frame]];
+ }
+ r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(rect, BackingScaleFactor());
+
+ return r.TopLeft();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0,0));
+}
+
+LayoutDeviceIntPoint nsCocoaWindow::GetClientOffset()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ LayoutDeviceIntRect clientRect = GetClientBounds();
+
+ return clientRect.TopLeft() - mBounds.TopLeft();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
+}
+
+LayoutDeviceIntSize
+nsCocoaWindow::ClientToWindowSize(const LayoutDeviceIntSize& aClientSize)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!mWindow)
+ return LayoutDeviceIntSize(0, 0);
+
+ CGFloat backingScale = BackingScaleFactor();
+ LayoutDeviceIntRect r(0, 0, aClientSize.width, aClientSize.height);
+ NSRect rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, backingScale);
+
+ NSRect inflatedRect = [mWindow frameRectForContentRect:rect];
+ r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(inflatedRect, backingScale);
+ return r.Size();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntSize(0,0));
+}
+
+nsMenuBarX* nsCocoaWindow::GetMenuBar()
+{
+ return mMenuBar;
+}
+
+void
+nsCocoaWindow::CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ gRollupListener = nullptr;
+
+ if (aDoCapture) {
+ if (![NSApp isActive]) {
+ // We need to capture mouse event if we aren't
+ // the active application. We only set this up when needed
+ // because they cause spurious mouse event after crash
+ // and gdb sessions. See bug 699538.
+ nsToolkit::GetToolkit()->RegisterForAllProcessMouseEvents();
+ }
+ gRollupListener = aListener;
+
+ // Sometimes more than one popup window can be visible at the same time
+ // (e.g. nested non-native context menus, or the test case (attachment
+ // 276885) for bmo bug 392389, which displays a non-native combo-box in a
+ // non-native popup window). In these cases the "active" popup window should
+ // be the topmost -- the (nested) context menu the mouse is currently over,
+ // or the combo-box's drop-down list (when it's displayed). But (among
+ // windows that have the same "level") OS X makes topmost the window that
+ // last received a mouse-down event, which may be incorrect (in the combo-
+ // box case, it makes topmost the window containing the combo-box). So
+ // here we fiddle with a non-native popup window's level to make sure the
+ // "active" one is always above any other non-native popup windows that
+ // may be visible.
+ if (mWindow && (mWindowType == eWindowType_popup))
+ SetPopupWindowLevel();
+ } else {
+ nsToolkit::GetToolkit()->UnregisterAllProcessMouseEventHandlers();
+
+ // XXXndeakin this doesn't make sense.
+ // Why is the new window assumed to be a modal panel?
+ if (mWindow && (mWindowType == eWindowType_popup))
+ [mWindow setLevel:NSModalPanelWindowLevel];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetAttention(int32_t aCycleCount)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [NSApp requestUserAttention:NSInformationalRequest];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+nsCocoaWindow::HasPendingInputEvent()
+{
+ return nsChildView::DoHasPendingInputEvent();
+}
+
+void
+nsCocoaWindow::SetWindowShadowStyle(int32_t aStyle)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ mShadowStyle = aStyle;
+
+ // Shadowless windows are only supported on popups.
+ if (mWindowType == eWindowType_popup)
+ [mWindow setHasShadow:(aStyle != NS_STYLE_WINDOW_SHADOW_NONE)];
+
+ [mWindow setUseMenuStyle:(aStyle == NS_STYLE_WINDOW_SHADOW_MENU)];
+ AdjustWindowShadow();
+ SetWindowBackgroundBlur();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetShowsToolbarButton(bool aShow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mWindow)
+ [mWindow setShowsToolbarButton:aShow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetShowsFullScreenButton(bool aShow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || ![mWindow respondsToSelector:@selector(toggleFullScreen:)] ||
+ mSupportsNativeFullScreen == aShow) {
+ return;
+ }
+
+ // If the window is currently in fullscreen mode, then we're going to
+ // transition out first, then set the collection behavior & toggle
+ // mSupportsNativeFullScreen, then transtion back into fullscreen mode. This
+ // prevents us from getting into a conflicting state with MakeFullScreen
+ // where mSupportsNativeFullScreen would lead us down the wrong path.
+ bool wasFullScreen = mInFullScreenMode;
+
+ if (wasFullScreen) {
+ MakeFullScreen(false);
+ }
+
+ NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
+ if (aShow) {
+ newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
+ } else {
+ newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
+ }
+ [mWindow setCollectionBehavior:newBehavior];
+ mSupportsNativeFullScreen = aShow;
+
+ if (wasFullScreen) {
+ MakeFullScreen(true);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetWindowAnimationType(nsIWidget::WindowAnimationType aType)
+{
+ mAnimationType = aType;
+}
+
+void
+nsCocoaWindow::SetDrawsTitle(bool aDrawTitle)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindow setWantsTitleDrawn:aDrawTitle];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsCocoaWindow::SetUseBrightTitlebarForeground(bool aBrightForeground)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindow setUseBrightTitlebarForeground:aBrightForeground];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetNonClientMargins(LayoutDeviceIntMargin &margins)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ SetDrawsInTitlebar(margins.top == 0);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsCocoaWindow::SetWindowTitlebarColor(nscolor aColor, bool aActive)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // If they pass a color with a complete transparent alpha component, use the
+ // native titlebar appearance.
+ if (NS_GET_A(aColor) == 0) {
+ [mWindow setTitlebarColor:nil forActiveWindow:(BOOL)aActive];
+ } else {
+ // Transform from sRGBA to monitor RGBA. This seems like it would make trying
+ // to match the system appearance lame, so probably we just shouldn't color
+ // correct chrome.
+ if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
+ qcms_transform *transform = gfxPlatform::GetCMSRGBATransform();
+ if (transform) {
+ uint8_t color[3];
+ color[0] = NS_GET_R(aColor);
+ color[1] = NS_GET_G(aColor);
+ color[2] = NS_GET_B(aColor);
+ qcms_transform_data(transform, color, color, 1);
+ aColor = NS_RGB(color[0], color[1], color[2]);
+ }
+ }
+
+ [mWindow setTitlebarColor:[NSColor colorWithDeviceRed:NS_GET_R(aColor)/255.0
+ green:NS_GET_G(aColor)/255.0
+ blue:NS_GET_B(aColor)/255.0
+ alpha:NS_GET_A(aColor)/255.0]
+ forActiveWindow:(BOOL)aActive];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetDrawsInTitlebar(bool aState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mWindow)
+ [mWindow setDrawsContentsIntoWindowFrame:aState];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+ if (mPopupContentView)
+ return mPopupContentView->SynthesizeNativeMouseEvent(aPoint, aNativeMessage,
+ aModifierFlags, nullptr);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsCocoaWindow::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mPopupContentView) {
+ return mPopupContentView->UpdateThemeGeometries(aThemeGeometries);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetPopupWindowLevel()
+{
+ if (!mWindow)
+ return;
+
+ // Floating popups are at the floating level and hide when the window is
+ // deactivated.
+ if (mPopupLevel == ePopupLevelFloating) {
+ [mWindow setLevel:NSFloatingWindowLevel];
+ [mWindow setHidesOnDeactivate:YES];
+ }
+ else {
+ // Otherwise, this is a top-level or parent popup. Parent popups always
+ // appear just above their parent and essentially ignore the level.
+ [mWindow setLevel:NSPopUpMenuWindowLevel];
+ [mWindow setHidesOnDeactivate:NO];
+ }
+}
+
+NS_IMETHODIMP_(void)
+nsCocoaWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mInputContext = aContext;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP_(bool)
+nsCocoaWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(aEvent, aCallback, aCallbackData);
+}
+
+@implementation WindowDelegate
+
+// We try to find a gecko menu bar to paint. If one does not exist, just paint
+// the application menu by itself so that a window doesn't have some other
+// window's menu bar.
++ (void)paintMenubarForWindow:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // make sure we only act on windows that have this kind of
+ // object as a delegate
+ id windowDelegate = [aWindow delegate];
+ if ([windowDelegate class] != [self class])
+ return;
+
+ nsCocoaWindow* geckoWidget = [windowDelegate geckoWidget];
+ NS_ASSERTION(geckoWidget, "Window delegate not returning a gecko widget!");
+
+ nsMenuBarX* geckoMenuBar = geckoWidget->GetMenuBar();
+ if (geckoMenuBar) {
+ geckoMenuBar->Paint();
+ }
+ else {
+ // sometimes we don't have a native application menu early in launching
+ if (!sApplicationMenu)
+ return;
+
+ NSMenu* mainMenu = [NSApp mainMenu];
+ NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+
+ // Create a new menu bar.
+ // We create a GeckoNSMenu because all menu bar NSMenu objects should use that subclass for
+ // key handling reasons.
+ GeckoNSMenu* newMenuBar = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
+
+ // move the application menu from the existing menu bar to the new one
+ NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
+ [mainMenu removeItemAtIndex:0];
+ [newMenuBar insertItem:firstMenuItem atIndex:0];
+ [firstMenuItem release];
+
+ // set our new menu bar as the main menu
+ [NSApp setMainMenu:newMenuBar];
+ [newMenuBar release];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ [super init];
+ mGeckoWindow = geckoWind;
+ mToplevelActiveState = false;
+ mHasEverBeenZoomed = false;
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize
+{
+ RollUpPopups();
+
+ return proposedFrameSize;
+}
+
+- (void)windowDidResize:(NSNotification *)aNotification
+{
+ BaseWindow* window = [aNotification object];
+ [window updateTrackingArea];
+
+ if (!mGeckoWindow)
+ return;
+
+ // Resizing might have changed our zoom state.
+ mGeckoWindow->DispatchSizeModeEvent();
+ mGeckoWindow->ReportSizeEvent();
+}
+
+- (void)windowDidChangeScreen:(NSNotification *)aNotification
+{
+ if (!mGeckoWindow)
+ return;
+
+ // Because of Cocoa's peculiar treatment of zero-size windows (see comments
+ // at GetBackingScaleFactor() above), we sometimes have a situation where
+ // our concept of backing scale (based on the screen where the zero-sized
+ // window is positioned) differs from Cocoa's idea (always based on the
+ // Retina screen, AFAICT, even when an external non-Retina screen is the
+ // primary display).
+ //
+ // As a result, if the window was created with zero size on an external
+ // display, but then made visible on the (secondary) Retina screen, we
+ // will *not* get a windowDidChangeBackingProperties notification for it.
+ // This leads to an incorrect GetDefaultScale(), and widget coordinate
+ // confusion, as per bug 853252.
+ //
+ // To work around this, we check for a backing scale mismatch when we
+ // receive a windowDidChangeScreen notification, as we will receive this
+ // even if Cocoa was already treating the zero-size window as having
+ // Retina backing scale.
+ NSWindow *window = (NSWindow *)[aNotification object];
+ if ([window respondsToSelector:@selector(backingScaleFactor)]) {
+ if (GetBackingScaleFactor(window) != mGeckoWindow->BackingScaleFactor()) {
+ mGeckoWindow->BackingScaleFactorChanged();
+ }
+ }
+
+ mGeckoWindow->ReportMoveEvent();
+}
+
+// Lion's full screen mode will bypass our internal fullscreen tracking, so
+// we need to catch it when we transition and call our own methods, which in
+// turn will fire "fullscreen" events.
+- (void)windowDidEnterFullScreen:(NSNotification *)notification
+{
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->EnteredFullScreen(true);
+
+ // On Yosemite, the NSThemeFrame class has two new properties --
+ // titlebarView (an NSTitlebarView object) and titlebarContainerView (an
+ // NSTitlebarContainerView object). These are used to display the titlebar
+ // in fullscreen mode. In Safari they're not transparent. But in Firefox
+ // for some reason they are, which causes bug 1069658. The following code
+ // works around this Apple bug or design flaw.
+ NSWindow *window = (NSWindow *) [notification object];
+ NSView *frameView = [[window contentView] superview];
+ NSView *titlebarView = nil;
+ NSView *titlebarContainerView = nil;
+ if ([frameView respondsToSelector:@selector(titlebarView)]) {
+ titlebarView = [frameView titlebarView];
+ }
+ if ([frameView respondsToSelector:@selector(titlebarContainerView)]) {
+ titlebarContainerView = [frameView titlebarContainerView];
+ }
+ if ([titlebarView respondsToSelector:@selector(setTransparent:)]) {
+ [titlebarView setTransparent:NO];
+ }
+ if ([titlebarContainerView respondsToSelector:@selector(setTransparent:)]) {
+ [titlebarContainerView setTransparent:NO];
+ }
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification
+{
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->EnteredFullScreen(false);
+}
+
+- (void)windowDidFailToEnterFullScreen:(NSWindow *)window
+{
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->EnteredFullScreen(false);
+}
+
+- (void)windowDidFailToExitFullScreen:(NSWindow *)window
+{
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->EnteredFullScreen(true);
+}
+
+- (void)windowDidBecomeMain:(NSNotification *)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
+ // app modally. If one of those is up then we want it to retain its menu bar.
+ if ([NSApp _isRunningAppModal])
+ return;
+ NSWindow* window = [aNotification object];
+ if (window)
+ [WindowDelegate paintMenubarForWindow:window];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowDidResignMain:(NSNotification *)aNotification
+{
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
+ // app modally. If one of those is up then we want it to retain its menu bar.
+ if ([NSApp _isRunningAppModal])
+ return;
+ RefPtr<nsMenuBarX> hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (hiddenWindowMenuBar) {
+ // printf("painting hidden window menu bar due to window losing main status\n");
+ hiddenWindowMenuBar->Paint();
+ }
+}
+
+- (void)windowDidBecomeKey:(NSNotification *)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ NSWindow* window = [aNotification object];
+ if ([window isSheet])
+ [WindowDelegate paintMenubarForWindow:window];
+
+ nsChildView* mainChildView =
+ static_cast<nsChildView*>([[(BaseWindow*)window mainChildView] widget]);
+ if (mainChildView) {
+ if (mainChildView->GetInputContext().IsPasswordEditor()) {
+ TextInputHandler::EnableSecureEventInput();
+ } else {
+ TextInputHandler::EnsureSecureEventInputDisabled();
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowDidResignKey:(NSNotification *)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ // If a sheet just resigned key then we should paint the menu bar
+ // for whatever window is now main.
+ NSWindow* window = [aNotification object];
+ if ([window isSheet])
+ [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]];
+
+ TextInputHandler::EnsureSecureEventInputDisabled();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowWillMove:(NSNotification *)aNotification
+{
+ RollUpPopups();
+}
+
+- (void)windowDidMove:(NSNotification *)aNotification
+{
+ if (mGeckoWindow)
+ mGeckoWindow->ReportMoveEvent();
+}
+
+- (BOOL)windowShouldClose:(id)sender
+{
+ nsIWidgetListener* listener = mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr;
+ if (listener)
+ listener->RequestWindowClose(mGeckoWindow);
+ return NO; // gecko will do it
+}
+
+- (void)windowWillClose:(NSNotification *)aNotification
+{
+ RollUpPopups();
+}
+
+- (void)windowWillMiniaturize:(NSNotification *)aNotification
+{
+ RollUpPopups();
+}
+
+- (void)windowDidMiniaturize:(NSNotification *)aNotification
+{
+ if (mGeckoWindow)
+ mGeckoWindow->DispatchSizeModeEvent();
+}
+
+- (void)windowDidDeminiaturize:(NSNotification *)aNotification
+{
+ if (mGeckoWindow)
+ mGeckoWindow->DispatchSizeModeEvent();
+}
+
+- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)proposedFrame
+{
+ if (!mHasEverBeenZoomed && [window isZoomed])
+ return NO; // See bug 429954.
+
+ mHasEverBeenZoomed = YES;
+ return YES;
+}
+
+- (void)didEndSheet:(NSWindow*)sheet returnCode:(int)returnCode contextInfo:(void*)contextInfo
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Note: 'contextInfo' (if it is set) is the window that is the parent of
+ // the sheet. The value of contextInfo is determined in
+ // nsCocoaWindow::Show(). If it's set, 'contextInfo' is always the top-
+ // level window, not another sheet itself. But 'contextInfo' is nil if
+ // our parent window is also a sheet -- in that case we shouldn't send
+ // the top-level window any activate events (because it's our parent
+ // window that needs to get these events, not the top-level window).
+ [TopLevelWindowData deactivateInWindow:sheet];
+ [sheet orderOut:self];
+ if (contextInfo)
+ [TopLevelWindowData activateInWindow:(NSWindow*)contextInfo];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowDidChangeBackingProperties:(NSNotification *)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSWindow *window = (NSWindow *)[aNotification object];
+
+ if ([window respondsToSelector:@selector(backingScaleFactor)]) {
+ CGFloat oldFactor =
+ [[[aNotification userInfo]
+ objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
+ if ([window backingScaleFactor] != oldFactor) {
+ mGeckoWindow->BackingScaleFactorChanged();
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (nsCocoaWindow*)geckoWidget
+{
+ return mGeckoWindow;
+}
+
+- (bool)toplevelActiveState
+{
+ return mToplevelActiveState;
+}
+
+- (void)sendToplevelActivateEvents
+{
+ if (!mToplevelActiveState && mGeckoWindow) {
+ nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
+ if (listener) {
+ listener->WindowActivated();
+ }
+ mToplevelActiveState = true;
+ }
+}
+
+- (void)sendToplevelDeactivateEvents
+{
+ if (mToplevelActiveState && mGeckoWindow) {
+ nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
+ if (listener) {
+ listener->WindowDeactivated();
+ }
+ mToplevelActiveState = false;
+ }
+}
+
+@end
+
+static float
+GetDPI(NSWindow* aWindow)
+{
+ NSScreen* screen = [aWindow screen];
+ if (!screen)
+ return 96.0f;
+
+ CGDirectDisplayID displayID =
+ [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
+ CGFloat heightMM = ::CGDisplayScreenSize(displayID).height;
+ size_t heightPx = ::CGDisplayPixelsHigh(displayID);
+ if (heightMM < 1 || heightPx < 1) {
+ // Something extremely bogus is going on
+ return 96.0f;
+ }
+
+ float dpi = heightPx / (heightMM / MM_PER_INCH_FLOAT);
+
+ // Account for HiDPI mode where Cocoa's "points" do not correspond to real
+ // device pixels
+ CGFloat backingScale = GetBackingScaleFactor(aWindow);
+
+ return dpi * backingScale;
+}
+
+@interface NSView(FrameViewMethodSwizzling)
+- (NSPoint)FrameView__closeButtonOrigin;
+- (NSPoint)FrameView__fullScreenButtonOrigin;
+@end
+
+@implementation NSView(FrameViewMethodSwizzling)
+
+- (NSPoint)FrameView__closeButtonOrigin
+{
+ NSPoint defaultPosition = [self FrameView__closeButtonOrigin];
+ if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
+ return [(ToolbarWindow*)[self window] windowButtonsPositionWithDefaultPosition:defaultPosition];
+ }
+ return defaultPosition;
+}
+
+- (NSPoint)FrameView__fullScreenButtonOrigin
+{
+ NSPoint defaultPosition = [self FrameView__fullScreenButtonOrigin];
+ if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
+ return [(ToolbarWindow*)[self window] fullScreenButtonPositionWithDefaultPosition:defaultPosition];
+ }
+ return defaultPosition;
+}
+
+@end
+
+static NSMutableSet *gSwizzledFrameViewClasses = nil;
+
+@interface NSWindow(PrivateSetNeedsDisplayInRectMethod)
+ - (void)_setNeedsDisplayInRect:(NSRect)aRect;
+@end
+
+// This method is on NSThemeFrame starting with 10.10, but since NSThemeFrame
+// is not a public class, we declare the method on NSView instead. We only have
+// this declaration in order to avoid compiler warnings.
+@interface NSView(PrivateAddKnownSubviewMethod)
+ - (void)_addKnownSubview:(NSView*)aView positioned:(NSWindowOrderingMode)place relativeTo:(NSView*)otherView;
+@end
+
+// Available on 10.10
+@interface NSWindow(PrivateCornerMaskMethod)
+ - (id)_cornerMask;
+ - (void)_cornerMaskChanged;
+@end
+
+#if !defined(MAC_OS_X_VERSION_10_10) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10
+
+@interface NSImage(CapInsets)
+- (void)setCapInsets:(NSEdgeInsets)capInsets;
+@end
+
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_8) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
+
+@interface NSImage(ImageCreationWithDrawingHandler)
++ (NSImage *)imageWithSize:(NSSize)size
+ flipped:(BOOL)drawingHandlerShouldBeCalledWithFlippedContext
+ drawingHandler:(BOOL (^)(NSRect dstRect))drawingHandler;
+@end
+
+#endif
+
+@interface BaseWindow(Private)
+- (void)removeTrackingArea;
+- (void)cursorUpdated:(NSEvent*)aEvent;
+- (void)updateContentViewSize;
+- (void)reflowTitlebarElements;
+@end
+
+@implementation BaseWindow
+
+- (id)_cornerMask
+{
+ if (!mUseMenuStyle) {
+ return [super _cornerMask];
+ }
+
+ 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;
+}
+
+// The frame of a window is implemented using undocumented NSView subclasses.
+// We offset the window buttons by overriding the methods _closeButtonOrigin
+// and _fullScreenButtonOrigin on these frame view classes. The class which is
+// used for a window is determined in the window's frameViewClassForStyleMask:
+// method, so this is where we make sure that we have swizzled the method on
+// all encountered classes.
++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask
+{
+ Class frameViewClass = [super frameViewClassForStyleMask:styleMask];
+
+ if (!gSwizzledFrameViewClasses) {
+ gSwizzledFrameViewClasses = [[NSMutableSet setWithCapacity:3] retain];
+ if (!gSwizzledFrameViewClasses) {
+ return frameViewClass;
+ }
+ }
+
+ static IMP our_closeButtonOrigin =
+ class_getMethodImplementation([NSView class],
+ @selector(FrameView__closeButtonOrigin));
+ static IMP our_fullScreenButtonOrigin =
+ class_getMethodImplementation([NSView class],
+ @selector(FrameView__fullScreenButtonOrigin));
+
+ if (![gSwizzledFrameViewClasses containsObject:frameViewClass]) {
+ // Either of these methods might be implemented in both a subclass of
+ // NSFrameView and one of its own subclasses. Which means that if we
+ // aren't careful we might end up swizzling the same method twice.
+ // Since method swizzling involves swapping pointers, this would break
+ // things.
+ IMP _closeButtonOrigin =
+ class_getMethodImplementation(frameViewClass,
+ @selector(_closeButtonOrigin));
+ if (_closeButtonOrigin && _closeButtonOrigin != our_closeButtonOrigin) {
+ nsToolkit::SwizzleMethods(frameViewClass, @selector(_closeButtonOrigin),
+ @selector(FrameView__closeButtonOrigin));
+ }
+ IMP _fullScreenButtonOrigin =
+ class_getMethodImplementation(frameViewClass,
+ @selector(_fullScreenButtonOrigin));
+ if (_fullScreenButtonOrigin &&
+ _fullScreenButtonOrigin != our_fullScreenButtonOrigin) {
+ nsToolkit::SwizzleMethods(frameViewClass, @selector(_fullScreenButtonOrigin),
+ @selector(FrameView__fullScreenButtonOrigin));
+ }
+ [gSwizzledFrameViewClasses addObject:frameViewClass];
+ }
+
+ return frameViewClass;
+}
+
+- (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
+{
+ mDrawsIntoWindowFrame = NO;
+ [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag];
+ mState = nil;
+ mActiveTitlebarColor = nil;
+ mInactiveTitlebarColor = nil;
+ mScheduledShadowInvalidation = NO;
+ mDisabledNeedsDisplay = NO;
+ mDPI = GetDPI(self);
+ mTrackingArea = nil;
+ mDirtyRect = NSZeroRect;
+ mBeingShown = NO;
+ mDrawTitle = NO;
+ mBrightTitlebarForeground = NO;
+ mUseMenuStyle = NO;
+ [self updateTrackingArea];
+
+ return self;
+}
+
+- (void)setUseMenuStyle:(BOOL)aValue
+{
+ if (aValue != mUseMenuStyle) {
+ mUseMenuStyle = aValue;
+ if ([self respondsToSelector:@selector(_cornerMaskChanged)]) {
+ [self _cornerMaskChanged];
+ }
+ }
+}
+
+- (void)setBeingShown:(BOOL)aValue
+{
+ mBeingShown = aValue;
+}
+
+- (BOOL)isBeingShown
+{
+ return mBeingShown;
+}
+
+- (BOOL)isVisibleOrBeingShown
+{
+ return [super isVisible] || mBeingShown;
+}
+
+- (void)disableSetNeedsDisplay
+{
+ mDisabledNeedsDisplay = YES;
+}
+
+- (void)enableSetNeedsDisplay
+{
+ mDisabledNeedsDisplay = NO;
+}
+
+- (void)dealloc
+{
+ [mActiveTitlebarColor release];
+ [mInactiveTitlebarColor release];
+ [self removeTrackingArea];
+ ChildViewMouseTracker::OnDestroyWindow(self);
+ [super dealloc];
+}
+
+static const NSString* kStateTitleKey = @"title";
+static const NSString* kStateDrawsContentsIntoWindowFrameKey = @"drawsContentsIntoWindowFrame";
+static const NSString* kStateActiveTitlebarColorKey = @"activeTitlebarColor";
+static const NSString* kStateInactiveTitlebarColorKey = @"inactiveTitlebarColor";
+static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
+static const NSString* kStateCollectionBehavior = @"collectionBehavior";
+
+- (void)importState:(NSDictionary*)aState
+{
+ [self setTitle:[aState objectForKey:kStateTitleKey]];
+ [self setDrawsContentsIntoWindowFrame:[[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey] boolValue]];
+ [self setTitlebarColor:[aState objectForKey:kStateActiveTitlebarColorKey] forActiveWindow:YES];
+ [self setTitlebarColor:[aState objectForKey:kStateInactiveTitlebarColorKey] forActiveWindow:NO];
+ [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton] boolValue]];
+ [self setCollectionBehavior:[[aState objectForKey:kStateCollectionBehavior] unsignedIntValue]];
+}
+
+- (NSMutableDictionary*)exportState
+{
+ NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10];
+ [state setObject:[self title] forKey:kStateTitleKey];
+ [state setObject:[NSNumber numberWithBool:[self drawsContentsIntoWindowFrame]]
+ forKey:kStateDrawsContentsIntoWindowFrameKey];
+ NSColor* activeTitlebarColor = [self titlebarColorForActiveWindow:YES];
+ if (activeTitlebarColor) {
+ [state setObject:activeTitlebarColor forKey:kStateActiveTitlebarColorKey];
+ }
+ NSColor* inactiveTitlebarColor = [self titlebarColorForActiveWindow:NO];
+ if (inactiveTitlebarColor) {
+ [state setObject:inactiveTitlebarColor forKey:kStateInactiveTitlebarColorKey];
+ }
+ [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]]
+ forKey:kStateShowsToolbarButton];
+ [state setObject:[NSNumber numberWithUnsignedInt: [self collectionBehavior]]
+ forKey:kStateCollectionBehavior];
+ return state;
+}
+
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
+{
+ bool changed = (aState != mDrawsIntoWindowFrame);
+ mDrawsIntoWindowFrame = aState;
+ if (changed) {
+ [self updateContentViewSize];
+ [self reflowTitlebarElements];
+ }
+}
+
+- (BOOL)drawsContentsIntoWindowFrame
+{
+ return mDrawsIntoWindowFrame;
+}
+
+- (void)setWantsTitleDrawn:(BOOL)aDrawTitle
+{
+ mDrawTitle = aDrawTitle;
+}
+
+- (BOOL)wantsTitleDrawn
+{
+ return mDrawTitle;
+}
+
+- (void)setUseBrightTitlebarForeground:(BOOL)aBrightForeground
+{
+ mBrightTitlebarForeground = aBrightForeground;
+ [[self standardWindowButton:NSWindowFullScreenButton] setNeedsDisplay:YES];
+}
+
+- (BOOL)useBrightTitlebarForeground
+{
+ return mBrightTitlebarForeground;
+}
+
+// Pass nil here to get the default appearance.
+- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
+{
+ [aColor retain];
+ if (aActive) {
+ [mActiveTitlebarColor release];
+ mActiveTitlebarColor = aColor;
+ } else {
+ [mInactiveTitlebarColor release];
+ mInactiveTitlebarColor = aColor;
+ }
+}
+
+- (NSColor*)titlebarColorForActiveWindow:(BOOL)aActive
+{
+ return aActive ? mActiveTitlebarColor : mInactiveTitlebarColor;
+}
+
+- (void)deferredInvalidateShadow
+{
+ if (mScheduledShadowInvalidation || [self isOpaque] || ![self hasShadow])
+ return;
+
+ [self performSelector:@selector(invalidateShadow) withObject:nil afterDelay:0];
+ mScheduledShadowInvalidation = YES;
+}
+
+- (void)invalidateShadow
+{
+ [super invalidateShadow];
+ mScheduledShadowInvalidation = NO;
+}
+
+- (float)getDPI
+{
+ return mDPI;
+}
+
+- (NSView*)trackingAreaView
+{
+ NSView* contentView = [self contentView];
+ return [contentView superview] ? [contentView superview] : contentView;
+}
+
+- (ChildView*)mainChildView
+{
+ NSView *contentView = [self contentView];
+ // A PopupWindow's contentView is a ChildView object.
+ if ([contentView isKindOfClass:[ChildView class]]) {
+ return (ChildView*)contentView;
+ }
+ NSView* lastView = [[contentView subviews] lastObject];
+ if ([lastView isKindOfClass:[ChildView class]]) {
+ return (ChildView*)lastView;
+ }
+ return nil;
+}
+
+- (void)removeTrackingArea
+{
+ if (mTrackingArea) {
+ [[self trackingAreaView] removeTrackingArea:mTrackingArea];
+ [mTrackingArea release];
+ mTrackingArea = nil;
+ }
+}
+
+- (void)updateTrackingArea
+{
+ [self removeTrackingArea];
+
+ NSView* view = [self trackingAreaView];
+ const NSTrackingAreaOptions options =
+ NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways;
+ mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds]
+ options:options
+ owner:self
+ userInfo:nil];
+ [view addTrackingArea:mTrackingArea];
+}
+
+- (void)mouseEntered:(NSEvent*)aEvent
+{
+ ChildViewMouseTracker::MouseEnteredWindow(aEvent);
+}
+
+- (void)mouseExited:(NSEvent*)aEvent
+{
+ ChildViewMouseTracker::MouseExitedWindow(aEvent);
+}
+
+- (void)mouseMoved:(NSEvent*)aEvent
+{
+ ChildViewMouseTracker::MouseMoved(aEvent);
+}
+
+- (void)cursorUpdated:(NSEvent*)aEvent
+{
+ // Nothing to do here, but NSTrackingArea wants us to implement this method.
+}
+
+- (void)_setNeedsDisplayInRect:(NSRect)aRect
+{
+ // Prevent unnecessary invalidations due to moving NSViews (e.g. for plugins)
+ if (!mDisabledNeedsDisplay) {
+ // This method is only called by Cocoa, so when we're here, we know that
+ // it's available and don't need to check whether our superclass responds
+ // to the selector.
+ [super _setNeedsDisplayInRect:aRect];
+ mDirtyRect = NSUnionRect(mDirtyRect, aRect);
+ }
+}
+
+- (NSRect)getAndResetNativeDirtyRect
+{
+ NSRect dirtyRect = mDirtyRect;
+ mDirtyRect = NSZeroRect;
+ return dirtyRect;
+}
+
+- (void)updateContentViewSize
+{
+ NSRect rect = [self contentRectForFrameRect:[self frame]];
+ [[self contentView] setFrameSize:rect.size];
+}
+
+// Possibly move the titlebar buttons.
+- (void)reflowTitlebarElements
+{
+ NSView *frameView = [[self contentView] superview];
+ if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) {
+ [frameView _tileTitlebarAndRedisplay:NO];
+ }
+}
+
+// Override methods that translate between content rect and frame rect.
+- (NSRect)contentRectForFrameRect:(NSRect)aRect
+{
+ if ([self drawsContentsIntoWindowFrame]) {
+ return aRect;
+ }
+ return [super contentRectForFrameRect:aRect];
+}
+
+- (NSRect)contentRectForFrameRect:(NSRect)aRect styleMask:(NSUInteger)aMask
+{
+ if ([self drawsContentsIntoWindowFrame]) {
+ return aRect;
+ }
+ if ([super respondsToSelector:@selector(contentRectForFrameRect:styleMask:)]) {
+ return [super contentRectForFrameRect:aRect styleMask:aMask];
+ } else {
+ return [NSWindow contentRectForFrameRect:aRect styleMask:aMask];
+ }
+}
+
+- (NSRect)frameRectForContentRect:(NSRect)aRect
+{
+ if ([self drawsContentsIntoWindowFrame]) {
+ return aRect;
+ }
+ return [super frameRectForContentRect:aRect];
+}
+
+- (NSRect)frameRectForContentRect:(NSRect)aRect styleMask:(NSUInteger)aMask
+{
+ if ([self drawsContentsIntoWindowFrame]) {
+ return aRect;
+ }
+ if ([super respondsToSelector:@selector(frameRectForContentRect:styleMask:)]) {
+ return [super frameRectForContentRect:aRect styleMask:aMask];
+ } else {
+ return [NSWindow frameRectForContentRect:aRect styleMask:aMask];
+ }
+}
+
+- (void)setContentView:(NSView*)aView
+{
+ [super setContentView:aView];
+
+ // Now move the contentView to the bottommost layer so that it's guaranteed
+ // to be under the window buttons.
+ NSView* frameView = [aView superview];
+ [aView removeFromSuperview];
+ if ([frameView respondsToSelector:@selector(_addKnownSubview:positioned:relativeTo:)]) {
+ // 10.10 prints a warning when we call addSubview on the frame view, so we
+ // silence the warning by calling a private method instead.
+ [frameView _addKnownSubview:aView positioned:NSWindowBelow relativeTo:nil];
+ } else {
+ [frameView addSubview:aView positioned:NSWindowBelow relativeTo:nil];
+ }
+}
+
+- (NSArray*)titlebarControls
+{
+ // Return all subviews of the frameView which are not the content view.
+ NSView* frameView = [[self contentView] superview];
+ NSMutableArray* array = [[[frameView subviews] mutableCopy] autorelease];
+ [array removeObject:[self contentView]];
+ return array;
+}
+
+- (BOOL)respondsToSelector:(SEL)aSelector
+{
+ // Claim the window doesn't respond to this so that the system
+ // doesn't steal keyboard equivalents for it. Bug 613710.
+ if (aSelector == @selector(cancelOperation:)) {
+ return NO;
+ }
+
+ return [super respondsToSelector:aSelector];
+}
+
+- (void) doCommandBySelector:(SEL)aSelector
+{
+ // We override this so that it won't beep if it can't act.
+ // We want to control the beeping for missing or disabled
+ // commands ourselves.
+ [self tryToPerform:aSelector with:nil];
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ id retval = [super accessibilityAttributeValue:attribute];
+
+ // The following works around a problem with Text-to-Speech on OS X 10.7.
+ // See bug 674612 for more info.
+ //
+ // When accessibility is off, AXUIElementCopyAttributeValue(), when called
+ // on an AXApplication object to get its AXFocusedUIElement attribute,
+ // always returns an AXWindow object (the actual browser window -- never a
+ // mozAccessible object). This also happens with accessibility turned on,
+ // if no other object in the browser window has yet been focused. But if
+ // the browser window has a title bar (as it currently always does), the
+ // AXWindow object will always have four "accessible" children, one of which
+ // is an AXStaticText object (the title bar's "title"; the other three are
+ // the close, minimize and zoom buttons). This means that (for complicated
+ // reasons, for which see bug 674612) Text-to-Speech on OS X 10.7 will often
+ // "speak" the window title, no matter what text is selected, or even if no
+ // text at all is selected. (This always happens when accessibility is off.
+ // It doesn't happen in Firefox releases because Apple has (on OS X 10.7)
+ // special-cased the handling of apps whose CFBundleIdentifier is
+ // org.mozilla.firefox.)
+ //
+ // We work around this problem by only returning AXChildren that are
+ // mozAccessible object or are one of the titlebar's buttons (which
+ // instantiate subclasses of NSButtonCell).
+ if ([retval isKindOfClass:[NSArray class]] &&
+ [attribute isEqualToString:@"AXChildren"]) {
+ NSMutableArray *holder = [NSMutableArray arrayWithCapacity:10];
+ [holder addObjectsFromArray:(NSArray *)retval];
+ NSUInteger count = [holder count];
+ for (NSInteger i = count - 1; i >= 0; --i) {
+ id item = [holder objectAtIndex:i];
+ // Remove anything from holder that isn't one of the titlebar's buttons
+ // (which instantiate subclasses of NSButtonCell) or a mozAccessible
+ // object (or one of its subclasses).
+ if (![item isKindOfClass:[NSButtonCell class]] &&
+ ![item respondsToSelector:@selector(hasRepresentedView)]) {
+ [holder removeObjectAtIndex:i];
+ }
+ }
+ retval = [NSArray arrayWithArray:holder];
+ }
+
+ return retval;
+}
+
+@end
+
+// This class allows us to exercise control over the window's title bar. This
+// allows for a "unified toolbar" look without having to extend the content
+// area into the title bar. It works like this:
+// 1) We set the window's style to textured.
+// 2) Because of this, the background color applies to the entire window, including
+// the titlebar area. For normal textured windows, the default pattern is a
+// "brushed metal" image on Tiger and a unified gradient on Leopard.
+// 3) We set the background color to a custom NSColor subclass that knows how tall the window is.
+// When -set is called on it, it sets a pattern (with a draw callback) as the fill. In that callback,
+// it paints the the titlebar and background colors in the correct areas of the context it's given,
+// which will fill the entire window (CG will tile it horizontally for us).
+// 4) Whenever the window's main state changes and when [window display] is called,
+// Cocoa redraws the titlebar using the patternDraw callback function.
+//
+// This class also provides us with a pill button to show/hide the toolbar up to 10.6.
+//
+// Drawing the unified gradient in the titlebar and the toolbar works like this:
+// 1) In the style sheet we set the toolbar's -moz-appearance to toolbar.
+// 2) When the toolbar is visible and we paint the application chrome
+// window, the array that Gecko passes nsChildView::UpdateThemeGeometries
+// will contain an entry for the widget type NS_THEME_TOOLBAR.
+// 3) nsChildView::UpdateThemeGeometries finds the toolbar frame's ToolbarWindow
+// and passes the toolbar frame's height to setUnifiedToolbarHeight.
+// 4) If the toolbar height has changed, a titlebar redraw is triggered and the
+// upper part of the unified gradient is drawn in the titlebar.
+// 5) The lower part of the unified gradient in the toolbar is drawn during
+// normal window content painting in nsNativeThemeCocoa::DrawUnifiedToolbar.
+//
+// Whenever the unified gradient is drawn in the titlebar or the toolbar, both
+// titlebar height and toolbar height must be known in order to construct the
+// correct gradient. But you can only get from the toolbar frame
+// to the containing window - the other direction doesn't work. That's why the
+// toolbar height is cached in the ToolbarWindow but nsNativeThemeCocoa can simply
+// query the window for its titlebar height when drawing the toolbar.
+//
+// Note that in drawsContentsIntoWindowFrame mode, titlebar drawing works in a
+// completely different way: In that mode, the window's mainChildView will
+// cover the titlebar completely and nothing that happens in the window
+// background will reach the screen.
+@implementation ToolbarWindow
+
+- (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ aStyle = aStyle | NSTexturedBackgroundWindowMask;
+ if ((self = [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag])) {
+ mColor = [[TitlebarAndBackgroundColor alloc] initWithWindow:self];
+ // Bypass our guard method below.
+ [super setBackgroundColor:mColor];
+ mBackgroundColor = [[NSColor whiteColor] retain];
+
+ mUnifiedToolbarHeight = 22.0f;
+ mWindowButtonsRect = NSZeroRect;
+ mFullScreenButtonRect = NSZeroRect;
+
+ // setBottomCornerRounded: is a private API call, so we check to make sure
+ // we respond to it just in case.
+ if ([self respondsToSelector:@selector(setBottomCornerRounded:)])
+ [self setBottomCornerRounded:YES];
+
+ [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
+ [self setContentBorderThickness:0.0f forEdge:NSMaxYEdge];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [super setBackgroundColor:[NSColor whiteColor]];
+ [mColor release];
+ [mBackgroundColor release];
+ [mTitlebarView release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
+{
+ [super setTitlebarColor:aColor forActiveWindow:aActive];
+ [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
+}
+
+- (void)setBackgroundColor:(NSColor*)aColor
+{
+ [aColor retain];
+ [mBackgroundColor release];
+ mBackgroundColor = aColor;
+}
+
+- (NSColor*)windowBackgroundColor
+{
+ return mBackgroundColor;
+}
+
+- (void)setTemporaryBackgroundColor
+{
+ [super setBackgroundColor:[NSColor whiteColor]];
+}
+
+- (void)restoreBackgroundColor
+{
+ [super setBackgroundColor:mBackgroundColor];
+}
+
+- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect
+{
+ [self setTitlebarNeedsDisplayInRect:aRect sync:NO];
+}
+
+- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync
+{
+ NSRect titlebarRect = [self titlebarRect];
+ NSRect rect = NSIntersectionRect(titlebarRect, aRect);
+ if (NSIsEmptyRect(rect))
+ return;
+
+ NSView* borderView = [[self contentView] superview];
+ if (!borderView)
+ return;
+
+ if (aSync) {
+ [borderView displayRect:rect];
+ } else {
+ [borderView setNeedsDisplayInRect:rect];
+ }
+}
+
+- (NSRect)titlebarRect
+{
+ CGFloat titlebarHeight = [self titlebarHeight];
+ return NSMakeRect(0, [self frame].size.height - titlebarHeight,
+ [self frame].size.width, titlebarHeight);
+}
+
+// Returns the unified height of titlebar + toolbar.
+- (CGFloat)unifiedToolbarHeight
+{
+ return mUnifiedToolbarHeight;
+}
+
+- (CGFloat)titlebarHeight
+{
+ // We use the original content rect here, not what we return from
+ // [self contentRectForFrameRect:], because that would give us a
+ // titlebarHeight of zero in drawsContentsIntoWindowFrame mode.
+ NSRect frameRect = [self frame];
+ NSRect originalContentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:[self styleMask]];
+ return NSMaxY(frameRect) - NSMaxY(originalContentRect);
+}
+
+// Stores the complete height of titlebar + toolbar.
+- (void)setUnifiedToolbarHeight:(CGFloat)aHeight
+{
+ if (aHeight == mUnifiedToolbarHeight)
+ return;
+
+ mUnifiedToolbarHeight = aHeight;
+
+ if (![self drawsContentsIntoWindowFrame]) {
+ // Redraw the title bar. If we're inside painting, we'll do it right now,
+ // otherwise we'll just invalidate it.
+ BOOL needSyncRedraw = ([NSView focusView] != nil);
+ [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw];
+ }
+}
+
+// Extending the content area into the title bar works by resizing the
+// mainChildView so that it covers the titlebar.
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
+{
+ BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
+ [super setDrawsContentsIntoWindowFrame:aState];
+ if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
+ // Here we extend / shrink our mainChildView. We do that by firing a resize
+ // event which will cause the ChildView to be resized to the rect returned
+ // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
+ // value on what we return from drawsContentsIntoWindowFrame.
+ WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
+ nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
+ if (geckoWindow) {
+ // Re-layout our contents.
+ geckoWindow->ReportSizeEvent();
+ }
+
+ // Resizing the content area causes a reflow which would send a synthesized
+ // mousemove event to the old mouse position relative to the top left
+ // corner of the content area. But the mouse has shifted relative to the
+ // content area, so that event would have wrong position information. So
+ // we'll send a mouse move event with the correct new position.
+ ChildViewMouseTracker::ResendLastMouseMoveEvent();
+ }
+}
+
+- (void)setWantsTitleDrawn:(BOOL)aDrawTitle
+{
+ [super setWantsTitleDrawn:aDrawTitle];
+ [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
+}
+
+- (void)setSheetAttachmentPosition:(CGFloat)aY
+{
+ CGFloat topMargin = aY - [self titlebarHeight];
+ [self setContentBorderThickness:topMargin forEdge:NSMaxYEdge];
+}
+
+- (void)placeWindowButtons:(NSRect)aRect
+{
+ if (!NSEqualRects(mWindowButtonsRect, aRect)) {
+ mWindowButtonsRect = aRect;
+ [self reflowTitlebarElements];
+ }
+}
+
+- (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition
+{
+ NSInteger styleMask = [self styleMask];
+ if ([self drawsContentsIntoWindowFrame] &&
+ !(styleMask & NSFullScreenWindowMask) && (styleMask & NSTitledWindowMask)) {
+ if (NSIsEmptyRect(mWindowButtonsRect)) {
+ // Empty rect. Let's hide the buttons.
+ // Position is in non-flipped window coordinates. Using frame's height
+ // for the vertical coordinate will move the buttons above the window,
+ // making them invisible.
+ return NSMakePoint(0, [self frame].size.height);
+ }
+ return NSMakePoint(mWindowButtonsRect.origin.x, mWindowButtonsRect.origin.y);
+ }
+ return aDefaultPosition;
+}
+
+- (void)placeFullScreenButton:(NSRect)aRect
+{
+ if (!NSEqualRects(mFullScreenButtonRect, aRect)) {
+ mFullScreenButtonRect = aRect;
+ [self reflowTitlebarElements];
+ }
+}
+
+- (NSPoint)fullScreenButtonPositionWithDefaultPosition:(NSPoint)aDefaultPosition
+{
+ if ([self drawsContentsIntoWindowFrame] && !NSIsEmptyRect(mFullScreenButtonRect)) {
+ return NSMakePoint(std::min(mFullScreenButtonRect.origin.x, aDefaultPosition.x),
+ std::min(mFullScreenButtonRect.origin.y, aDefaultPosition.y));
+ }
+ return aDefaultPosition;
+}
+
+// Returning YES here makes the setShowsToolbarButton method work even though
+// the window doesn't contain an NSToolbar.
+- (BOOL)_hasToolbar
+{
+ return YES;
+}
+
+// Dispatch a toolbar pill button clicked message to Gecko.
+- (void)_toolbarPillButtonClicked:(id)sender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+
+ if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
+ WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
+ nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
+ if (!geckoWindow)
+ return;
+
+ nsIWidgetListener* listener = geckoWindow->GetWidgetListener();
+ if (listener)
+ listener->OSToolbarButtonPressed();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Retain and release "self" to avoid crashes when our widget (and its native
+// window) is closed as a result of processing a key equivalent (e.g.
+// Command+w or Command+q). This workaround is only needed for a window
+// that can become key.
+- (BOOL)performKeyEquivalent:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSWindow *nativeWindow = [self retain];
+ BOOL retval = [super performKeyEquivalent:theEvent];
+ [nativeWindow release];
+ return retval;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (void)sendEvent:(NSEvent *)anEvent
+{
+ NSEventType type = [anEvent type];
+
+ switch (type) {
+ case NSScrollWheel:
+ case NSLeftMouseDown:
+ case NSLeftMouseUp:
+ case NSRightMouseDown:
+ case NSRightMouseUp:
+ case NSOtherMouseDown:
+ case NSOtherMouseUp:
+ case NSMouseMoved:
+ case NSLeftMouseDragged:
+ case NSRightMouseDragged:
+ case NSOtherMouseDragged:
+ {
+ // Drop all mouse events if a modal window has appeared above us.
+ // This helps make us behave as if the OS were running a "real" modal
+ // event loop.
+ id delegate = [self delegate];
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
+ nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
+ if (widget) {
+ if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
+ return;
+ if (widget->HasModalDescendents())
+ return;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ [super sendEvent:anEvent];
+}
+
+@end
+
+// Custom NSColor subclass where most of the work takes place for drawing in
+// the titlebar area. Not used in drawsContentsIntoWindowFrame mode.
+@implementation TitlebarAndBackgroundColor
+
+- (id)initWithWindow:(ToolbarWindow*)aWindow
+{
+ if ((self = [super init])) {
+ mWindow = aWindow; // weak ref to avoid a cycle
+ }
+ return self;
+}
+
+static void
+DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ CGFloat aUnifiedToolbarHeight, BOOL aIsMain)
+{
+ nsNativeThemeCocoa::DrawNativeTitlebar(aContext, aTitlebarRect, aUnifiedToolbarHeight, aIsMain, NO);
+
+ // The call to CUIDraw doesn't draw the top pixel strip at some window widths.
+ // We don't want to have a flickering transparent line, so we overdraw it.
+ CGContextSetRGBFillColor(aContext, 0.95, 0.95, 0.95, 1);
+ CGContextFillRect(aContext, CGRectMake(0, CGRectGetMaxY(aTitlebarRect) - 1,
+ aTitlebarRect.size.width, 1));
+}
+
+// Pattern draw callback for standard titlebar gradients and solid titlebar colors
+static void
+TitlebarDrawCallback(void* aInfo, CGContextRef aContext)
+{
+ ToolbarWindow *window = (ToolbarWindow*)aInfo;
+ if (![window drawsContentsIntoWindowFrame]) {
+ NSRect titlebarRect = [window titlebarRect];
+ BOOL isMain = [window isMainWindow];
+ NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain];
+ if (!titlebarColor) {
+ // If the titlebar color is nil, draw the default titlebar shading.
+ DrawNativeTitlebar(aContext, NSRectToCGRect(titlebarRect),
+ [window unifiedToolbarHeight], isMain);
+ } else {
+ // If the titlebar color is not nil, just set and draw it normally.
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:aContext flipped:NO]];
+ [titlebarColor set];
+ NSRectFill(titlebarRect);
+ [NSGraphicsContext restoreGraphicsState];
+ }
+ }
+}
+
+- (void)setFill
+{
+ float patternWidth = [mWindow frame].size.width;
+
+ CGPatternCallbacks callbacks = {0, &TitlebarDrawCallback, NULL};
+ CGPatternRef pattern = CGPatternCreate(mWindow, CGRectMake(0.0f, 0.0f, patternWidth, [mWindow frame].size.height),
+ CGAffineTransformIdentity, patternWidth, [mWindow frame].size.height,
+ kCGPatternTilingConstantSpacing, true, &callbacks);
+
+ // Set the pattern as the fill, which is what we were asked to do. All our
+ // drawing will take place in the patternDraw callback.
+ CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
+ CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ CGContextSetFillColorSpace(context, patternSpace);
+ CGColorSpaceRelease(patternSpace);
+ CGFloat component = 1.0f;
+ CGContextSetFillPattern(context, pattern, &component);
+ CGPatternRelease(pattern);
+}
+
+- (void)set
+{
+ [self setFill];
+}
+
+- (NSString*)colorSpaceName
+{
+ return NSDeviceRGBColorSpace;
+}
+
+@end
+
+@implementation PopupWindow
+
+- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
+ backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ mIsContextMenu = false;
+ return [super initWithContentRect:contentRect styleMask:styleMask
+ backing:bufferingType defer:deferCreation];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)isContextMenu
+{
+ return mIsContextMenu;
+}
+
+- (void)setIsContextMenu:(BOOL)flag
+{
+ mIsContextMenu = flag;
+}
+
+- (BOOL)canBecomeMainWindow
+{
+ // This is overriden because the default is 'yes' when a titlebar is present.
+ return NO;
+}
+
+@end
+
+// According to Apple's docs on [NSWindow canBecomeKeyWindow] and [NSWindow
+// canBecomeMainWindow], windows without a title bar or resize bar can't (by
+// default) become key or main. But if a window can't become key, it can't
+// accept keyboard input (bmo bug 393250). And it should also be possible for
+// an otherwise "ordinary" window to become main. We need to override these
+// two methods to make this happen.
+@implementation BorderlessWindow
+
+- (BOOL)canBecomeKeyWindow
+{
+ return YES;
+}
+
+- (void)sendEvent:(NSEvent *)anEvent
+{
+ NSEventType type = [anEvent type];
+
+ switch (type) {
+ case NSScrollWheel:
+ case NSLeftMouseDown:
+ case NSLeftMouseUp:
+ case NSRightMouseDown:
+ case NSRightMouseUp:
+ case NSOtherMouseDown:
+ case NSOtherMouseUp:
+ case NSMouseMoved:
+ case NSLeftMouseDragged:
+ case NSRightMouseDragged:
+ case NSOtherMouseDragged:
+ {
+ // Drop all mouse events if a modal window has appeared above us.
+ // This helps make us behave as if the OS were running a "real" modal
+ // event loop.
+ id delegate = [self delegate];
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
+ nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
+ if (widget) {
+ if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
+ return;
+ if (widget->HasModalDescendents())
+ return;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ [super sendEvent:anEvent];
+}
+
+// Apple's doc on this method says that the NSWindow class's default is not to
+// become main if the window isn't "visible" -- so we should replicate that
+// behavior here. As best I can tell, the [NSWindow isVisible] method is an
+// accurate test of what Apple means by "visibility".
+- (BOOL)canBecomeMainWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (![self isVisible])
+ return NO;
+ return YES;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// Retain and release "self" to avoid crashes when our widget (and its native
+// window) is closed as a result of processing a key equivalent (e.g.
+// Command+w or Command+q). This workaround is only needed for a window
+// that can become key.
+- (BOOL)performKeyEquivalent:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSWindow *nativeWindow = [self retain];
+ BOOL retval = [super performKeyEquivalent:theEvent];
+ [nativeWindow release];
+ return retval;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+@end
diff --git a/widget/cocoa/nsColorPicker.h b/widget/cocoa/nsColorPicker.h
new file mode 100644
index 000000000..4b3e26218
--- /dev/null
+++ b/widget/cocoa/nsColorPicker.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsColorPicker_h_
+#define nsColorPicker_h_
+
+#include "nsIColorPicker.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsIColorPickerShownCallback;
+class mozIDOMWindowProxy;
+@class NSColorPanelWrapper;
+@class NSColor;
+
+class nsColorPicker final : public nsIColorPicker
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ const nsAString& aInitialColor) override;
+ NS_IMETHOD Open(nsIColorPickerShownCallback* aCallback) override;
+
+ // For NSColorPanelWrapper.
+ void Update(NSColor* aColor);
+ // Call this method if you are done with this input, but the color picker needs to
+ // stay open as it will be associated to another input
+ void DoneWithRetarget();
+ // Same as DoneWithRetarget + clean the static instance of sColorPanelWrapper,
+ // as it is not needed anymore for now
+ void Done();
+
+private:
+ ~nsColorPicker();
+
+ static NSColor* GetNSColorFromHexString(const nsAString& aColor);
+ static void GetHexStringFromNSColor(NSColor* aColor, nsAString& aResult);
+
+ static NSColorPanelWrapper* sColorPanelWrapper;
+
+ nsString mTitle;
+ nsString mColor;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+};
+
+#endif // nsColorPicker_h_
diff --git a/widget/cocoa/nsColorPicker.mm b/widget/cocoa/nsColorPicker.mm
new file mode 100644
index 000000000..263ea349b
--- /dev/null
+++ b/widget/cocoa/nsColorPicker.mm
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsColorPicker.h"
+#include "nsCocoaUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+static unsigned int
+HexStrToInt(NSString* str)
+{
+ unsigned int result = 0;
+
+ for (unsigned int i = 0; i < [str length]; ++i) {
+ char c = [str characterAtIndex:i];
+ result *= 16;
+ if (c >= '0' && c <= '9') {
+ result += c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ result += 10 + (c - 'A');
+ } else {
+ result += 10 + (c - 'a');
+ }
+ }
+
+ return result;
+}
+
+@interface NSColorPanelWrapper : NSObject <NSWindowDelegate>
+{
+ NSColorPanel* mColorPanel;
+ nsColorPicker* mColorPicker;
+}
+- (id)initWithPicker:(nsColorPicker*)aPicker;
+- (void)open:(NSColor*)aInitialColor title:(NSString*)aTitle;
+- (void)retarget:(nsColorPicker*)aPicker;
+- (void)colorChanged:(NSColorPanel*)aPanel;
+@end
+
+@implementation NSColorPanelWrapper
+- (id)initWithPicker:(nsColorPicker*)aPicker
+{
+ mColorPicker = aPicker;
+ mColorPanel = [NSColorPanel sharedColorPanel];
+
+ self = [super init];
+ return self;
+}
+
+- (void)open:(NSColor*)aInitialColor title:(NSString*)aTitle
+{
+ [mColorPanel setTarget:self];
+ [mColorPanel setAction:@selector(colorChanged:)];
+ [mColorPanel setDelegate:self];
+ [mColorPanel setTitle:aTitle];
+ [mColorPanel setColor:aInitialColor];
+ [mColorPanel makeKeyAndOrderFront:nil];
+}
+
+- (void)colorChanged:(NSColorPanel*)aPanel
+{
+ mColorPicker->Update([mColorPanel color]);
+}
+
+- (void)windowWillClose:(NSNotification*)aNotification
+{
+ mColorPicker->Done();
+}
+
+- (void)retarget:(nsColorPicker*)aPicker
+{
+ mColorPicker->DoneWithRetarget();
+ mColorPicker = aPicker;
+}
+
+- (void)dealloc
+{
+ [mColorPanel setTarget:nil];
+ [mColorPanel setAction:nil];
+ [mColorPanel setDelegate:nil];
+
+ mColorPanel = nil;
+ mColorPicker = nullptr;
+
+ [super dealloc];
+}
+@end
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+NSColorPanelWrapper* nsColorPicker::sColorPanelWrapper = nullptr;
+
+nsColorPicker::~nsColorPicker()
+{
+}
+
+NS_IMETHODIMP
+nsColorPicker::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ const nsAString& aInitialColor)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Color pickers can only be opened from main thread currently");
+ mTitle = aTitle;
+ mColor = aInitialColor;
+
+ if (sColorPanelWrapper) {
+ // Update current wrapper to target the new input instead
+ [sColorPanelWrapper retarget:this];
+ } else {
+ // Create a brand new color panel wrapper
+ sColorPanelWrapper = [[NSColorPanelWrapper alloc] initWithPicker:this];
+ }
+ return NS_OK;
+}
+
+/* static */ NSColor*
+nsColorPicker::GetNSColorFromHexString(const nsAString& aColor)
+{
+ NSString* str = nsCocoaUtils::ToNSString(aColor);
+
+ double red = HexStrToInt([str substringWithRange:NSMakeRange(1, 2)]) / 255.0;
+ double green = HexStrToInt([str substringWithRange:NSMakeRange(3, 2)]) / 255.0;
+ double blue = HexStrToInt([str substringWithRange:NSMakeRange(5, 2)]) / 255.0;
+
+ return [NSColor colorWithDeviceRed: red green: green blue: blue alpha: 1.0];
+}
+
+/* static */ void
+nsColorPicker::GetHexStringFromNSColor(NSColor* aColor, nsAString& aResult)
+{
+ CGFloat redFloat, greenFloat, blueFloat;
+
+ NSColor* color = aColor;
+ @try {
+ [color getRed:&redFloat green:&greenFloat blue:&blueFloat alpha: nil];
+ } @catch (NSException* e) {
+ color = [color colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
+ [color getRed:&redFloat green:&greenFloat blue:&blueFloat alpha: nil];
+ }
+
+ nsCocoaUtils::GetStringForNSString([NSString stringWithFormat:@"#%02x%02x%02x",
+ (int)(redFloat * 255),
+ (int)(greenFloat * 255),
+ (int)(blueFloat * 255)],
+ aResult);
+}
+
+NS_IMETHODIMP
+nsColorPicker::Open(nsIColorPickerShownCallback* aCallback)
+{
+ MOZ_ASSERT(aCallback);
+ mCallback = aCallback;
+
+ [sColorPanelWrapper open:GetNSColorFromHexString(mColor)
+ title:nsCocoaUtils::ToNSString(mTitle)];
+
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+}
+
+void
+nsColorPicker::Update(NSColor* aColor)
+{
+ GetHexStringFromNSColor(aColor, mColor);
+ mCallback->Update(mColor);
+}
+
+void
+nsColorPicker::DoneWithRetarget()
+{
+ mCallback->Done(EmptyString());
+ mCallback = nullptr;
+ NS_RELEASE_THIS();
+}
+
+void
+nsColorPicker::Done()
+{
+ [sColorPanelWrapper release];
+ sColorPanelWrapper = nullptr;
+ DoneWithRetarget();
+}
diff --git a/widget/cocoa/nsCursorManager.h b/widget/cocoa/nsCursorManager.h
new file mode 100644
index 000000000..6dba8f903
--- /dev/null
+++ b/widget/cocoa/nsCursorManager.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCursorManager_h_
+#define nsCursorManager_h_
+
+#import <Foundation/Foundation.h>
+#include "nsIWidget.h"
+#include "nsMacCursor.h"
+
+/*! @class nsCursorManager
+ @abstract Singleton service provides access to all cursors available in the application.
+ @discussion Use <code>nsCusorManager</code> to set the current cursor using an XP <code>nsCusor</code> enum value.
+ <code>nsCursorManager</code> encapsulates the details of setting different types of cursors, animating
+ cursors and cleaning up cursors when they are no longer in use.
+ */
+@interface nsCursorManager : NSObject
+{
+ @private
+ NSMutableDictionary *mCursors;
+ nsMacCursor *mCurrentMacCursor;
+}
+
+/*! @method setCursor:
+ @abstract Sets the current cursor.
+ @discussion Sets the current cursor to the cursor indicated by the XP cursor constant given as an argument.
+ Resources associated with the previous cursor are cleaned up.
+ @param aCursor the cursor to use
+*/
+- (nsresult) setCursor: (nsCursor) aCursor;
+
+/*! @method setCursorWithImage:hotSpotX:hotSpotY:
+ @abstract Sets the current cursor to a custom image
+ @discussion Sets the current cursor to the cursor given by the aCursorImage argument.
+ Resources associated with the previous cursor are cleaned up.
+ @param aCursorImage the cursor image to use
+ @param aHotSpotX the x coordinate of the cursor's hotspot
+ @param aHotSpotY the y coordinate of the cursor's hotspot
+ @param scaleFactor the scale factor of the target display (2 for a retina display)
+ */
+- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY scaleFactor: (CGFloat) scaleFactor;
+
+
+/*! @method sharedInstance
+ @abstract Get the Singleton instance of the cursor manager.
+ @discussion Use this method to obtain a reference to the cursor manager.
+ @result a reference to the cursor manager
+*/
++ (nsCursorManager *) sharedInstance;
+
+/*! @method dispose
+ @abstract Releases the shared instance of the cursor manager.
+ @discussion Use dispose to clean up the cursor manager and associated cursors.
+*/
++ (void) dispose;
+@end
+
+@interface NSCursor (Undocumented)
+// busyButClickableCursor is an undocumented NSCursor API, but has been in use since
+// at least OS X 10.4 and through 10.9.
++ (NSCursor*)busyButClickableCursor;
+@end
+
+#endif // nsCursorManager_h_
diff --git a/widget/cocoa/nsCursorManager.mm b/widget/cocoa/nsCursorManager.mm
new file mode 100644
index 000000000..c4281a438
--- /dev/null
+++ b/widget/cocoa/nsCursorManager.mm
@@ -0,0 +1,308 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+#include "nsCursorManager.h"
+#include "nsObjCExceptions.h"
+#include <math.h>
+
+static nsCursorManager *gInstance;
+static CGFloat sCursorScaleFactor = 0.0f;
+static imgIContainer *sCursorImgContainer = nullptr;
+static const nsCursor sCustomCursor = eCursorCount;
+
+/*! @category nsCursorManager(PrivateMethods)
+ Private methods for the cursor manager class.
+*/
+@interface nsCursorManager(PrivateMethods)
+/*! @method getCursor:
+ @abstract Get a reference to the native Mac representation of a cursor.
+ @discussion Gets a reference to the Mac native implementation of a cursor.
+ If the cursor has been requested before, it is retreived from the cursor cache,
+ otherwise it is created and cached.
+ @param aCursor the cursor to get
+ @result the Mac native implementation of the cursor
+*/
+- (nsMacCursor *) getCursor: (nsCursor) aCursor;
+
+/*! @method setMacCursor:
+ @abstract Set the current Mac native cursor
+ @discussion Sets the current cursor - this routine is what actually causes the cursor to change.
+ The argument is retained and the old cursor is released.
+ @param aMacCursor the cursor to set
+ @result NS_OK
+ */
+- (nsresult) setMacCursor: (nsMacCursor*) aMacCursor;
+
+/*! @method createCursor:
+ @abstract Create a Mac native representation of a cursor.
+ @discussion Creates a version of the Mac native representation of this cursor
+ @param aCursor the cursor to create
+ @result the Mac native implementation of the cursor
+*/
++ (nsMacCursor *) createCursor: (enum nsCursor) aCursor;
+
+@end
+
+@implementation nsCursorManager
+
++ (nsCursorManager *) sharedInstance
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!gInstance) {
+ gInstance = [[nsCursorManager alloc] init];
+ }
+ return gInstance;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (void) dispose
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [gInstance release];
+ gInstance = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
++ (nsMacCursor *) createCursor: (enum nsCursor) aCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ switch(aCursor)
+ {
+ SEL cursorSelector;
+ case eCursor_standard:
+ return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
+ case eCursor_wait:
+ case eCursor_spinning:
+ {
+ return [nsMacCursor cursorWithCursor:[NSCursor busyButClickableCursor] type:aCursor];
+ }
+ case eCursor_select:
+ return [nsMacCursor cursorWithCursor:[NSCursor IBeamCursor] type:aCursor];
+ case eCursor_hyperlink:
+ return [nsMacCursor cursorWithCursor:[NSCursor pointingHandCursor] type:aCursor];
+ case eCursor_crosshair:
+ return [nsMacCursor cursorWithCursor:[NSCursor crosshairCursor] type:aCursor];
+ case eCursor_move:
+ return [nsMacCursor cursorWithImageNamed:@"move" hotSpot:NSMakePoint(12,12) type:aCursor];
+ case eCursor_help:
+ return [nsMacCursor cursorWithImageNamed:@"help" hotSpot:NSMakePoint(12,12) type:aCursor];
+ case eCursor_copy:
+ cursorSelector = @selector(dragCopyCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ case eCursor_alias:
+ cursorSelector = @selector(dragLinkCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ case eCursor_context_menu:
+ cursorSelector = @selector(contextualMenuCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ case eCursor_cell:
+ return [nsMacCursor cursorWithImageNamed:@"cell" hotSpot:NSMakePoint(12,12) type:aCursor];
+ case eCursor_grab:
+ return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
+ case eCursor_grabbing:
+ return [nsMacCursor cursorWithCursor:[NSCursor closedHandCursor] type:aCursor];
+ case eCursor_zoom_in:
+ return [nsMacCursor cursorWithImageNamed:@"zoomIn" hotSpot:NSMakePoint(10,10) type:aCursor];
+ case eCursor_zoom_out:
+ return [nsMacCursor cursorWithImageNamed:@"zoomOut" hotSpot:NSMakePoint(10,10) type:aCursor];
+ case eCursor_vertical_text:
+ return [nsMacCursor cursorWithImageNamed:@"vtIBeam" hotSpot:NSMakePoint(12,11) type:aCursor];
+ case eCursor_all_scroll:
+ return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
+ case eCursor_not_allowed:
+ case eCursor_no_drop:
+ cursorSelector = @selector(operationNotAllowedCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ // Resize Cursors:
+ // North
+ case eCursor_n_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeUpCursor] type:aCursor];
+ // North East
+ case eCursor_ne_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNE" hotSpot:NSMakePoint(12,11) type:aCursor];
+ // East
+ case eCursor_e_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeRightCursor] type:aCursor];
+ // South East
+ case eCursor_se_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeSE" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // South
+ case eCursor_s_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeDownCursor] type:aCursor];
+ // South West
+ case eCursor_sw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeSW" hotSpot:NSMakePoint(10,12) type:aCursor];
+ // West
+ case eCursor_w_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftCursor] type:aCursor];
+ // North West
+ case eCursor_nw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNW" hotSpot:NSMakePoint(11,11) type:aCursor];
+ // North & South
+ case eCursor_ns_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeUpDownCursor] type:aCursor];
+ // East & West
+ case eCursor_ew_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftRightCursor] type:aCursor];
+ // North East & South West
+ case eCursor_nesw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNESW" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // North West & South East
+ case eCursor_nwse_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNWSE" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // Column Resize
+ case eCursor_col_resize:
+ return [nsMacCursor cursorWithImageNamed:@"colResize" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // Row Resize
+ case eCursor_row_resize:
+ return [nsMacCursor cursorWithImageNamed:@"rowResize" hotSpot:NSMakePoint(12,12) type:aCursor];
+ default:
+ return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id) init
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mCursors = [[NSMutableDictionary alloc] initWithCapacity:25];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (nsresult) setCursor: (enum nsCursor) aCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Some plugins mess with our cursors and set a cursor that even
+ // [NSCursor currentCursor] doesn't know about. In case that happens, just
+ // reset the state.
+ [[NSCursor currentCursor] set];
+
+ nsCursor oldType = [mCurrentMacCursor type];
+ if (oldType != aCursor) {
+ if (aCursor == eCursor_none) {
+ [NSCursor hide];
+ } else if (oldType == eCursor_none) {
+ [NSCursor unhide];
+ }
+ }
+ [self setMacCursor:[self getCursor:aCursor]];
+
+ // if a custom cursor was previously set, release sCursorImgContainer
+ if (oldType == sCustomCursor) {
+ NS_IF_RELEASE(sCursorImgContainer);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+- (nsresult) setMacCursor: (nsMacCursor*) aMacCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mCurrentMacCursor != aMacCursor || ![mCurrentMacCursor isSet]) {
+ [aMacCursor retain];
+ [mCurrentMacCursor unset];
+ [aMacCursor set];
+ [mCurrentMacCursor release];
+ mCurrentMacCursor = aMacCursor;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY scaleFactor: (CGFloat) scaleFactor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+ // As the user moves the mouse, this gets called repeatedly with the same aCursorImage
+ if (sCursorImgContainer == aCursorImage && sCursorScaleFactor == scaleFactor && mCurrentMacCursor) {
+ [self setMacCursor:mCurrentMacCursor];
+ return NS_OK;
+ }
+
+ [[NSCursor currentCursor] set];
+ int32_t width = 0, height = 0;
+ aCursorImage->GetWidth(&width);
+ aCursorImage->GetHeight(&height);
+ // prevent DoS attacks
+ if (width > 128 || height > 128) {
+ return NS_OK;
+ }
+
+ NSImage *cursorImage;
+ nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(aCursorImage, imgIContainer::FRAME_FIRST, &cursorImage, scaleFactor);
+ if (NS_FAILED(rv) || !cursorImage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // if the hotspot is nonsensical, make it 0,0
+ aHotspotX = (aHotspotX > (uint32_t)width - 1) ? 0 : aHotspotX;
+ aHotspotY = (aHotspotY > (uint32_t)height - 1) ? 0 : aHotspotY;
+
+ NSPoint hotSpot = ::NSMakePoint(aHotspotX, aHotspotY);
+ [self setMacCursor:[nsMacCursor cursorWithCursor:[[NSCursor alloc] initWithImage:cursorImage hotSpot:hotSpot] type:sCustomCursor]];
+ [cursorImage release];
+
+ NS_IF_RELEASE(sCursorImgContainer);
+ sCursorImgContainer = aCursorImage;
+ sCursorScaleFactor = scaleFactor;
+ NS_ADDREF(sCursorImgContainer);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+- (nsMacCursor *) getCursor: (enum nsCursor) aCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsMacCursor * result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]];
+ if (!result) {
+ result = [nsCursorManager createCursor:aCursor];
+ [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]];
+ }
+ return result;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void) dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mCurrentMacCursor unset];
+ [mCurrentMacCursor release];
+ [mCursors release];
+ NS_IF_RELEASE(sCursorImgContainer);
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/nsDeviceContextSpecX.h b/widget/cocoa/nsDeviceContextSpecX.h
new file mode 100644
index 000000000..2df52418a
--- /dev/null
+++ b/widget/cocoa/nsDeviceContextSpecX.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDeviceContextSpecX_h_
+#define nsDeviceContextSpecX_h_
+
+#include "nsIDeviceContextSpec.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+class nsDeviceContextSpecX : public nsIDeviceContextSpec
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsDeviceContextSpecX();
+
+ NS_IMETHOD Init(nsIWidget *aWidget, nsIPrintSettings* aPS, bool aIsPrintPreview) override;
+ virtual already_AddRefed<PrintTarget> MakePrintTarget() final;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage) override;
+ NS_IMETHOD EndDocument() override;
+ NS_IMETHOD BeginPage() override;
+ NS_IMETHOD EndPage() override;
+
+ void GetPaperRect(double* aTop, double* aLeft, double* aBottom, double* aRight);
+
+protected:
+ virtual ~nsDeviceContextSpecX();
+
+protected:
+ PMPrintSession mPrintSession; // printing context.
+ PMPageFormat mPageFormat; // page format.
+ PMPrintSettings mPrintSettings; // print settings.
+};
+
+#endif //nsDeviceContextSpecX_h_
diff --git a/widget/cocoa/nsDeviceContextSpecX.mm b/widget/cocoa/nsDeviceContextSpecX.mm
new file mode 100644
index 000000000..d252adef6
--- /dev/null
+++ b/widget/cocoa/nsDeviceContextSpecX.mm
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeviceContextSpecX.h"
+
+#include "mozilla/gfx/PrintTargetCG.h"
+#include "mozilla/RefPtr.h"
+#include "nsCRT.h"
+#include <unistd.h>
+
+#include "nsQueryObject.h"
+#include "nsIServiceManager.h"
+#include "nsPrintSettingsX.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+nsDeviceContextSpecX::nsDeviceContextSpecX()
+: mPrintSession(NULL)
+, mPageFormat(kPMNoPageFormat)
+, mPrintSettings(kPMNoPrintSettings)
+{
+}
+
+nsDeviceContextSpecX::~nsDeviceContextSpecX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mPrintSession)
+ ::PMRelease(mPrintSession);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecX, nsIDeviceContextSpec)
+
+NS_IMETHODIMP nsDeviceContextSpecX::Init(nsIWidget *aWidget,
+ nsIPrintSettings* aPS,
+ bool aIsPrintPreview)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ RefPtr<nsPrintSettingsX> settings(do_QueryObject(aPS));
+ if (!settings)
+ return NS_ERROR_NO_INTERFACE;
+
+ mPrintSession = settings->GetPMPrintSession();
+ ::PMRetain(mPrintSession);
+ mPageFormat = settings->GetPMPageFormat();
+ mPrintSettings = settings->GetPMPrintSettings();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aTitle.IsEmpty()) {
+ CFStringRef cfString =
+ ::CFStringCreateWithCharacters(NULL, reinterpret_cast<const UniChar*>(aTitle.BeginReading()),
+ aTitle.Length());
+ if (cfString) {
+ ::PMPrintSettingsSetJobName(mPrintSettings, cfString);
+ ::CFRelease(cfString);
+ }
+ }
+
+ OSStatus status;
+ status = ::PMSetFirstPage(mPrintSettings, aStartPage, false);
+ NS_ASSERTION(status == noErr, "PMSetFirstPage failed");
+ status = ::PMSetLastPage(mPrintSettings, aEndPage, false);
+ NS_ASSERTION(status == noErr, "PMSetLastPage failed");
+
+ status = ::PMSessionBeginCGDocumentNoDialog(mPrintSession, mPrintSettings, mPageFormat);
+ if (status != noErr)
+ return NS_ERROR_ABORT;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::EndDocument()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ ::PMSessionEndDocumentNoDialog(mPrintSession);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::BeginPage()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMSessionError(mPrintSession);
+ OSStatus status = ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, NULL);
+ if (status != noErr) return NS_ERROR_ABORT;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::EndPage()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ OSStatus status = ::PMSessionEndPageNoDialog(mPrintSession);
+ if (status != noErr) return NS_ERROR_ABORT;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsDeviceContextSpecX::GetPaperRect(double* aTop, double* aLeft, double* aBottom, double* aRight)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ PMRect paperRect;
+ ::PMGetAdjustedPaperRect(mPageFormat, &paperRect);
+
+ *aTop = paperRect.top, *aLeft = paperRect.left;
+ *aBottom = paperRect.bottom, *aRight = paperRect.right;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecX::MakePrintTarget()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ double top, left, bottom, right;
+ GetPaperRect(&top, &left, &bottom, &right);
+ const double width = right - left;
+ const double height = bottom - top;
+ IntSize size = IntSize::Floor(width, height);
+
+ CGContextRef context;
+ ::PMSessionGetCGGraphicsContext(mPrintSession, &context);
+
+ if (context) {
+ // Initially, origin is at bottom-left corner of the paper.
+ // Here, we translate it to top-left corner of the paper.
+ CGContextTranslateCTM(context, 0, height);
+ CGContextScaleCTM(context, 1.0, -1.0);
+ return PrintTargetCG::CreateOrNull(context, size);
+ }
+
+ // Apparently we do need this branch - bug 368933.
+ return PrintTargetCG::CreateOrNull(size, SurfaceFormat::A8R8G8B8_UINT32);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
diff --git a/widget/cocoa/nsDragService.h b/widget/cocoa/nsDragService.h
new file mode 100644
index 000000000..ea6702812
--- /dev/null
+++ b/widget/cocoa/nsDragService.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDragService_h_
+#define nsDragService_h_
+
+#include "nsBaseDragService.h"
+
+#include <Cocoa/Cocoa.h>
+
+extern NSString* const kWildcardPboardType;
+extern NSString* const kCorePboardType_url;
+extern NSString* const kCorePboardType_urld;
+extern NSString* const kCorePboardType_urln;
+extern NSString* const kCustomTypesPboardType;
+
+class nsDragService : public nsBaseDragService
+{
+public:
+ nsDragService();
+
+ // nsBaseDragService
+ virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType);
+ // nsIDragService
+ NS_IMETHOD EndDragSession(bool aDoneDrag);
+
+ // nsIDragSession
+ NS_IMETHOD GetData(nsITransferable * aTransferable, uint32_t aItemIndex);
+ NS_IMETHOD IsDataFlavorSupported(const char *aDataFlavor, bool *_retval);
+ NS_IMETHOD GetNumDropItems(uint32_t * aNumItems);
+
+protected:
+ virtual ~nsDragService();
+
+private:
+
+ NSImage* ConstructDragImage(nsIDOMNode* aDOMNode,
+ mozilla::LayoutDeviceIntRect* aDragRect,
+ nsIScriptableRegion* aRegion);
+ bool IsValidType(NSString* availableType, bool allowFileURL);
+ NSString* GetStringForType(NSPasteboardItem* item, const NSString* type,
+ bool allowFileURL = false);
+ NSString* GetTitleForURL(NSPasteboardItem* item);
+ NSString* GetFilePath(NSPasteboardItem* item);
+
+ nsCOMPtr<nsIArray> mDataItems; // only valid for a drag started within gecko
+ NSView* mNativeDragView;
+ NSEvent* mNativeDragEvent;
+};
+
+#endif // nsDragService_h_
diff --git a/widget/cocoa/nsDragService.mm b/widget/cocoa/nsDragService.mm
new file mode 100644
index 000000000..b92db1b2a
--- /dev/null
+++ b/widget/cocoa/nsDragService.mm
@@ -0,0 +1,669 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include "nsArrayUtils.h"
+#include "nsDragService.h"
+#include "nsArrayUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsITransferable.h"
+#include "nsString.h"
+#include "nsClipboard.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCOMPtr.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsLinebreakConverter.h"
+#include "nsIMacUtils.h"
+#include "nsIDOMNode.h"
+#include "nsRect.h"
+#include "nsPoint.h"
+#include "nsIIOService.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsView.h"
+#include "gfxContext.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatform.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+extern PRLogModuleInfo* sCocoaLog;
+
+extern void EnsureLogInitialized();
+
+extern NSPasteboard* globalDragPboard;
+extern NSView* gLastDragView;
+extern NSEvent* gLastDragMouseDownEvent;
+extern bool gUserCancelledDrag;
+
+// This global makes the transferable array available to Cocoa's promised
+// file destination callback.
+nsIArray *gDraggedTransferables = nullptr;
+
+NSString* const kWildcardPboardType = @"MozillaWildcard";
+NSString* const kCorePboardType_url = @"CorePasteboardFlavorType 0x75726C20"; // 'url ' url
+NSString* const kCorePboardType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' desc
+NSString* const kCorePboardType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title
+NSString* const kUTTypeURLName = @"public.url-name";
+NSString* const kCustomTypesPboardType = @"org.mozilla.custom-clipdata";
+
+nsDragService::nsDragService()
+{
+ mNativeDragView = nil;
+ mNativeDragEvent = nil;
+
+ EnsureLogInitialized();
+}
+
+nsDragService::~nsDragService()
+{
+}
+
+static nsresult SetUpDragClipboard(nsIArray* aTransferableArray)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aTransferableArray)
+ return NS_ERROR_FAILURE;
+
+ uint32_t count = 0;
+ aTransferableArray->GetLength(&count);
+
+ NSPasteboard* dragPBoard = [NSPasteboard pasteboardWithName:NSDragPboard];
+
+ for (uint32_t j = 0; j < count; j++) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(aTransferableArray, j);
+ if (!currentTransferable)
+ return NS_ERROR_FAILURE;
+
+ // Transform the transferable to an NSDictionary
+ NSDictionary* pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(currentTransferable);
+ if (!pasteboardOutputDict)
+ return NS_ERROR_FAILURE;
+
+ // write everything out to the general pasteboard
+ unsigned int typeCount = [pasteboardOutputDict count];
+ NSMutableArray* types = [NSMutableArray arrayWithCapacity:typeCount + 1];
+ [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
+ // Gecko is initiating this drag so we always want its own views to consider
+ // it. Add our wildcard type to the pasteboard to accomplish this.
+ [types addObject:kWildcardPboardType]; // we don't increase the count for the loop below on purpose
+ [dragPBoard declareTypes:types owner:nil];
+ for (unsigned int k = 0; k < typeCount; k++) {
+ NSString* currentKey = [types objectAtIndex:k];
+ id currentValue = [pasteboardOutputDict valueForKey:currentKey];
+ if (currentKey == NSStringPboardType ||
+ currentKey == kCorePboardType_url ||
+ currentKey == kCorePboardType_urld ||
+ currentKey == kCorePboardType_urln) {
+ [dragPBoard setString:currentValue forType:currentKey];
+ }
+ else if (currentKey == NSHTMLPboardType) {
+ [dragPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
+ forType:currentKey];
+ }
+ else if (currentKey == NSTIFFPboardType ||
+ currentKey == kCustomTypesPboardType) {
+ [dragPBoard setData:currentValue forType:currentKey];
+ }
+ else if (currentKey == NSFilesPromisePboardType ||
+ currentKey == NSFilenamesPboardType) {
+ [dragPBoard setPropertyList:currentValue forType:currentKey];
+ }
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NSImage*
+nsDragService::ConstructDragImage(nsIDOMNode* aDOMNode,
+ LayoutDeviceIntRect* aDragRect,
+ nsIScriptableRegion* aRegion)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
+
+ RefPtr<SourceSurface> surface;
+ nsPresContext* pc;
+ nsresult rv = DrawDrag(aDOMNode, aRegion, mScreenPosition,
+ aDragRect, &surface, &pc);
+ if (pc && (!aDragRect->width || !aDragRect->height)) {
+ // just use some suitable defaults
+ int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
+ aDragRect->SetRect(pc->CSSPixelsToDevPixels(mScreenPosition.x),
+ pc->CSSPixelsToDevPixels(mScreenPosition.y), size, size);
+ }
+
+ if (NS_FAILED(rv) || !surface)
+ return nil;
+
+ uint32_t width = aDragRect->width;
+ uint32_t height = aDragRect->height;
+
+ RefPtr<DataSourceSurface> dataSurface =
+ Factory::CreateDataSourceSurface(IntSize(width, height),
+ SurfaceFormat::B8G8R8A8);
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
+ return nil;
+ }
+
+ RefPtr<DrawTarget> dt =
+ Factory::CreateDrawTargetForData(BackendType::CAIRO,
+ map.mData,
+ dataSurface->GetSize(),
+ map.mStride,
+ dataSurface->GetFormat());
+ if (!dt) {
+ dataSurface->Unmap();
+ return nil;
+ }
+
+ dt->FillRect(gfx::Rect(0, 0, width, height),
+ SurfacePattern(surface, ExtendMode::CLAMP),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+
+ NSBitmapImageRep* imageRep =
+ [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
+ pixelsWide:width
+ pixelsHigh:height
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:width * 4
+ bitsPerPixel:32];
+
+ uint8_t* dest = [imageRep bitmapData];
+ for (uint32_t i = 0; i < height; ++i) {
+ uint8_t* src = map.mData + i * map.mStride;
+ for (uint32_t j = 0; j < width; ++j) {
+ // Reduce transparency overall by multipying by a factor. Remember, Alpha
+ // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
+#ifdef IS_BIG_ENDIAN
+ dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
+ dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
+ dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
+ dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
+#else
+ dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
+ dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
+ dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
+ dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
+#endif
+ src += 4;
+ dest += 4;
+ }
+ }
+ dataSurface->Unmap();
+
+ NSImage* image =
+ [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor,
+ height / scaleFactor)];
+ [image addRepresentation:imageRep];
+ [imageRep release];
+
+ return [image autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool
+nsDragService::IsValidType(NSString* availableType, bool allowFileURL)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Prevent exposing fileURL for non-fileURL type.
+ // We need URL provided by dropped webloc file, but don't need file's URL.
+ // kUTTypeFileURL is returned by [NSPasteboard availableTypeFromArray:] for
+ // kUTTypeURL, since it conforms to kUTTypeURL.
+ if (!allowFileURL && [availableType isEqualToString:(id)kUTTypeFileURL]) {
+ return false;
+ }
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK(false);
+}
+
+NSString*
+nsDragService::GetStringForType(NSPasteboardItem* item, const NSString* type,
+ bool allowFileURL)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* availableType = [item availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
+ if (availableType && IsValidType(availableType, allowFileURL)) {
+ return [item stringForType:(id)availableType];
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+NSString*
+nsDragService::GetTitleForURL(NSPasteboardItem* item)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* name = GetStringForType(item, (const NSString*)kUTTypeURLName);
+ if (name) {
+ return name;
+ }
+
+ NSString* filePath = GetFilePath(item);
+ if (filePath) {
+ return [filePath lastPathComponent];
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+NSString*
+nsDragService::GetFilePath(NSPasteboardItem* item)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* urlString = GetStringForType(item, (const NSString*)kUTTypeFileURL, true);
+ if (urlString) {
+ NSURL* url = [NSURL URLWithString:urlString];
+ if (url) {
+ return [url path];
+ }
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// We can only invoke NSView's 'dragImage:at:offset:event:pasteboard:source:slideBack:' from
+// within NSView's 'mouseDown:' or 'mouseDragged:'. Luckily 'mouseDragged' is always on the
+// stack when InvokeDragSession gets called.
+nsresult
+nsDragService::InvokeDragSessionImpl(nsIArray* aTransferableArray,
+ nsIScriptableRegion* aDragRgn,
+ uint32_t aActionType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mDataItems = aTransferableArray;
+
+ // put data on the clipboard
+ if (NS_FAILED(SetUpDragClipboard(aTransferableArray)))
+ return NS_ERROR_FAILURE;
+
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
+
+ LayoutDeviceIntRect dragRect(0, 0, 20, 20);
+ NSImage* image = ConstructDragImage(mSourceNode, &dragRect, aDragRgn);
+ if (!image) {
+ // if no image was returned, just draw a rectangle
+ NSSize size;
+ size.width = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.width, scaleFactor);
+ size.height = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.height, scaleFactor);
+ image = [[NSImage alloc] initWithSize:size];
+ [image lockFocus];
+ [[NSColor grayColor] set];
+ NSBezierPath* path = [NSBezierPath bezierPath];
+ [path setLineWidth:2.0];
+ [path moveToPoint:NSMakePoint(0, 0)];
+ [path lineToPoint:NSMakePoint(0, size.height)];
+ [path lineToPoint:NSMakePoint(size.width, size.height)];
+ [path lineToPoint:NSMakePoint(size.width, 0)];
+ [path lineToPoint:NSMakePoint(0, 0)];
+ [path stroke];
+ [image unlockFocus];
+ }
+
+ LayoutDeviceIntPoint pt(dragRect.x, dragRect.YMost());
+ NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
+ point.y = nsCocoaUtils::FlippedScreenY(point.y);
+
+ point = nsCocoaUtils::ConvertPointFromScreen([gLastDragView window], point);
+ NSPoint localPoint = [gLastDragView convertPoint:point fromView:nil];
+
+ // Save the transferables away in case a promised file callback is invoked.
+ gDraggedTransferables = aTransferableArray;
+
+ nsBaseDragService::StartDragSession();
+ nsBaseDragService::OpenDragPopup();
+
+ // We need to retain the view and the event during the drag in case either gets destroyed.
+ mNativeDragView = [gLastDragView retain];
+ mNativeDragEvent = [gLastDragMouseDownEvent retain];
+
+ gUserCancelledDrag = false;
+ [mNativeDragView dragImage:image
+ at:localPoint
+ offset:NSZeroSize
+ event:mNativeDragEvent
+ pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
+ source:mNativeDragView
+ slideBack:YES];
+ gUserCancelledDrag = false;
+
+ if (mDoingDrag)
+ nsBaseDragService::EndDragSession(false);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aTransferable)
+ return NS_ERROR_FAILURE;
+
+ // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t acceptableFlavorCount;
+ flavorList->GetLength(&acceptableFlavorCount);
+
+ // if this drag originated within Mozilla we should just use the cached data from
+ // when the drag started if possible
+ if (mDataItems) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, aItemIndex);
+ if (currentTransferable) {
+ for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ nsCOMPtr<nsISupports> dataSupports;
+ uint32_t dataSize = 0;
+ rv = currentTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
+ if (NS_SUCCEEDED(rv)) {
+ aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
+ return NS_OK; // maybe try to fill in more types? Is there a point?
+ }
+ }
+ }
+ }
+
+ // now check the actual clipboard for data
+ for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get()));
+
+ NSArray* droppedItems = [globalDragPboard pasteboardItems];
+ if (!droppedItems) {
+ continue;
+ }
+
+ uint32_t itemCount = [droppedItems count];
+ if (aItemIndex >= itemCount) {
+ continue;
+ }
+
+ NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex];
+ if (!item) {
+ continue;
+ }
+
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ NSString* filePath = GetFilePath(item);
+ if (!filePath)
+ continue;
+
+ unsigned int stringLength = [filePath length];
+ unsigned int dataLength = (stringLength + 1) * sizeof(char16_t); // in bytes
+ char16_t* clipboardDataPtr = (char16_t*)malloc(dataLength);
+ if (!clipboardDataPtr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ [filePath getCharacters:reinterpret_cast<unichar*>(clipboardDataPtr)];
+ clipboardDataPtr[stringLength] = 0; // null terminate
+
+ nsCOMPtr<nsIFile> file;
+ rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr), true, getter_AddRefs(file));
+ free(clipboardDataPtr);
+ if (NS_FAILED(rv))
+ continue;
+
+ aTransferable->SetTransferData(flavorStr, file, dataLength);
+
+ break;
+ }
+ else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ NSString* availableType = [item availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]];
+ if (!availableType || !IsValidType(availableType, false)) {
+ continue;
+ }
+ NSData *pasteboardData = [item dataForType:availableType];
+ if (!pasteboardData) {
+ continue;
+ }
+
+ unsigned int dataLength = [pasteboardData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ [pasteboardData getBytes:clipboardDataPtr];
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
+ getter_AddRefs(genericDataWrapper));
+
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, sizeof(nsIInputStream*));
+ free(clipboardDataPtr);
+ break;
+ }
+
+ NSString* pString = nil;
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeUTF8PlainText);
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeHTML);
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeURL);
+ if (pString) {
+ NSString* title = GetTitleForURL(item);
+ if (!title) {
+ title = pString;
+ }
+ pString = [NSString stringWithFormat:@"%@\n%@", pString, title];
+ }
+ } else if (flavorStr.EqualsLiteral(kURLDataMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeURL);
+ } else if (flavorStr.EqualsLiteral(kURLDescriptionMime)) {
+ pString = GetTitleForURL(item);
+ } else if (flavorStr.EqualsLiteral(kRTFMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeRTF);
+ }
+ if (pString) {
+ NSData* stringData;
+ if (flavorStr.EqualsLiteral(kRTFMime)) {
+ stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
+ } else {
+ stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
+ }
+ unsigned int dataLength = [stringData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ [stringData getBytes:clipboardDataPtr];
+
+ // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
+ int32_t signedDataLength = dataLength;
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
+ dataLength = signedDataLength;
+
+ // skip BOM (Byte Order Mark to distinguish little or big endian)
+ char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
+ if ((dataLength > 2) &&
+ ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
+ (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
+ dataLength -= sizeof(char16_t);
+ clipboardDataPtrNoBOM += 1;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
+ free(clipboardDataPtr);
+ break;
+ }
+
+ // We have never supported this on Mac OS X, we should someday. Normally dragging images
+ // in is accomplished with a file path drag instead of the image data itself.
+ /*
+ if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
+
+ }
+ */
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *_retval = false;
+
+ if (!globalDragPboard)
+ return NS_ERROR_FAILURE;
+
+ nsDependentCString dataFlavor(aDataFlavor);
+
+ // first see if we have data for this in our cached transferable
+ if (mDataItems) {
+ uint32_t dataItemsCount;
+ mDataItems->GetLength(&dataItemsCount);
+ for (unsigned int i = 0; i < dataItemsCount; i++) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, i);
+ if (!currentTransferable)
+ continue;
+
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = currentTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ continue;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+ for (uint32_t j = 0; j < flavorCount; j++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, j);
+ if (!currentFlavor)
+ continue;
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ if (dataFlavor.Equals(flavorStr)) {
+ *_retval = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ const NSString* type = nil;
+ bool allowFileURL = false;
+ if (dataFlavor.EqualsLiteral(kFileMime)) {
+ type = (const NSString*)kUTTypeFileURL;
+ allowFileURL = true;
+ } else if (dataFlavor.EqualsLiteral(kUnicodeMime)) {
+ type = (const NSString*)kUTTypeUTF8PlainText;
+ } else if (dataFlavor.EqualsLiteral(kHTMLMime)) {
+ type = (const NSString*)kUTTypeHTML;
+ } else if (dataFlavor.EqualsLiteral(kURLMime) ||
+ dataFlavor.EqualsLiteral(kURLDataMime)) {
+ type = (const NSString*)kUTTypeURL;
+ } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) {
+ type = (const NSString*)kUTTypeURLName;
+ } else if (dataFlavor.EqualsLiteral(kRTFMime)) {
+ type = (const NSString*)kUTTypeRTF;
+ } else if (dataFlavor.EqualsLiteral(kCustomTypesMime)) {
+ type = (const NSString*)kCustomTypesPboardType;
+ }
+
+ NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
+ if (availableType && IsValidType(availableType, allowFileURL)) {
+ *_retval = true;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsDragService::GetNumDropItems(uint32_t* aNumItems)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *aNumItems = 0;
+
+ // first check to see if we have a number of items cached
+ if (mDataItems) {
+ mDataItems->GetLength(aNumItems);
+ return NS_OK;
+ }
+
+ NSArray* droppedItems = [globalDragPboard pasteboardItems];
+ if (droppedItems) {
+ *aNumItems = [droppedItems count];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mNativeDragView) {
+ [mNativeDragView release];
+ mNativeDragView = nil;
+ }
+ if (mNativeDragEvent) {
+ [mNativeDragEvent release];
+ mNativeDragEvent = nil;
+ }
+
+ mUserCancelled = gUserCancelledDrag;
+
+ nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag);
+ mDataItems = nullptr;
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsFilePicker.h b/widget/cocoa/nsFilePicker.h
new file mode 100644
index 000000000..1aeb22cc1
--- /dev/null
+++ b/widget/cocoa/nsFilePicker.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFilePicker_h_
+#define nsFilePicker_h_
+
+#include "nsBaseFilePicker.h"
+#include "nsString.h"
+#include "nsIFileChannel.h"
+#include "nsIFile.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+
+class nsILocalFileMac;
+@class NSArray;
+
+class nsFilePicker : public nsBaseFilePicker
+{
+public:
+ nsFilePicker();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIFilePicker (less what's in nsBaseFilePicker)
+ NS_IMETHOD GetDefaultString(nsAString& aDefaultString) override;
+ NS_IMETHOD SetDefaultString(const nsAString& aDefaultString) override;
+ NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension) override;
+ NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension) override;
+ NS_IMETHOD GetFile(nsIFile * *aFile) override;
+ NS_IMETHOD GetFileURL(nsIURI * *aFileURL) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles) override;
+ NS_IMETHOD Show(int16_t *_retval) override;
+ NS_IMETHOD AppendFilter(const nsAString& aTitle, const nsAString& aFilter) override;
+
+ /**
+ * Returns the current filter list in the format used by Cocoa's NSSavePanel
+ * and NSOpenPanel.
+ * Returns nil if no filter currently apply.
+ */
+ NSArray* GetFilterList();
+
+protected:
+ virtual ~nsFilePicker();
+
+ virtual void InitNative(nsIWidget *aParent, const nsAString& aTitle) override;
+
+ // actual implementations of get/put dialogs using NSOpenPanel & NSSavePanel
+ // aFile is an existing but unspecified file. These functions must specify it.
+ //
+ // will return |returnCancel| or |returnOK| as result.
+ int16_t GetLocalFiles(const nsString& inTitle, bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles);
+ int16_t GetLocalFolder(const nsString& inTitle, nsIFile** outFile);
+ int16_t PutLocalFile(const nsString& inTitle, const nsString& inDefaultName, nsIFile** outFile);
+
+ void SetDialogTitle(const nsString& inTitle, id aDialog);
+ NSString *PanelDefaultDirectory();
+ NSView* GetAccessoryView();
+
+ nsString mTitle;
+ nsCOMArray<nsIFile> mFiles;
+ nsString mDefault;
+
+ nsTArray<nsString> mFilters;
+ nsTArray<nsString> mTitles;
+
+ int32_t mSelectedTypeIndex;
+};
+
+#endif // nsFilePicker_h_
diff --git a/widget/cocoa/nsFilePicker.mm b/widget/cocoa/nsFilePicker.mm
new file mode 100644
index 000000000..5213dee24
--- /dev/null
+++ b/widget/cocoa/nsFilePicker.mm
@@ -0,0 +1,676 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsFilePicker.h"
+#include "nsCOMPtr.h"
+#include "nsReadableUtils.h"
+#include "nsNetUtil.h"
+#include "nsIComponentManager.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsIURL.h"
+#include "nsArrayEnumerator.h"
+#include "nsIStringBundle.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/Preferences.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+const float kAccessoryViewPadding = 5;
+const int kSaveTypeControlTag = 1;
+
+static bool gCallSecretHiddenFileAPI = false;
+const char kShowHiddenFilesPref[] = "filepicker.showHiddenFiles";
+
+/**
+ * This class is an observer of NSPopUpButton selection change.
+ */
+@interface NSPopUpButtonObserver : NSObject
+{
+ NSPopUpButton* mPopUpButton;
+ NSOpenPanel* mOpenPanel;
+ nsFilePicker* mFilePicker;
+}
+- (void) setPopUpButton:(NSPopUpButton*)aPopUpButton;
+- (void) setOpenPanel:(NSOpenPanel*)aOpenPanel;
+- (void) setFilePicker:(nsFilePicker*)aFilePicker;
+- (void) menuChangedItem:(NSNotification*)aSender;
+@end
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+// We never want to call the secret show hidden files API unless the pref
+// has been set. Once the pref has been set we always need to call it even
+// if it disappears so that we stop showing hidden files if a user deletes
+// the pref. If the secret API was used once and things worked out it should
+// continue working for subsequent calls so the user is at no more risk.
+static void SetShowHiddenFileState(NSSavePanel* panel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ bool show = false;
+ if (NS_SUCCEEDED(Preferences::GetBool(kShowHiddenFilesPref, &show))) {
+ gCallSecretHiddenFileAPI = true;
+ }
+
+ if (gCallSecretHiddenFileAPI) {
+ // invoke a method to get a Cocoa-internal nav view
+ SEL navViewSelector = @selector(_navView);
+ NSMethodSignature* navViewSignature = [panel methodSignatureForSelector:navViewSelector];
+ if (!navViewSignature)
+ return;
+ NSInvocation* navViewInvocation = [NSInvocation invocationWithMethodSignature:navViewSignature];
+ [navViewInvocation setSelector:navViewSelector];
+ [navViewInvocation setTarget:panel];
+ [navViewInvocation invoke];
+
+ // get the returned nav view
+ id navView = nil;
+ [navViewInvocation getReturnValue:&navView];
+
+ // invoke the secret show hidden file state method on the nav view
+ SEL showHiddenFilesSelector = @selector(setShowsHiddenFiles:);
+ NSMethodSignature* showHiddenFilesSignature = [navView methodSignatureForSelector:showHiddenFilesSelector];
+ if (!showHiddenFilesSignature)
+ return;
+ NSInvocation* showHiddenFilesInvocation = [NSInvocation invocationWithMethodSignature:showHiddenFilesSignature];
+ [showHiddenFilesInvocation setSelector:showHiddenFilesSelector];
+ [showHiddenFilesInvocation setTarget:navView];
+ [showHiddenFilesInvocation setArgument:&show atIndex:2];
+ [showHiddenFilesInvocation invoke];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsFilePicker::nsFilePicker()
+: mSelectedTypeIndex(0)
+{
+}
+
+nsFilePicker::~nsFilePicker()
+{
+}
+
+void
+nsFilePicker::InitNative(nsIWidget *aParent, const nsAString& aTitle)
+{
+ mTitle = aTitle;
+}
+
+NSView* nsFilePicker::GetAccessoryView()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSView* accessoryView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)] autorelease];
+
+ // Set a label's default value.
+ NSString* label = @"Format:";
+
+ // Try to get the localized string.
+ nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = sbs->CreateBundle("chrome://global/locale/filepicker.properties", getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsXPIDLString locaLabel;
+ bundle->GetStringFromName(u"formatLabel", getter_Copies(locaLabel));
+ if (locaLabel) {
+ label = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(locaLabel.get())
+ length:locaLabel.Length()];
+ }
+ }
+
+ // set up label text field
+ NSTextField* textField = [[[NSTextField alloc] init] autorelease];
+ [textField setEditable:NO];
+ [textField setSelectable:NO];
+ [textField setDrawsBackground:NO];
+ [textField setBezeled:NO];
+ [textField setBordered:NO];
+ [textField setFont:[NSFont labelFontOfSize:13.0]];
+ [textField setStringValue:label];
+ [textField setTag:0];
+ [textField sizeToFit];
+
+ // set up popup button
+ NSPopUpButton* popupButton = [[[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 0, 0) pullsDown:NO] autorelease];
+ uint32_t numMenuItems = mTitles.Length();
+ for (uint32_t i = 0; i < numMenuItems; i++) {
+ const nsString& currentTitle = mTitles[i];
+ NSString *titleString;
+ if (currentTitle.IsEmpty()) {
+ const nsString& currentFilter = mFilters[i];
+ titleString = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentFilter.get())
+ length:currentFilter.Length()];
+ }
+ else {
+ titleString = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentTitle.get())
+ length:currentTitle.Length()];
+ }
+ [popupButton addItemWithTitle:titleString];
+ [titleString release];
+ }
+ if (mSelectedTypeIndex >= 0 && (uint32_t)mSelectedTypeIndex < numMenuItems)
+ [popupButton selectItemAtIndex:mSelectedTypeIndex];
+ [popupButton setTag:kSaveTypeControlTag];
+ [popupButton sizeToFit]; // we have to do sizeToFit to get the height calculated for us
+ // This is just a default width that works well, doesn't truncate the vast majority of
+ // things that might end up in the menu.
+ [popupButton setFrameSize:NSMakeSize(180, [popupButton frame].size.height)];
+
+ // position everything based on control sizes with kAccessoryViewPadding pix padding
+ // on each side kAccessoryViewPadding pix horizontal padding between controls
+ float greatestHeight = [textField frame].size.height;
+ if ([popupButton frame].size.height > greatestHeight)
+ greatestHeight = [popupButton frame].size.height;
+ float totalViewHeight = greatestHeight + kAccessoryViewPadding * 2;
+ float totalViewWidth = [textField frame].size.width + [popupButton frame].size.width + kAccessoryViewPadding * 3;
+ [accessoryView setFrameSize:NSMakeSize(totalViewWidth, totalViewHeight)];
+
+ float textFieldOriginY = ((greatestHeight - [textField frame].size.height) / 2 + 1) + kAccessoryViewPadding;
+ [textField setFrameOrigin:NSMakePoint(kAccessoryViewPadding, textFieldOriginY)];
+
+ float popupOriginX = [textField frame].size.width + kAccessoryViewPadding * 2;
+ float popupOriginY = ((greatestHeight - [popupButton frame].size.height) / 2) + kAccessoryViewPadding;
+ [popupButton setFrameOrigin:NSMakePoint(popupOriginX, popupOriginY)];
+
+ [accessoryView addSubview:textField];
+ [accessoryView addSubview:popupButton];
+ return accessoryView;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Display the file dialog
+NS_IMETHODIMP nsFilePicker::Show(int16_t *retval)
+{
+ NS_ENSURE_ARG_POINTER(retval);
+
+ *retval = returnCancel;
+
+ int16_t userClicksOK = returnCancel;
+
+// Random questions from DHH:
+//
+// Why do we pass mTitle, mDefault to the functions? Can GetLocalFile. PutLocalFile,
+// and GetLocalFolder get called someplace else? It generates a bunch of warnings
+// as it is right now.
+//
+// I think we could easily combine GetLocalFile and GetLocalFolder together, just
+// setting panel pick options based on mMode. I didn't do it here b/c I wanted to
+// make this look as much like Carbon nsFilePicker as possible.
+
+ mFiles.Clear();
+ nsCOMPtr<nsIFile> theFile;
+
+ switch (mMode)
+ {
+ case modeOpen:
+ userClicksOK = GetLocalFiles(mTitle, false, mFiles);
+ break;
+
+ case modeOpenMultiple:
+ userClicksOK = GetLocalFiles(mTitle, true, mFiles);
+ break;
+
+ case modeSave:
+ userClicksOK = PutLocalFile(mTitle, mDefault, getter_AddRefs(theFile));
+ break;
+
+ case modeGetFolder:
+ userClicksOK = GetLocalFolder(mTitle, getter_AddRefs(theFile));
+ break;
+
+ default:
+ NS_ERROR("Unknown file picker mode");
+ break;
+ }
+
+ if (theFile)
+ mFiles.AppendObject(theFile);
+
+ *retval = userClicksOK;
+ return NS_OK;
+}
+
+static
+void UpdatePanelFileTypes(NSOpenPanel* aPanel, NSArray* aFilters)
+{
+ // If we show all file types, also "expose" bundles' contents.
+ [aPanel setTreatsFilePackagesAsDirectories:!aFilters];
+
+ [aPanel setAllowedFileTypes:aFilters];
+}
+
+@implementation NSPopUpButtonObserver
+- (void) setPopUpButton:(NSPopUpButton*)aPopUpButton
+{
+ mPopUpButton = aPopUpButton;
+}
+
+- (void) setOpenPanel:(NSOpenPanel*)aOpenPanel
+{
+ mOpenPanel = aOpenPanel;
+}
+
+- (void) setFilePicker:(nsFilePicker*)aFilePicker
+{
+ mFilePicker = aFilePicker;
+}
+
+- (void) menuChangedItem:(NSNotification *)aSender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+ int32_t selectedItem = [mPopUpButton indexOfSelectedItem];
+ if (selectedItem < 0) {
+ return;
+ }
+
+ mFilePicker->SetFilterIndex(selectedItem);
+ UpdatePanelFileTypes(mOpenPanel, mFilePicker->GetFilterList());
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN();
+}
+@end
+
+// Use OpenPanel to do a GetFile. Returns |returnOK| if the user presses OK in the dialog.
+int16_t
+nsFilePicker::GetLocalFiles(const nsString& inTitle, bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ int16_t retVal = (int16_t)returnCancel;
+ NSOpenPanel *thePanel = [NSOpenPanel openPanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ // Set the options for how the get file dialog will appear
+ SetDialogTitle(inTitle, thePanel);
+ [thePanel setAllowsMultipleSelection:inAllowMultiple];
+ [thePanel setCanSelectHiddenExtension:YES];
+ [thePanel setCanChooseDirectories:NO];
+ [thePanel setCanChooseFiles:YES];
+ [thePanel setResolvesAliases:YES]; //this is default - probably doesn't need to be set
+
+ // Get filters
+ // filters may be null, if we should allow all file types.
+ NSArray *filters = GetFilterList();
+
+ // set up default directory
+ NSString *theDir = PanelDefaultDirectory();
+
+ // if this is the "Choose application..." dialog, and no other start
+ // dir has been set, then use the Applications folder.
+ if (!theDir) {
+ if (filters && [filters count] == 1 &&
+ [(NSString *)[filters objectAtIndex:0] isEqualToString:@"app"])
+ theDir = @"/Applications/";
+ else
+ theDir = @"";
+ }
+
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+
+ int result;
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ if (mFilters.Length() > 1) {
+ // [NSURL initWithString:] (below) throws an exception if URLString is nil.
+
+ NSPopUpButtonObserver* observer = [[NSPopUpButtonObserver alloc] init];
+
+ NSView* accessoryView = GetAccessoryView();
+ [thePanel setAccessoryView:accessoryView];
+
+ [observer setPopUpButton:[accessoryView viewWithTag:kSaveTypeControlTag]];
+ [observer setOpenPanel:thePanel];
+ [observer setFilePicker:this];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:observer
+ selector:@selector(menuChangedItem:)
+ name:NSMenuWillSendActionNotification object:nil];
+
+ UpdatePanelFileTypes(thePanel, filters);
+ result = [thePanel runModal];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:observer];
+ [observer release];
+ } else {
+ // If we show all file types, also "expose" bundles' contents.
+ if (!filters) {
+ [thePanel setTreatsFilePackagesAsDirectories:YES];
+ }
+ [thePanel setAllowedFileTypes:filters];
+ result = [thePanel runModal];
+ }
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ if (result == NSFileHandlingPanelCancelButton)
+ return retVal;
+
+ // Converts data from a NSArray of NSURL to the returned format.
+ // We should be careful to not call [thePanel URLs] more than once given that
+ // it creates a new array each time.
+ // We are using Fast Enumeration, thus the NSURL array is created once then
+ // iterated.
+ for (NSURL* url in [thePanel URLs]) {
+ if (!url) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)url))) {
+ outFiles.AppendObject(localFile);
+ }
+ }
+
+ if (outFiles.Count() > 0)
+ retVal = returnOK;
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+// Use OpenPanel to do a GetFolder. Returns |returnOK| if the user presses OK in the dialog.
+int16_t
+nsFilePicker::GetLocalFolder(const nsString& inTitle, nsIFile** outFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+ NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
+
+ int16_t retVal = (int16_t)returnCancel;
+ NSOpenPanel *thePanel = [NSOpenPanel openPanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ // Set the options for how the get file dialog will appear
+ SetDialogTitle(inTitle, thePanel);
+ [thePanel setAllowsMultipleSelection:NO]; //this is default -probably doesn't need to be set
+ [thePanel setCanSelectHiddenExtension:YES];
+ [thePanel setCanChooseDirectories:YES];
+ [thePanel setCanChooseFiles:NO];
+ [thePanel setResolvesAliases:YES]; //this is default - probably doesn't need to be set
+ [thePanel setCanCreateDirectories:YES];
+
+ // packages != folders
+ [thePanel setTreatsFilePackagesAsDirectories:NO];
+
+ // set up default directory
+ NSString *theDir = PanelDefaultDirectory();
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int result = [thePanel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ if (result == NSFileHandlingPanelCancelButton)
+ return retVal;
+
+ // get the path for the folder (we allow just 1, so that's all we get)
+ NSURL *theURL = [[thePanel URLs] objectAtIndex:0];
+ if (theURL) {
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)theURL))) {
+ *outFile = localFile;
+ NS_ADDREF(*outFile);
+ retVal = returnOK;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+// Returns |returnOK| if the user presses OK in the dialog.
+int16_t
+nsFilePicker::PutLocalFile(const nsString& inTitle, const nsString& inDefaultName, nsIFile** outFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+ NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
+
+ int16_t retVal = returnCancel;
+ NSSavePanel *thePanel = [NSSavePanel savePanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ SetDialogTitle(inTitle, thePanel);
+
+ // set up accessory view for file format options
+ NSView* accessoryView = GetAccessoryView();
+ [thePanel setAccessoryView:accessoryView];
+
+ // set up default file name
+ NSString* defaultFilename = [NSString stringWithCharacters:(const unichar*)inDefaultName.get() length:inDefaultName.Length()];
+
+ // set up allowed types; this prevents the extension from being selected
+ // use the UTI for the file type to allow alternate extensions (e.g., jpg vs. jpeg)
+ NSString* extension = defaultFilename.pathExtension;
+ if (extension.length != 0) {
+ CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, NULL);
+
+ if (type) {
+ thePanel.allowedFileTypes = @[(NSString*)type];
+ CFRelease(type);
+ } else {
+ // if there's no UTI for the file extension, use the extension itself.
+ thePanel.allowedFileTypes = @[extension];
+ }
+ }
+ // Allow users to change the extension.
+ thePanel.allowsOtherFileTypes = YES;
+
+ // set up default directory
+ NSString *theDir = PanelDefaultDirectory();
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+
+ // load the panel
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ [thePanel setNameFieldStringValue:defaultFilename];
+ int result = [thePanel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+ if (result == NSFileHandlingPanelCancelButton)
+ return retVal;
+
+ // get the save type
+ NSPopUpButton* popupButton = [accessoryView viewWithTag:kSaveTypeControlTag];
+ if (popupButton) {
+ mSelectedTypeIndex = [popupButton indexOfSelectedItem];
+ }
+
+ NSURL* fileURL = [thePanel URL];
+ if (fileURL) {
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)fileURL))) {
+ *outFile = localFile;
+ NS_ADDREF(*outFile);
+ // We tell if we are replacing or not by just looking to see if the file exists.
+ // The user could not have hit OK and not meant to replace the file.
+ if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
+ retVal = returnReplace;
+ else
+ retVal = returnOK;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+NSArray *
+nsFilePicker::GetFilterList()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mFilters.Length()) {
+ return nil;
+ }
+
+ if (mFilters.Length() <= (uint32_t)mSelectedTypeIndex) {
+ NS_WARNING("An out of range index has been selected. Using the first index instead.");
+ mSelectedTypeIndex = 0;
+ }
+
+ const nsString& filterWide = mFilters[mSelectedTypeIndex];
+ if (!filterWide.Length()) {
+ return nil;
+ }
+
+ if (filterWide.Equals(NS_LITERAL_STRING("*"))) {
+ return nil;
+ }
+
+ // The extensions in filterWide are in the format "*.ext" but are expected
+ // in the format "ext" by NSOpenPanel. So we need to filter some characters.
+ NSMutableString* filterString = [[[NSMutableString alloc] initWithString:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(filterWide.get())
+ length:filterWide.Length()]] autorelease];
+ NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@". *"];
+ NSRange range = [filterString rangeOfCharacterFromSet:set];
+ while (range.length) {
+ [filterString replaceCharactersInRange:range withString:@""];
+ range = [filterString rangeOfCharacterFromSet:set];
+ }
+
+ return [[[NSArray alloc] initWithArray:
+ [filterString componentsSeparatedByString:@";"]] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Sets the dialog title to whatever it should be. If it fails, eh,
+// the OS will provide a sensible default.
+void
+nsFilePicker::SetDialogTitle(const nsString& inTitle, id aPanel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [aPanel setTitle:[NSString stringWithCharacters:(const unichar*)inTitle.get() length:inTitle.Length()]];
+
+ if (!mOkButtonLabel.IsEmpty()) {
+ [aPanel setPrompt:[NSString stringWithCharacters:(const unichar*)mOkButtonLabel.get() length:mOkButtonLabel.Length()]];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Converts path from an nsIFile into a NSString path
+// If it fails, returns an empty string.
+NSString *
+nsFilePicker::PanelDefaultDirectory()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString *directory = nil;
+ if (mDisplayDirectory) {
+ nsAutoString pathStr;
+ mDisplayDirectory->GetPath(pathStr);
+ directory = [[[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(pathStr.get())
+ length:pathStr.Length()] autorelease];
+ }
+ return directory;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+NS_IMETHODIMP nsFilePicker::GetFile(nsIFile **aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = nullptr;
+
+ // just return the first file
+ if (mFiles.Count() > 0) {
+ *aFile = mFiles.ObjectAt(0);
+ NS_IF_ADDREF(*aFile);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI **aFileURL)
+{
+ NS_ENSURE_ARG_POINTER(aFileURL);
+ *aFileURL = nullptr;
+
+ if (mFiles.Count() == 0)
+ return NS_OK;
+
+ return NS_NewFileURI(aFileURL, mFiles.ObjectAt(0));
+}
+
+NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
+{
+ return NS_NewArrayEnumerator(aFiles, mFiles);
+}
+
+NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString)
+{
+ mDefault = aString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::GetDefaultString(nsAString& aString)
+{
+ return NS_ERROR_FAILURE;
+}
+
+// The default extension to use for files
+NS_IMETHODIMP nsFilePicker::GetDefaultExtension(nsAString& aExtension)
+{
+ aExtension.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension)
+{
+ return NS_OK;
+}
+
+// Append an entry to the filters array
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
+{
+ // "..apps" has to be translated with native executable extensions.
+ if (aFilter.EqualsLiteral("..apps")) {
+ mFilters.AppendElement(NS_LITERAL_STRING("*.app"));
+ } else {
+ mFilters.AppendElement(aFilter);
+ }
+ mTitles.AppendElement(aTitle);
+
+ return NS_OK;
+}
+
+// Get the filter index - do we still need this?
+NS_IMETHODIMP nsFilePicker::GetFilterIndex(int32_t *aFilterIndex)
+{
+ *aFilterIndex = mSelectedTypeIndex;
+ return NS_OK;
+}
+
+// Set the filter index - do we still need this?
+NS_IMETHODIMP nsFilePicker::SetFilterIndex(int32_t aFilterIndex)
+{
+ mSelectedTypeIndex = aFilterIndex;
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsIdleServiceX.h b/widget/cocoa/nsIdleServiceX.h
new file mode 100644
index 000000000..f0b3d92ed
--- /dev/null
+++ b/widget/cocoa/nsIdleServiceX.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIdleServiceX_h_
+#define nsIdleServiceX_h_
+
+#include "nsIdleService.h"
+
+class nsIdleServiceX : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsIdleServiceX> GetInstance()
+ {
+ RefPtr<nsIdleService> idleService = nsIdleService::GetInstance();
+ if (!idleService) {
+ idleService = new nsIdleServiceX();
+ }
+
+ return idleService.forget().downcast<nsIdleServiceX>();
+ }
+
+protected:
+ nsIdleServiceX() { }
+ virtual ~nsIdleServiceX() { }
+ bool UsePollMode() override;
+};
+
+#endif // nsIdleServiceX_h_
diff --git a/widget/cocoa/nsIdleServiceX.mm b/widget/cocoa/nsIdleServiceX.mm
new file mode 100644
index 000000000..234a15414
--- /dev/null
+++ b/widget/cocoa/nsIdleServiceX.mm
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIdleServiceX.h"
+#include "nsObjCExceptions.h"
+#include "nsIServiceManager.h"
+#import <Foundation/Foundation.h>
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceX, nsIdleService)
+
+bool
+nsIdleServiceX::PollIdleTime(uint32_t *aIdleTime)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ kern_return_t rval;
+ mach_port_t masterPort;
+
+ rval = IOMasterPort(kIOMasterPortDefault, &masterPort);
+ if (rval != KERN_SUCCESS)
+ return false;
+
+ io_iterator_t hidItr;
+ rval = IOServiceGetMatchingServices(masterPort,
+ IOServiceMatching("IOHIDSystem"),
+ &hidItr);
+
+ if (rval != KERN_SUCCESS)
+ return false;
+ NS_ASSERTION(hidItr, "Our iterator is null, but it ought not to be!");
+
+ io_registry_entry_t entry = IOIteratorNext(hidItr);
+ NS_ASSERTION(entry, "Our IO Registry Entry is null, but it shouldn't be!");
+
+ IOObjectRelease(hidItr);
+
+ NSMutableDictionary *hidProps;
+ rval = IORegistryEntryCreateCFProperties(entry,
+ (CFMutableDictionaryRef*)&hidProps,
+ kCFAllocatorDefault, 0);
+ if (rval != KERN_SUCCESS)
+ return false;
+ NS_ASSERTION(hidProps, "HIDProperties is null, but no error was returned.");
+ [hidProps autorelease];
+
+ id idleObj = [hidProps objectForKey:@"HIDIdleTime"];
+ NS_ASSERTION([idleObj isKindOfClass: [NSData class]] ||
+ [idleObj isKindOfClass: [NSNumber class]],
+ "What we got for the idle object is not what we expect!");
+
+ uint64_t time;
+ if ([idleObj isKindOfClass: [NSData class]])
+ [idleObj getBytes: &time];
+ else
+ time = [idleObj unsignedLongLongValue];
+
+ IOObjectRelease(entry);
+
+ // convert to ms from ns
+ time /= 1000000;
+ if (time > UINT32_MAX) // Overflow will occur
+ return false;
+
+ *aIdleTime = static_cast<uint32_t>(time);
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool
+nsIdleServiceX::UsePollMode()
+{
+ return true;
+}
+
diff --git a/widget/cocoa/nsLookAndFeel.h b/widget/cocoa/nsLookAndFeel.h
new file mode 100644
index 000000000..2ad31a2aa
--- /dev/null
+++ b/widget/cocoa/nsLookAndFeel.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLookAndFeel_h_
+#define nsLookAndFeel_h_
+#include "nsXPLookAndFeel.h"
+
+class nsLookAndFeel: public nsXPLookAndFeel {
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel);
+ virtual char16_t GetPasswordCharacterImpl()
+ {
+ // unicode value for the bullet character, used for password textfields.
+ return 0x2022;
+ }
+
+ static bool UseOverlayScrollbars();
+
+ virtual nsTArray<LookAndFeelInt> GetIntCacheImpl();
+ virtual void SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache);
+
+ virtual void RefreshImpl();
+
+protected:
+ static bool SystemWantsOverlayScrollbars();
+ static bool AllowOverlayScrollbarsOverlap();
+
+private:
+ int32_t mUseOverlayScrollbars;
+ bool mUseOverlayScrollbarsCached;
+
+ int32_t mAllowOverlayScrollbarsOverlap;
+ bool mAllowOverlayScrollbarsOverlapCached;
+};
+
+#endif // nsLookAndFeel_h_
diff --git a/widget/cocoa/nsLookAndFeel.mm b/widget/cocoa/nsLookAndFeel.mm
new file mode 100644
index 000000000..cbee90f58
--- /dev/null
+++ b/widget/cocoa/nsLookAndFeel.mm
@@ -0,0 +1,581 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsLookAndFeel.h"
+#include "nsCocoaFeatures.h"
+#include "nsIServiceManager.h"
+#include "nsNativeThemeColors.h"
+#include "nsStyleConsts.h"
+#include "nsCocoaFeatures.h"
+#include "nsIContent.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "gfxPlatformMac.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/widget/WidgetMessageUtils.h"
+
+#import <Cocoa/Cocoa.h>
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+enum {
+ mozNSScrollerStyleLegacy = 0,
+ mozNSScrollerStyleOverlay = 1
+};
+typedef NSInteger mozNSScrollerStyle;
+
+@interface NSScroller(AvailableSinceLion)
++ (mozNSScrollerStyle)preferredScrollerStyle;
+@end
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel()
+ , mUseOverlayScrollbars(-1)
+ , mUseOverlayScrollbarsCached(false)
+ , mAllowOverlayScrollbarsOverlap(-1)
+ , mAllowOverlayScrollbarsOverlapCached(false)
+{
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+}
+
+static nscolor GetColorFromNSColor(NSColor* aColor)
+{
+ NSColor* deviceColor = [aColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ return NS_RGB((unsigned int)([deviceColor redComponent] * 255.0),
+ (unsigned int)([deviceColor greenComponent] * 255.0),
+ (unsigned int)([deviceColor blueComponent] * 255.0));
+}
+
+static nscolor GetColorFromNSColorWithAlpha(NSColor* aColor, float alpha)
+{
+ NSColor* deviceColor = [aColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ return NS_RGBA((unsigned int)([deviceColor redComponent] * 255.0),
+ (unsigned int)([deviceColor greenComponent] * 255.0),
+ (unsigned int)([deviceColor blueComponent] * 255.0),
+ (unsigned int)(alpha * 255.0));
+}
+
+nsresult
+nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case eColorID_WindowBackground:
+ aColor = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_WindowForeground:
+ aColor = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_WidgetBackground:
+ aColor = NS_RGB(0xdd,0xdd,0xdd);
+ break;
+ case eColorID_WidgetForeground:
+ aColor = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_WidgetSelectBackground:
+ aColor = NS_RGB(0x80,0x80,0x80);
+ break;
+ case eColorID_WidgetSelectForeground:
+ aColor = NS_RGB(0x00,0x00,0x80);
+ break;
+ case eColorID_Widget3DHighlight:
+ aColor = NS_RGB(0xa0,0xa0,0xa0);
+ break;
+ case eColorID_Widget3DShadow:
+ aColor = NS_RGB(0x40,0x40,0x40);
+ break;
+ case eColorID_TextBackground:
+ aColor = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_TextForeground:
+ aColor = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_TextSelectBackground:
+ aColor = GetColorFromNSColor([NSColor selectedTextBackgroundColor]);
+ break;
+ case eColorID_highlight: // CSS2 color
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlColor]);
+ break;
+ case eColorID__moz_menuhover:
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlColor]);
+ break;
+ case eColorID_TextSelectForeground:
+ GetColor(eColorID_TextSelectBackground, aColor);
+ if (aColor == 0x000000)
+ aColor = NS_RGB(0xff,0xff,0xff);
+ else
+ aColor = NS_DONT_CHANGE_COLOR;
+ break;
+ case eColorID_highlighttext: // CSS2 color
+ case eColorID__moz_menuhovertext:
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlTextColor]);
+ break;
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aColor = NS_40PERCENT_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ break;
+
+ //
+ // css2 system colors http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ //
+ // It's really hard to effectively map these to the Appearance Manager properly,
+ // since they are modeled word for word after the win32 system colors and don't have any
+ // real counterparts in the Mac world. I'm sure we'll be tweaking these for
+ // years to come.
+ //
+ // Thanks to mpt26@student.canterbury.ac.nz for the hardcoded values that form the defaults
+ // if querying the Appearance Manager fails ;)
+ //
+ case eColorID__moz_mac_buttonactivetext:
+ case eColorID__moz_mac_defaultbuttontext:
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ aColor = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ }
+ // Otherwise fall through and return the regular button text:
+
+ case eColorID_buttontext:
+ case eColorID__moz_buttonhovertext:
+ aColor = GetColorFromNSColor([NSColor controlTextColor]);
+ break;
+ case eColorID_captiontext:
+ case eColorID_menutext:
+ case eColorID_infotext:
+ case eColorID__moz_menubartext:
+ aColor = GetColorFromNSColor([NSColor textColor]);
+ break;
+ case eColorID_windowtext:
+ aColor = GetColorFromNSColor([NSColor windowFrameTextColor]);
+ break;
+ case eColorID_activecaption:
+ aColor = GetColorFromNSColor([NSColor gridColor]);
+ break;
+ case eColorID_activeborder:
+ aColor = GetColorFromNSColor([NSColor keyboardFocusIndicatorColor]);
+ break;
+ case eColorID_appworkspace:
+ aColor = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ case eColorID_background:
+ aColor = NS_RGB(0x63,0x63,0xCE);
+ break;
+ case eColorID_buttonface:
+ case eColorID__moz_buttonhoverface:
+ aColor = NS_RGB(0xF0,0xF0,0xF0);
+ break;
+ case eColorID_buttonhighlight:
+ aColor = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ case eColorID_buttonshadow:
+ aColor = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID_graytext:
+ aColor = GetColorFromNSColor([NSColor disabledControlTextColor]);
+ break;
+ case eColorID_inactiveborder:
+ aColor = GetColorFromNSColor([NSColor controlBackgroundColor]);
+ break;
+ case eColorID_inactivecaption:
+ aColor = GetColorFromNSColor([NSColor controlBackgroundColor]);
+ break;
+ case eColorID_inactivecaptiontext:
+ aColor = NS_RGB(0x45,0x45,0x45);
+ break;
+ case eColorID_scrollbar:
+ aColor = GetColorFromNSColor([NSColor scrollBarColor]);
+ break;
+ case eColorID_threeddarkshadow:
+ aColor = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID_threedshadow:
+ aColor = NS_RGB(0xE0,0xE0,0xE0);
+ break;
+ case eColorID_threedface:
+ aColor = NS_RGB(0xF0,0xF0,0xF0);
+ break;
+ case eColorID_threedhighlight:
+ aColor = GetColorFromNSColor([NSColor highlightColor]);
+ break;
+ case eColorID_threedlightshadow:
+ aColor = NS_RGB(0xDA,0xDA,0xDA);
+ break;
+ case eColorID_menu:
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlTextColor]);
+ break;
+ case eColorID_infobackground:
+ aColor = NS_RGB(0xFF,0xFF,0xC7);
+ break;
+ case eColorID_windowframe:
+ aColor = GetColorFromNSColor([NSColor gridColor]);
+ break;
+ case eColorID_window:
+ case eColorID__moz_field:
+ case eColorID__moz_combobox:
+ aColor = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID__moz_fieldtext:
+ case eColorID__moz_comboboxtext:
+ aColor = GetColorFromNSColor([NSColor controlTextColor]);
+ break;
+ case eColorID__moz_dialog:
+ aColor = GetColorFromNSColor([NSColor controlHighlightColor]);
+ break;
+ case eColorID__moz_dialogtext:
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aColor = GetColorFromNSColor([NSColor controlTextColor]);
+ break;
+ case eColorID__moz_dragtargetzone:
+ aColor = GetColorFromNSColor([NSColor selectedControlColor]);
+ break;
+ case eColorID__moz_mac_chrome_active:
+ case eColorID__moz_mac_chrome_inactive: {
+ int grey = NativeGreyColorAsInt(toolbarFillGrey, (aID == eColorID__moz_mac_chrome_active));
+ aColor = NS_RGB(grey, grey, grey);
+ }
+ break;
+ case eColorID__moz_mac_focusring:
+ aColor = GetColorFromNSColorWithAlpha([NSColor keyboardFocusIndicatorColor], 0.48);
+ break;
+ case eColorID__moz_mac_menushadow:
+ aColor = NS_RGB(0xA3,0xA3,0xA3);
+ break;
+ case eColorID__moz_mac_menutextdisable:
+ aColor = NS_RGB(0x98,0x98,0x98);
+ break;
+ case eColorID__moz_mac_menutextselect:
+ aColor = GetColorFromNSColor([NSColor selectedMenuItemTextColor]);
+ break;
+ case eColorID__moz_mac_disabledtoolbartext:
+ aColor = GetColorFromNSColor([NSColor disabledControlTextColor]);
+ break;
+ case eColorID__moz_mac_menuselect:
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlColor]);
+ break;
+ case eColorID__moz_buttondefault:
+ aColor = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ case eColorID__moz_mac_secondaryhighlight:
+ // For inactive list selection
+ aColor = GetColorFromNSColor([NSColor secondarySelectedControlColor]);
+ break;
+ case eColorID__moz_eventreerow:
+ // Background color of even list rows.
+ aColor = GetColorFromNSColor([[NSColor controlAlternatingRowBackgroundColors] objectAtIndex:0]);
+ break;
+ case eColorID__moz_oddtreerow:
+ // Background color of odd list rows.
+ aColor = GetColorFromNSColor([[NSColor controlAlternatingRowBackgroundColors] objectAtIndex:1]);
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ // There appears to be no available system defined color. HARDCODING to the appropriate color.
+ aColor = NS_RGB(0x14,0x4F,0xAE);
+ break;
+ default:
+ NS_WARNING("Someone asked nsILookAndFeel for a color I don't know about");
+ aColor = NS_RGB(0xff,0xff,0xff);
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return res;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ aResult = 567;
+ break;
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case eIntID_SubmenuDelay:
+ aResult = 200;
+ break;
+ case eIntID_TooltipDelay:
+ aResult = 500;
+ break;
+ case eIntID_MenusCanOverlapOSBar:
+ // xul popups are not allowed to overlap the menubar.
+ aResult = 0;
+ break;
+ case eIntID_SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case eIntID_DragThresholdX:
+ case eIntID_DragThresholdY:
+ aResult = 4;
+ break;
+ case eIntID_ScrollArrowStyle:
+ aResult = eScrollArrow_None;
+ break;
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case eIntID_UseOverlayScrollbars:
+ if (!mUseOverlayScrollbarsCached) {
+ mUseOverlayScrollbars = SystemWantsOverlayScrollbars() ? 1 : 0;
+ mUseOverlayScrollbarsCached = true;
+ }
+ aResult = mUseOverlayScrollbars;
+ break;
+ case eIntID_AllowOverlayScrollbarsOverlap:
+ if (!mAllowOverlayScrollbarsOverlapCached) {
+ mAllowOverlayScrollbarsOverlap = AllowOverlayScrollbarsOverlap() ? 1 : 0;
+ mAllowOverlayScrollbarsOverlapCached = true;
+ }
+ aResult = mAllowOverlayScrollbarsOverlap;
+ break;
+ case eIntID_ScrollbarDisplayOnMouseMove:
+ aResult = 0;
+ break;
+ case eIntID_ScrollbarFadeBeginDelay:
+ aResult = 450;
+ break;
+ case eIntID_ScrollbarFadeDuration:
+ aResult = 200;
+ break;
+ case eIntID_TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case eIntID_TreeScrollDelay:
+ aResult = 100;
+ break;
+ case eIntID_TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case eIntID_DWMCompositor:
+ case eIntID_WindowsClassic:
+ case eIntID_WindowsDefaultTheme:
+ case eIntID_TouchEnabled:
+ case eIntID_WindowsThemeIdentifier:
+ case eIntID_OperatingSystemVersionIdentifier:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_MacGraphiteTheme:
+ aResult = [NSColor currentControlTint] == NSGraphiteControlTint;
+ break;
+ case eIntID_MacYosemiteTheme:
+ aResult = nsCocoaFeatures::OnYosemiteOrLater();
+ break;
+ case eIntID_AlertNotificationOrigin:
+ aResult = NS_ALERT_TOP;
+ break;
+ case eIntID_TabFocusModel:
+ aResult = [NSApp isFullKeyboardAccessEnabled] ?
+ nsIContent::eTabFocus_any : nsIContent::eTabFocus_textControlsMask;
+ break;
+ case eIntID_ScrollToClick:
+ {
+ aResult = [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollerPagingBehavior"];
+ }
+ break;
+ case eIntID_ChosenMenuItemsShouldBlink:
+ aResult = 1;
+ break;
+ case eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DOTTED;
+ break;
+ case eIntID_ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+ case eIntID_SwipeAnimationEnabled:
+ aResult = 0;
+ if ([NSEvent respondsToSelector:@selector(
+ isSwipeTrackingFromScrollEventsEnabled)]) {
+ aResult = [NSEvent isSwipeTrackingFromScrollEventsEnabled] ? 1 : 0;
+ }
+ break;
+ case eIntID_ColorPickerAvailable:
+ aResult = 1;
+ break;
+ case eIntID_ContextMenuOffsetVertical:
+ aResult = -6;
+ break;
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 1;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+bool nsLookAndFeel::UseOverlayScrollbars()
+{
+ return GetInt(eIntID_UseOverlayScrollbars) != 0;
+}
+
+bool nsLookAndFeel::SystemWantsOverlayScrollbars()
+{
+ return ([NSScroller respondsToSelector:@selector(preferredScrollerStyle)] &&
+ [NSScroller preferredScrollerStyle] == mozNSScrollerStyleOverlay);
+}
+
+bool nsLookAndFeel::AllowOverlayScrollbarsOverlap()
+{
+ return (UseOverlayScrollbars());
+}
+
+bool
+nsLookAndFeel::GetFontImpl(FontID aID, nsString &aFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // hack for now
+ if (aID == eFont_Window || aID == eFont_Document) {
+ aFontStyle.style = NS_FONT_STYLE_NORMAL;
+ aFontStyle.weight = NS_FONT_WEIGHT_NORMAL;
+ aFontStyle.stretch = NS_FONT_STRETCH_NORMAL;
+ aFontStyle.size = 14 * aDevPixPerCSSPixel;
+ aFontStyle.systemFont = true;
+
+ aFontName.AssignLiteral("sans-serif");
+ return true;
+ }
+
+ gfxPlatformMac::LookupSystemFont(aID, aFontName, aFontStyle,
+ aDevPixPerCSSPixel);
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+nsTArray<LookAndFeelInt>
+nsLookAndFeel::GetIntCacheImpl()
+{
+ nsTArray<LookAndFeelInt> lookAndFeelIntCache =
+ nsXPLookAndFeel::GetIntCacheImpl();
+
+ LookAndFeelInt useOverlayScrollbars;
+ useOverlayScrollbars.id = eIntID_UseOverlayScrollbars;
+ useOverlayScrollbars.value = GetInt(eIntID_UseOverlayScrollbars);
+ lookAndFeelIntCache.AppendElement(useOverlayScrollbars);
+
+ LookAndFeelInt allowOverlayScrollbarsOverlap;
+ allowOverlayScrollbarsOverlap.id = eIntID_AllowOverlayScrollbarsOverlap;
+ allowOverlayScrollbarsOverlap.value = GetInt(eIntID_AllowOverlayScrollbarsOverlap);
+ lookAndFeelIntCache.AppendElement(allowOverlayScrollbarsOverlap);
+
+ return lookAndFeelIntCache;
+}
+
+void
+nsLookAndFeel::SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache)
+{
+ for (auto entry : aLookAndFeelIntCache) {
+ switch(entry.id) {
+ case eIntID_UseOverlayScrollbars:
+ mUseOverlayScrollbars = entry.value;
+ mUseOverlayScrollbarsCached = true;
+ break;
+ case eIntID_AllowOverlayScrollbarsOverlap:
+ mAllowOverlayScrollbarsOverlap = entry.value;
+ mAllowOverlayScrollbarsOverlapCached = true;
+ break;
+ }
+ }
+}
+
+void
+nsLookAndFeel::RefreshImpl()
+{
+ // We should only clear the cache if we're in the main browser process.
+ // Otherwise, we should wait for the parent to inform us of new values
+ // to cache via LookAndFeel::SetIntCache.
+ if (XRE_IsParentProcess()) {
+ mUseOverlayScrollbarsCached = false;
+ mAllowOverlayScrollbarsOverlapCached = false;
+ }
+}
diff --git a/widget/cocoa/nsMacCursor.h b/widget/cocoa/nsMacCursor.h
new file mode 100644
index 000000000..cf9c84c7e
--- /dev/null
+++ b/widget/cocoa/nsMacCursor.h
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMacCursor_h_
+#define nsMacCursor_h_
+
+#import <Cocoa/Cocoa.h>
+#import "nsIWidget.h"
+
+/*! @class nsMacCursor
+ @abstract Represents a native Mac cursor.
+ @discussion <code>nsMacCursor</code> provides a simple API for creating and working with native Macintosh cursors.
+ Cursors can be created used without needing to be aware of the way different cursors are implemented,
+ in particular the details of managing an animated cursor are hidden.
+*/
+@interface nsMacCursor : NSObject
+{
+ @private
+ NSTimer *mTimer;
+ @protected
+ nsCursor mType;
+ int mFrameCounter;
+}
+
+/*! @method cursorWithCursor:
+ @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>.
+ @discussion Creates a cursor representing the given Cocoa built-in cursor.
+ @param aCursor the <code>NSCursor</code> to use
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> representing the given <code>NSCursor</code>
+ */
++ (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType;
+
+/*! @method cursorWithImageNamed:hotSpot:type:
+ @abstract Create a cursor by specifying the name of an image resource to use for the cursor and a hotspot.
+ @discussion Creates a cursor by loading the named image using the <code>+[NSImage imageNamed:]</code> method.
+ <p>The image must be compatible with any restrictions laid down by <code>NSCursor</code>. These vary
+ by operating system version.</p>
+ <p>The hotspot precisely determines the point where the user clicks when using the cursor.</p>
+ @param aCursor the name of the image to use for the cursor
+ @param aPoint the point within the cursor to use as the hotspot
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> that uses the given image and hotspot
+ */
++ (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType;
+
+/*! @method cursorWithFrames:type:
+ @abstract Create an animated cursor by specifying the frames to use for the animation.
+ @discussion Creates a cursor that will animate by cycling through the given frames. Each element of the array
+ must be an instance of <code>NSCursor</code>
+ @param aCursorFrames an array of <code>NSCursor</code>, representing the frames of an animated cursor, in the
+ order they should be played.
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> that will animate the given cursor frames
+ */
++ (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType;
+
+/*! @method cocoaCursorWithImageNamed:hotSpot:
+ @abstract Create a Cocoa NSCursor object with a Gecko image resource name and a hotspot point.
+ @discussion Create a Cocoa NSCursor object with a Gecko image resource name and a hotspot point.
+ @param imageName the name of the gecko image resource, "tiff" extension is assumed, do not append.
+ @param aPoint the point within the cursor to use as the hotspot
+ @result an autoreleased instance of <code>nsMacCursor</code> that will animate the given cursor frames
+ */
++ (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint;
+
+/*! @method isSet
+ @abstract Determines whether this cursor is currently active.
+ @discussion This can be helpful when the Cocoa NSCursor state can be influenced without going
+ through nsCursorManager.
+ @result whether the cursor is currently set
+ */
+- (BOOL) isSet;
+
+/*! @method set
+ @abstract Set the cursor.
+ @discussion Makes this cursor the current cursor. If the cursor is animated, the animation is started.
+ */
+- (void) set;
+
+/*! @method unset
+ @abstract Unset the cursor. The cursor will return to the default (usually the arrow cursor).
+ @discussion Unsets the cursor. If the cursor is animated, the animation is stopped.
+ */
+- (void) unset;
+
+/*! @method isAnimated
+ @abstract Tests whether this cursor is animated.
+ @discussion Use this method to determine whether a cursor is animated
+ @result YES if the cursor is animated (has more than one frame), NO if it is a simple static cursor.
+ */
+- (BOOL) isAnimated;
+
+/** @method cursorType
+ @abstract Get the cursor type for this cursor
+ @discussion This method returns the <code>nsCursor</code> constant that corresponds to this cursor, which is
+ equivalent to the CSS name for the cursor.
+ @result The nsCursor constant corresponding to this cursor, or nsCursor's 'eCursorCount' if the cursor
+ is a custom cursor loaded from a URI
+ */
+- (nsCursor) type;
+@end
+
+#endif // nsMacCursor_h_
diff --git a/widget/cocoa/nsMacCursor.mm b/widget/cocoa/nsMacCursor.mm
new file mode 100644
index 000000000..4fcdfd3e5
--- /dev/null
+++ b/widget/cocoa/nsMacCursor.mm
@@ -0,0 +1,382 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMacCursor.h"
+#include "nsObjCExceptions.h"
+#include "nsDebug.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+/*! @category nsMacCursor (PrivateMethods)
+ @abstract Private methods internal to the nsMacCursor class.
+ @discussion <code>nsMacCursor</code> is effectively an abstract class. It does not define complete
+ behaviour in and of itself, the subclasses defined in this file provide the useful implementations.
+*/
+@interface nsMacCursor (PrivateMethods)
+
+/*! @method getNextCursorFrame
+ @abstract get the index of the next cursor frame to display.
+ @discussion Increments and returns the frame counter of an animated cursor.
+ @result The index of the next frame to display in the cursor animation
+*/
+- (int) getNextCursorFrame;
+
+/*! @method numFrames
+ @abstract Query the number of frames in this cursor's animation.
+ @discussion Returns the number of frames in this cursor's animation. Static cursors return 1.
+*/
+- (int) numFrames;
+
+/*! @method createTimer
+ @abstract Create a Timer to use to animate the cursor.
+ @discussion Creates an instance of <code>NSTimer</code> which is used to drive the cursor animation.
+ This method should only be called for cursors that are animated.
+*/
+- (void) createTimer;
+
+/*! @method destroyTimer
+ @abstract Destroy any timer instance associated with this cursor.
+ @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor.
+ */
+- (void) destroyTimer;
+/*! @method destroyTimer
+ @abstract Destroy any timer instance associated with this cursor.
+ @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor.
+*/
+
+/*! @method advanceAnimatedCursor:
+ @abstract Method called by animation timer to perform animation.
+ @discussion Called by an animated cursor's associated timer to advance the animation to the next frame.
+ Determines which frame should occur next and sets the cursor to that frame.
+ @param aTimer the timer causing the animation
+*/
+- (void) advanceAnimatedCursor: (NSTimer *) aTimer;
+
+/*! @method setFrame:
+ @abstract Sets the current cursor, using an index to determine which frame in the animation to display.
+ @discussion Sets the current cursor. The frame index determines which frame is shown if the cursor is animated.
+ Frames and numbered from <code>0</code> to <code>-[nsMacCursor numFrames] - 1</code>. A static cursor
+ has a single frame, numbered 0.
+ @param aFrameIndex the index indicating which frame from the animation to display
+*/
+- (void) setFrame: (int) aFrameIndex;
+
+@end
+
+/*! @class nsCocoaCursor
+ @abstract Implementation of <code>nsMacCursor</code> that uses Cocoa <code>NSCursor</code> instances.
+ @discussion Displays a static or animated cursor, using Cocoa <code>NSCursor</code> instances. These can be either
+ built-in <code>NSCursor</code> instances, or custom <code>NSCursor</code>s created from images.
+ When more than one <code>NSCursor</code> is provided, the cursor will use these as animation frames.
+*/
+@interface nsCocoaCursor : nsMacCursor
+{
+ @private
+ NSArray *mFrames;
+ NSCursor *mLastSetCocoaCursor;
+}
+
+/*! @method initWithFrames:
+ @abstract Create an animated cursor by specifying the frames to use for the animation.
+ @discussion Creates a cursor that will animate by cycling through the given frames. Each element of the array
+ must be an instance of <code>NSCursor</code>
+ @param aCursorFrames an array of <code>NSCursor</code>, representing the frames of an animated cursor, in the
+ order they should be played.
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> that will animate the given cursor frames
+ */
+- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType;
+
+/*! @method initWithCursor:
+ @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>.
+ @discussion Creates a cursor representing the given Cocoa built-in cursor.
+ @param aCursor the <code>NSCursor</code> to use
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> representing the given <code>NSCursor</code>
+*/
+- (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType;
+
+/*! @method initWithImageNamed:hotSpot:
+ @abstract Create a cursor by specifying the name of an image resource to use for the cursor and a hotspot.
+ @discussion Creates a cursor by loading the named image using the <code>+[NSImage imageNamed:]</code> method.
+ <p>The image must be compatible with any restrictions laid down by <code>NSCursor</code>. These vary
+ by operating system version.</p>
+ <p>The hotspot precisely determines the point where the user clicks when using the cursor.</p>
+ @param aCursor the name of the image to use for the cursor
+ @param aPoint the point within the cursor to use as the hotspot
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> that uses the given image and hotspot
+*/
+- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType;
+
+@end
+
+@implementation nsMacCursor
+
++ (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsCOMPtr<nsIFile> resDir;
+ nsAutoCString resPath;
+ NSString* pathToImage, *pathToHiDpiImage;
+ NSImage* cursorImage, *hiDpiCursorImage;
+
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
+ if (NS_FAILED(rv))
+ goto INIT_FAILURE;
+ resDir->AppendNative(NS_LITERAL_CSTRING("res"));
+ resDir->AppendNative(NS_LITERAL_CSTRING("cursors"));
+
+ rv = resDir->GetNativePath(resPath);
+ if (NS_FAILED(rv))
+ goto INIT_FAILURE;
+
+ pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()];
+ if (!pathToImage)
+ goto INIT_FAILURE;
+ pathToImage = [pathToImage stringByAppendingPathComponent:imageName];
+ pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"];
+ // Add same extension to both image paths.
+ pathToImage = [pathToImage stringByAppendingPathExtension:@"png"];
+ pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"];
+
+ cursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
+ if (!cursorImage)
+ goto INIT_FAILURE;
+
+ // Note 1: There are a few different ways to get a hidpi image via
+ // initWithContentsOfFile. We let the OS handle this here: when the
+ // file basename ends in "@2x", it will be displayed at native resolution
+ // instead of being pixel-doubled. See bug 784909 comment 7 for alternates ways.
+ //
+ // Note 2: The OS is picky, and will ignore the hidpi representation
+ // unless it is exactly twice the size of the lowdpi image.
+ hiDpiCursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
+ if (hiDpiCursorImage) {
+ NSImageRep *imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
+ [cursorImage addRepresentation: imageRep];
+ }
+ return [[[NSCursor alloc] initWithImage:cursorImage hotSpot:aPoint] autorelease];
+
+INIT_FAILURE:
+ NS_WARNING("Problem getting path to cursor image file!");
+ [self release];
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL) isSet
+{
+ // implemented by subclasses
+ return NO;
+}
+
+- (void) set
+{
+ if ([self isAnimated]) {
+ [self createTimer];
+ }
+ // if the cursor isn't animated or the timer creation fails for any reason...
+ if (!mTimer) {
+ [self setFrame:0];
+ }
+}
+
+- (void) unset
+{
+ [self destroyTimer];
+}
+
+- (BOOL) isAnimated
+{
+ return [self numFrames] > 1;
+}
+
+- (int) numFrames
+{
+ // subclasses need to override this to support animation
+ return 1;
+}
+
+- (int) getNextCursorFrame
+{
+ mFrameCounter = (mFrameCounter + 1) % [self numFrames];
+ return mFrameCounter;
+}
+
+- (void) createTimer
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mTimer) {
+ mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25
+ target:self
+ selector:@selector(advanceAnimatedCursor:)
+ userInfo:nil
+ repeats:YES] retain];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void) destroyTimer
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTimer) {
+ [mTimer invalidate];
+ [mTimer release];
+ mTimer = nil;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void) advanceAnimatedCursor: (NSTimer *) aTimer
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([aTimer isValid]) {
+ [self setFrame:[self getNextCursorFrame]];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void) setFrame: (int) aFrameIndex
+{
+ // subclasses need to do something useful here
+}
+
+- (nsCursor) type {
+ return mType;
+}
+
+- (void) dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self destroyTimer];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+@implementation nsCocoaCursor
+
+- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ self = [super init];
+ NSEnumerator *it = [aCursorFrames objectEnumerator];
+ NSObject *frame = nil;
+ while ((frame = [it nextObject])) {
+ NS_ASSERTION([frame isKindOfClass:[NSCursor class]], "Invalid argument: All frames must be of type NSCursor");
+ }
+ mFrames = [aCursorFrames retain];
+ mFrameCounter = 0;
+ mType = aType;
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSArray *frame = [NSArray arrayWithObjects:aCursor, nil];
+ return [self initWithFrames:frame type:aType];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint] type:aType];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL) isSet
+{
+ return [NSCursor currentCursor] == mLastSetCocoaCursor;
+}
+
+- (void) setFrame: (int) aFrameIndex
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
+ [newCursor set];
+ mLastSetCocoaCursor = newCursor;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (int) numFrames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [mFrames count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (NSString *) description
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [mFrames description];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void) dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mFrames release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/nsMacDockSupport.h b/widget/cocoa/nsMacDockSupport.h
new file mode 100644
index 000000000..a638b89e0
--- /dev/null
+++ b/widget/cocoa/nsMacDockSupport.h
@@ -0,0 +1,41 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMacDockSupport.h"
+#include "nsIStandaloneNativeMenu.h"
+#include "nsITaskbarProgress.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsNativeThemeCocoa.h"
+
+class nsMacDockSupport : public nsIMacDockSupport, public nsITaskbarProgress
+{
+public:
+ nsMacDockSupport();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACDOCKSUPPORT
+ NS_DECL_NSITASKBARPROGRESS
+
+protected:
+ virtual ~nsMacDockSupport();
+
+ nsCOMPtr<nsIStandaloneNativeMenu> mDockMenu;
+ nsString mBadgeText;
+
+ NSImage *mAppIcon, *mProgressBackground;
+
+ HIRect mProgressBounds;
+ nsTaskbarProgressState mProgressState;
+ double mProgressFraction;
+ nsCOMPtr<nsITimer> mProgressTimer;
+ RefPtr<nsNativeThemeCocoa> mTheme;
+
+ static void RedrawIconCallback(nsITimer* aTimer, void* aClosure);
+
+ bool InitProgress();
+ nsresult RedrawIcon();
+};
diff --git a/widget/cocoa/nsMacDockSupport.mm b/widget/cocoa/nsMacDockSupport.mm
new file mode 100644
index 000000000..56b37822b
--- /dev/null
+++ b/widget/cocoa/nsMacDockSupport.mm
@@ -0,0 +1,174 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsMacDockSupport.h"
+#include "nsObjCExceptions.h"
+
+NS_IMPL_ISUPPORTS(nsMacDockSupport, nsIMacDockSupport, nsITaskbarProgress)
+
+nsMacDockSupport::nsMacDockSupport()
+: mAppIcon(nil)
+, mProgressBackground(nil)
+, mProgressState(STATE_NO_PROGRESS)
+, mProgressFraction(0.0)
+{
+ mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+}
+
+nsMacDockSupport::~nsMacDockSupport()
+{
+ if (mAppIcon) {
+ [mAppIcon release];
+ mAppIcon = nil;
+ }
+ if (mProgressBackground) {
+ [mProgressBackground release];
+ mProgressBackground = nil;
+ }
+ if (mProgressTimer) {
+ mProgressTimer->Cancel();
+ mProgressTimer = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::GetDockMenu(nsIStandaloneNativeMenu ** aDockMenu)
+{
+ nsCOMPtr<nsIStandaloneNativeMenu> dockMenu(mDockMenu);
+ dockMenu.forget(aDockMenu);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetDockMenu(nsIStandaloneNativeMenu * aDockMenu)
+{
+ mDockMenu = aDockMenu;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::ActivateApplication(bool aIgnoreOtherApplications)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [[NSApplication sharedApplication] activateIgnoringOtherApps:aIgnoreOtherApplications];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetBadgeText(const nsAString& aBadgeText)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSDockTile *tile = [[NSApplication sharedApplication] dockTile];
+ mBadgeText = aBadgeText;
+ if (aBadgeText.IsEmpty())
+ [tile setBadgeLabel: nil];
+ else
+ [tile setBadgeLabel:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(mBadgeText.get())
+ length:mBadgeText.Length()]];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::GetBadgeText(nsAString& aBadgeText)
+{
+ aBadgeText = mBadgeText;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetProgressState(nsTaskbarProgressState aState,
+ uint64_t aCurrentValue,
+ uint64_t aMaxValue)
+{
+ NS_ENSURE_ARG_RANGE(aState, 0, STATE_PAUSED);
+ if (aState == STATE_NO_PROGRESS || aState == STATE_INDETERMINATE) {
+ NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
+ }
+ if (aCurrentValue > aMaxValue) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mProgressState = aState;
+ if (aMaxValue == 0) {
+ mProgressFraction = 0;
+ } else {
+ mProgressFraction = (double)aCurrentValue / aMaxValue;
+ }
+
+ if (mProgressState == STATE_NORMAL || mProgressState == STATE_INDETERMINATE) {
+ int perSecond = 8; // Empirically determined, see bug 848792
+ mProgressTimer->InitWithFuncCallback(RedrawIconCallback, this, 1000 / perSecond,
+ nsITimer::TYPE_REPEATING_SLACK);
+ return NS_OK;
+ } else {
+ mProgressTimer->Cancel();
+ return RedrawIcon();
+ }
+}
+
+// static
+void nsMacDockSupport::RedrawIconCallback(nsITimer* aTimer, void* aClosure)
+{
+ static_cast<nsMacDockSupport*>(aClosure)->RedrawIcon();
+}
+
+// Return whether to draw progress
+bool nsMacDockSupport::InitProgress()
+{
+ if (mProgressState != STATE_NORMAL && mProgressState != STATE_INDETERMINATE) {
+ return false;
+ }
+
+ if (!mAppIcon) {
+ mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ mAppIcon = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
+ mProgressBackground = [mAppIcon copyWithZone:nil];
+ mTheme = new nsNativeThemeCocoa();
+
+ NSSize sz = [mProgressBackground size];
+ mProgressBounds = CGRectMake(sz.width * 1/32, sz.height * 3/32,
+ sz.width * 30/32, sz.height * 4/32);
+ [mProgressBackground lockFocus];
+ [[NSColor whiteColor] set];
+ NSRectFill(NSRectFromCGRect(mProgressBounds));
+ [mProgressBackground unlockFocus];
+ }
+ return true;
+}
+
+nsresult
+nsMacDockSupport::RedrawIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (InitProgress()) {
+ // TODO: - Implement ERROR and PAUSED states?
+ NSImage *icon = [mProgressBackground copyWithZone:nil];
+ bool isIndeterminate = (mProgressState != STATE_NORMAL);
+
+ [icon lockFocus];
+ CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ mTheme->DrawProgress(ctx, mProgressBounds, isIndeterminate,
+ true, mProgressFraction, 1.0, NULL);
+ [icon unlockFocus];
+ [NSApp setApplicationIconImage:icon];
+ [icon release];
+ } else {
+ [NSApp setApplicationIconImage:mAppIcon];
+ }
+
+ return NS_OK;
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMacWebAppUtils.h b/widget/cocoa/nsMacWebAppUtils.h
new file mode 100644
index 000000000..98ef23561
--- /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 000000000..1b98cef7c
--- /dev/null
+++ b/widget/cocoa/nsMacWebAppUtils.mm
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMacWebAppUtils.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+#include "nsString.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+// Find the path to the app with the given bundleIdentifier, if any.
+// Note that the OS will return the path to the newest binary, if there is more than one.
+// The determination of 'newest' is complex and beyond the scope of this comment.
+
+NS_IMPL_ISUPPORTS(nsMacWebAppUtils, nsIMacWebAppUtils)
+
+NS_IMETHODIMP nsMacWebAppUtils::PathForAppWithIdentifier(const nsAString& bundleIdentifier, nsAString& outPath) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ outPath.Truncate();
+
+ nsAutoreleasePool localPool;
+
+ //note that the result of this expression might be nil, meaning no matching app was found.
+ NSString* temp = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)bundleIdentifier).get())
+ length:((nsString)bundleIdentifier).Length()]];
+
+ if (temp) {
+ // Copy out the resultant absolute path into outPath if non-nil.
+ nsCocoaUtils::GetStringForNSString(temp, outPath);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsMacWebAppUtils::LaunchAppWithIdentifier(const nsAString& bundleIdentifier) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsAutoreleasePool localPool;
+
+ // Note this might return false, meaning the app wasnt launched for some reason.
+ BOOL success = [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)bundleIdentifier).get())
+ length:((nsString)bundleIdentifier).Length()]
+ options: (NSWorkspaceLaunchOptions)0
+ additionalEventParamDescriptor: nil
+ launchIdentifier: NULL];
+
+ return success ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsMacWebAppUtils::TrashApp(const nsAString& path, nsITrashAppCallback* aCallback) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!aCallback)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsITrashAppCallback> callback = aCallback;
+
+ NSString* tempString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)path).get())
+ length:path.Length()];
+
+ [[NSWorkspace sharedWorkspace] recycleURLs: [NSArray arrayWithObject:[NSURL fileURLWithPath:tempString]]
+ completionHandler: ^(NSDictionary *newURLs, NSError *error) {
+ nsresult rv = (error == nil) ? NS_OK : NS_ERROR_FAILURE;
+ callback->TrashAppFinished(rv);
+ }];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMenuBarX.h b/widget/cocoa/nsMenuBarX.h
new file mode 100644
index 000000000..7cbb8ce62
--- /dev/null
+++ b/widget/cocoa/nsMenuBarX.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuBarX_h_
+#define nsMenuBarX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/UniquePtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsChangeObserver.h"
+#include "nsINativeMenuService.h"
+#include "nsString.h"
+
+class nsMenuX;
+class nsIWidget;
+class nsIContent;
+
+// The native menu service for creating native menu bars.
+class nsNativeMenuServiceX : public nsINativeMenuService
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsNativeMenuServiceX() {}
+
+ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode) override;
+
+protected:
+ virtual ~nsNativeMenuServiceX() {}
+};
+
+// Objective-C class used to allow us to intervene with keyboard event handling.
+// We allow mouse actions to work normally.
+@interface GeckoNSMenu : NSMenu
+{
+}
+@end
+
+// Objective-C class used as action target for menu items
+@interface NativeMenuItemTarget : NSObject
+{
+}
+-(IBAction)menuItemHit:(id)sender;
+@end
+
+// Objective-C class used for menu items on the Services menu to allow Gecko
+// to override their standard behavior in order to stop key equivalents from
+// firing in certain instances.
+@interface GeckoServicesNSMenuItem : NSMenuItem
+{
+}
+- (id) target;
+- (SEL) action;
+- (void) _doNothing:(id)sender;
+@end
+
+// Objective-C class used as the Services menu so that Gecko can override the
+// standard behavior of the Services menu in order to stop key equivalents
+// from firing in certain instances.
+@interface GeckoServicesNSMenu : NSMenu
+{
+}
+- (void)addItem:(NSMenuItem *)newItem;
+- (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv;
+- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index;
+- (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index;
+- (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem;
+@end
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuBarX : public nsMenuGroupOwnerX, public nsChangeObserver
+{
+public:
+ nsMenuBarX();
+ virtual ~nsMenuBarX();
+
+ static NativeMenuItemTarget* sNativeEventTarget;
+ static nsMenuBarX* sLastGeckoMenuBarPainted;
+
+ // The following content nodes have been removed from the menu system.
+ // We save them here for use in command handling.
+ nsCOMPtr<nsIContent> mAboutItemContent;
+ nsCOMPtr<nsIContent> mPrefItemContent;
+ nsCOMPtr<nsIContent> mQuitItemContent;
+
+ // nsChangeObserver
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override {return (void*)mNativeMenu;}
+ nsMenuObjectTypeX MenuObjectType() override {return eMenuBarObjectType;}
+
+ // nsMenuBarX
+ nsresult Create(nsIWidget* aParent, nsIContent* aContent);
+ void SetParent(nsIWidget* aParent);
+ uint32_t GetMenuCount();
+ bool MenuContainsAppMenu();
+ nsMenuX* GetMenuAt(uint32_t aIndex);
+ nsMenuX* GetXULHelpMenu();
+ void SetSystemHelpMenu();
+ nsresult Paint();
+ void ForceUpdateNativeMenuAt(const nsAString& indexString);
+ void ForceNativeMenuReload(); // used for testing
+ static char GetLocalizedAccelKey(const char *shortcutID);
+ static void ResetNativeApplicationMenu();
+
+protected:
+ void ConstructNativeMenus();
+ void ConstructFallbackNativeMenus();
+ nsresult InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex);
+ void RemoveMenuAtIndex(uint32_t aIndex);
+ void HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode);
+ void AquifyMenuBar();
+ NSMenuItem* CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
+ int tag, NativeMenuItemTarget* target);
+ nsresult CreateApplicationMenu(nsMenuX* inMenu);
+
+ nsTArray<mozilla::UniquePtr<nsMenuX>> mMenuArray;
+ nsIWidget* mParentWindow; // [weak]
+ GeckoNSMenu* mNativeMenu; // root menu, representing entire menu bar
+};
+
+#endif // nsMenuBarX_h_
diff --git a/widget/cocoa/nsMenuBarX.mm b/widget/cocoa/nsMenuBarX.mm
new file mode 100644
index 000000000..ff25eb81f
--- /dev/null
+++ b/widget/cocoa/nsMenuBarX.mm
@@ -0,0 +1,979 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <objc/objc-runtime.h>
+
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+#include "nsChildView.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsGkAtoms.h"
+#include "nsObjCExceptions.h"
+#include "nsThreadUtils.h"
+
+#include "nsIContent.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIAppStartup.h"
+#include "nsIStringBundle.h"
+#include "nsToolkitCompsCID.h"
+
+NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil;
+nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
+NSMenu* sApplicationMenu = nil;
+BOOL sApplicationMenuIsFallback = NO;
+BOOL gSomeMenuBarPainted = NO;
+
+// We keep references to the first quit and pref item content nodes we find, which
+// will be from the hidden window. We use these when the document for the current
+// window does not have a quit or pref item. We don't need strong refs here because
+// these items are always strong ref'd by their owning menu bar (instance variable).
+static nsIContent* sAboutItemContent = nullptr;
+static nsIContent* sPrefItemContent = nullptr;
+static nsIContent* sQuitItemContent = nullptr;
+
+NS_IMPL_ISUPPORTS(nsNativeMenuServiceX, nsINativeMenuService)
+
+NS_IMETHODIMP nsNativeMenuServiceX::CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!");
+
+ RefPtr<nsMenuBarX> mb = new nsMenuBarX();
+ if (!mb)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return mb->Create(aParent, aMenuBarNode);
+}
+
+nsMenuBarX::nsMenuBarX()
+: nsMenuGroupOwnerX(), mParentWindow(nullptr)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsMenuBarX::~nsMenuBarX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (nsMenuBarX::sLastGeckoMenuBarPainted == this)
+ nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
+
+ // the quit/pref items of a random window might have been used if there was no
+ // hidden window, thus we need to invalidate the weak references.
+ if (sAboutItemContent == mAboutItemContent)
+ sAboutItemContent = nullptr;
+ if (sQuitItemContent == mQuitItemContent)
+ sQuitItemContent = nullptr;
+ if (sPrefItemContent == mPrefItemContent)
+ sPrefItemContent = nullptr;
+
+ // make sure we unregister ourselves as a content observer
+ if (mContent) {
+ UnregisterForContentChanges(mContent);
+ }
+
+ // We have to manually clear the array here because clearing causes menu items
+ // to call back into the menu bar to unregister themselves. We don't want to
+ // depend on member variable ordering to ensure that the array gets cleared
+ // before the registration hash table is destroyed.
+ mMenuArray.Clear();
+
+ [mNativeMenu release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent)
+{
+ if (!aParent)
+ return NS_ERROR_INVALID_ARG;
+
+ mParentWindow = aParent;
+ mContent = aContent;
+
+ if (mContent) {
+ AquifyMenuBar();
+
+ nsresult rv = nsMenuGroupOwnerX::Create(mContent);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RegisterForContentChanges(mContent, this);
+ ConstructNativeMenus();
+ } else {
+ ConstructFallbackNativeMenus();
+ }
+
+ // Give this to the parent window. The parent takes ownership.
+ static_cast<nsCocoaWindow*>(mParentWindow)->SetMenuBar(this);
+
+ return NS_OK;
+}
+
+void nsMenuBarX::ConstructNativeMenus()
+{
+ uint32_t count = mContent->GetChildCount();
+ for (uint32_t i = 0; i < count; i++) {
+ nsIContent *menuContent = mContent->GetChildAt(i);
+ if (menuContent &&
+ menuContent->IsXULElement(nsGkAtoms::menu)) {
+ nsMenuX* newMenu = new nsMenuX();
+ if (newMenu) {
+ nsresult rv = newMenu->Create(this, this, menuContent);
+ if (NS_SUCCEEDED(rv))
+ InsertMenuAtIndex(newMenu, GetMenuCount());
+ else
+ delete newMenu;
+ }
+ }
+ }
+}
+
+void nsMenuBarX::ConstructFallbackNativeMenus()
+{
+ if (sApplicationMenu) {
+ // Menu has already been built.
+ return;
+ }
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/fallbackMenubar.properties", getter_AddRefs(stringBundle));
+
+ if (!stringBundle) {
+ return;
+ }
+
+ nsXPIDLString labelUTF16;
+ nsXPIDLString keyUTF16;
+
+ const char16_t* labelProp = u"quitMenuitem.label";
+ const char16_t* keyProp = u"quitMenuitem.key";
+
+ stringBundle->GetStringFromName(labelProp, getter_Copies(labelUTF16));
+ stringBundle->GetStringFromName(keyProp, getter_Copies(keyUTF16));
+
+ NSString* labelStr = [NSString stringWithUTF8String:
+ NS_ConvertUTF16toUTF8(labelUTF16).get()];
+ NSString* keyStr= [NSString stringWithUTF8String:
+ NS_ConvertUTF16toUTF8(keyUTF16).get()];
+
+ if (!nsMenuBarX::sNativeEventTarget) {
+ nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
+ }
+
+ sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
+ NSMenuItem* quitMenuItem = [[[NSMenuItem alloc] initWithTitle:labelStr
+ action:@selector(menuItemHit:)
+ keyEquivalent:keyStr] autorelease];
+ [quitMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [quitMenuItem setTag:eCommand_ID_Quit];
+ [sApplicationMenu addItem:quitMenuItem];
+ sApplicationMenuIsFallback = YES;
+}
+
+uint32_t nsMenuBarX::GetMenuCount()
+{
+ return mMenuArray.Length();
+}
+
+bool nsMenuBarX::MenuContainsAppMenu()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return ([mNativeMenu numberOfItems] > 0 &&
+ [[mNativeMenu itemAtIndex:0] submenu] == sApplicationMenu);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+nsresult nsMenuBarX::InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // If we've only yet created a fallback global Application menu (using
+ // ContructFallbackNativeMenus()), destroy it before recreating it properly.
+ if (sApplicationMenu && sApplicationMenuIsFallback) {
+ ResetNativeApplicationMenu();
+ }
+ // If we haven't created a global Application menu yet, do it.
+ if (!sApplicationMenu) {
+ nsresult rv = NS_OK; // avoid warning about rv being unused
+ rv = CreateApplicationMenu(aMenu);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't create Application menu");
+
+ // Hook the new Application menu up to the menu bar.
+ NSMenu* mainMenu = [NSApp mainMenu];
+ NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+ [[mainMenu itemAtIndex:0] setSubmenu:sApplicationMenu];
+ }
+
+ // add menu to array that owns our menus
+ mMenuArray.InsertElementAt(aIndex, aMenu);
+
+ // hook up submenus
+ nsIContent* menuContent = aMenu->Content();
+ if (menuContent->GetChildCount() > 0 &&
+ !nsMenuUtilsX::NodeIsHiddenOrCollapsed(menuContent)) {
+ int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(this, aMenu);
+ if (MenuContainsAppMenu())
+ insertionIndex++;
+ [mNativeMenu insertItem:aMenu->NativeMenuItem() atIndex:insertionIndex];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsMenuBarX::RemoveMenuAtIndex(uint32_t aIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mMenuArray.Length() <= aIndex) {
+ NS_ERROR("Attempting submenu removal with bad index!");
+ return;
+ }
+
+ // Our native menu and our internal menu object array might be out of sync.
+ // This happens, for example, when a submenu is hidden. Because of this we
+ // should not assume that a native submenu is hooked up.
+ NSMenuItem* nativeMenuItem = mMenuArray[aIndex]->NativeMenuItem();
+ int nativeMenuItemIndex = [mNativeMenu indexOfItem:nativeMenuItem];
+ if (nativeMenuItemIndex != -1)
+ [mNativeMenu removeItemAtIndex:nativeMenuItemIndex];
+
+ mMenuArray.RemoveElementAt(aIndex);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuBarX::ObserveAttributeChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ nsIAtom* aAttribute)
+{
+}
+
+void nsMenuBarX::ObserveContentRemoved(nsIDocument* aDocument,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ RemoveMenuAtIndex(aIndexInContainer);
+}
+
+void nsMenuBarX::ObserveContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild)
+{
+ nsMenuX* newMenu = new nsMenuX();
+ if (newMenu) {
+ nsresult rv = newMenu->Create(this, this, aChild);
+ if (NS_SUCCEEDED(rv))
+ InsertMenuAtIndex(newMenu, aContainer->IndexOf(aChild));
+ else
+ delete newMenu;
+ }
+}
+
+void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+ NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0)
+ return;
+
+ nsMenuX* currentMenu = NULL;
+ int targetIndex = [[indexes objectAtIndex:0] intValue];
+ int visible = 0;
+ uint32_t length = mMenuArray.Length();
+ // first find a menu in the menu bar
+ for (unsigned int i = 0; i < length; i++) {
+ nsMenuX* menu = mMenuArray[i].get();
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
+ visible++;
+ if (visible == (targetIndex + 1)) {
+ currentMenu = menu;
+ break;
+ }
+ }
+ }
+
+ if (!currentMenu)
+ return;
+
+ // fake open/close to cause lazy update to happen so submenus populate
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+
+ // now find the correct submenu
+ for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
+ targetIndex = [[indexes objectAtIndex:i] intValue];
+ visible = 0;
+ length = currentMenu->GetItemCount();
+ for (unsigned int j = 0; j < length; j++) {
+ nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
+ if (!targetMenu)
+ return;
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
+ visible++;
+ if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
+ currentMenu = static_cast<nsMenuX*>(targetMenu);
+ // fake open/close to cause lazy update to happen
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+ break;
+ }
+ }
+ }
+ }
+}
+
+// Calling this forces a full reload of the menu system, reloading all native
+// menus and their items.
+// Without this testing is hard because changes to the DOM affect the native
+// menu system lazily.
+void nsMenuBarX::ForceNativeMenuReload()
+{
+ // tear down everything
+ while (GetMenuCount() > 0)
+ RemoveMenuAtIndex(0);
+
+ // construct everything
+ ConstructNativeMenus();
+}
+
+nsMenuX* nsMenuBarX::GetMenuAt(uint32_t aIndex)
+{
+ if (mMenuArray.Length() <= aIndex) {
+ NS_ERROR("Requesting menu at invalid index!");
+ return NULL;
+ }
+ return mMenuArray[aIndex].get();
+}
+
+nsMenuX* nsMenuBarX::GetXULHelpMenu()
+{
+ // The Help menu is usually (always?) the last one, so we start there and
+ // count back.
+ for (int32_t i = GetMenuCount() - 1; i >= 0; --i) {
+ nsMenuX* aMenu = GetMenuAt(i);
+ if (aMenu && nsMenuX::IsXULHelpMenu(aMenu->Content()))
+ return aMenu;
+ }
+ return nil;
+}
+
+// On SnowLeopard and later we must tell the OS which is our Help menu.
+// Otherwise it will only add Spotlight for Help (the Search item) to our
+// Help menu if its label/title is "Help" -- i.e. if the menu is in English.
+// This resolves bugs 489196 and 539317.
+void nsMenuBarX::SetSystemHelpMenu()
+{
+ nsMenuX* xulHelpMenu = GetXULHelpMenu();
+ if (xulHelpMenu) {
+ NSMenu* helpMenu = (NSMenu*)xulHelpMenu->NativeData();
+ if (helpMenu)
+ [NSApp setHelpMenu:helpMenu];
+ }
+}
+
+nsresult nsMenuBarX::Paint()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Don't try to optimize anything in this painting by checking
+ // sLastGeckoMenuBarPainted because the menubar can be manipulated by
+ // native dialogs and sheet code and other things besides this paint method.
+
+ // We have to keep the same menu item for the Application menu so we keep
+ // passing it along.
+ NSMenu* outgoingMenu = [NSApp mainMenu];
+ NS_ASSERTION([outgoingMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+
+ NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain];
+ [outgoingMenu removeItemAtIndex:0];
+ [mNativeMenu insertItem:appMenuItem atIndex:0];
+ [appMenuItem release];
+
+ // Set menu bar and event target.
+ [NSApp setMainMenu:mNativeMenu];
+ SetSystemHelpMenu();
+ nsMenuBarX::sLastGeckoMenuBarPainted = this;
+
+ gSomeMenuBarPainted = YES;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Returns the 'key' attribute of the 'shortcutID' object (if any) in the
+// currently active menubar's DOM document. 'shortcutID' should be the id
+// (i.e. the name) of a component that defines a commonly used (and
+// localized) cmd+key shortcut, and belongs to a keyset containing similar
+// objects. For example "key_selectAll". Returns a value that can be
+// compared to the first character of [NSEvent charactersIgnoringModifiers]
+// when [NSEvent modifierFlags] == NSCommandKeyMask.
+char nsMenuBarX::GetLocalizedAccelKey(const char *shortcutID)
+{
+ if (!sLastGeckoMenuBarPainted)
+ return 0;
+
+ nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(sLastGeckoMenuBarPainted->mContent->OwnerDoc()));
+ if (!domDoc)
+ return 0;
+
+ NS_ConvertASCIItoUTF16 shortcutIDStr((const char *)shortcutID);
+ nsCOMPtr<nsIDOMElement> shortcutElement;
+ domDoc->GetElementById(shortcutIDStr, getter_AddRefs(shortcutElement));
+ nsCOMPtr<nsIContent> shortcutContent = do_QueryInterface(shortcutElement);
+ if (!shortcutContent)
+ return 0;
+
+ nsAutoString key;
+ shortcutContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
+ NS_LossyConvertUTF16toASCII keyASC(key.get());
+ const char *keyASCPtr = keyASC.get();
+ if (!keyASCPtr)
+ return 0;
+ // If keyID's 'key' attribute isn't exactly one character long, it's not
+ // what we're looking for.
+ if (strlen(keyASCPtr) != sizeof(char))
+ return 0;
+ // Make sure retval is lower case.
+ char retval = tolower(keyASCPtr[0]);
+
+ return retval;
+}
+
+/* static */
+void nsMenuBarX::ResetNativeApplicationMenu()
+{
+ [sApplicationMenu removeAllItems];
+ [sApplicationMenu release];
+ sApplicationMenu = nil;
+ sApplicationMenuIsFallback = NO;
+}
+
+// Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so
+// the caller can hang onto it if they so choose. It is acceptable to pass nsull
+// for |outHiddenNode| if the caller doesn't care about the hidden node.
+void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode)
+{
+ nsCOMPtr<nsIDOMElement> menuItem;
+ inDoc->GetElementById(inID, getter_AddRefs(menuItem));
+ nsCOMPtr<nsIContent> menuContent(do_QueryInterface(menuItem));
+ if (menuContent) {
+ menuContent->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, NS_LITERAL_STRING("true"), false);
+ if (outHiddenNode) {
+ *outHiddenNode = menuContent.get();
+ NS_IF_ADDREF(*outHiddenNode);
+ }
+ }
+}
+
+// Do what is necessary to conform to the Aqua guidelines for menus.
+void nsMenuBarX::AquifyMenuBar()
+{
+ nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mContent->GetComposedDoc()));
+ if (domDoc) {
+ // remove the "About..." item and its separator
+ HideItem(domDoc, NS_LITERAL_STRING("aboutSeparator"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("aboutName"), getter_AddRefs(mAboutItemContent));
+ if (!sAboutItemContent)
+ sAboutItemContent = mAboutItemContent;
+
+ // remove quit item and its separator
+ HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitSeparator"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitItem"), getter_AddRefs(mQuitItemContent));
+ if (!sQuitItemContent)
+ sQuitItemContent = mQuitItemContent;
+
+ // remove prefs item and its separator, but save off the pref content node
+ // so we can invoke its command later.
+ HideItem(domDoc, NS_LITERAL_STRING("menu_PrefsSeparator"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_preferences"), getter_AddRefs(mPrefItemContent));
+ if (!sPrefItemContent)
+ sPrefItemContent = mPrefItemContent;
+
+ // hide items that we use for the Application menu
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_services"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_app"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_others"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_show_all"), nullptr);
+ }
+}
+
+// for creating menu items destined for the Application menu
+NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
+ int tag, NativeMenuItemTarget* target)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsCOMPtr<nsIDocument> doc = inMenu->Content()->GetUncomposedDoc();
+ if (!doc) {
+ return nil;
+ }
+
+ nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc));
+ if (!domdoc) {
+ return nil;
+ }
+
+ // Get information from the gecko menu item
+ nsAutoString label;
+ nsAutoString modifiers;
+ nsAutoString key;
+ nsCOMPtr<nsIDOMElement> menuItem;
+ domdoc->GetElementById(nodeID, getter_AddRefs(menuItem));
+ if (menuItem) {
+ menuItem->GetAttribute(NS_LITERAL_STRING("label"), label);
+ menuItem->GetAttribute(NS_LITERAL_STRING("modifiers"), modifiers);
+ menuItem->GetAttribute(NS_LITERAL_STRING("key"), key);
+ }
+ else {
+ return nil;
+ }
+
+ // Get more information about the key equivalent. Start by
+ // finding the key node we need.
+ NSString* keyEquiv = nil;
+ unsigned int macKeyModifiers = 0;
+ if (!key.IsEmpty()) {
+ nsCOMPtr<nsIDOMElement> keyElement;
+ domdoc->GetElementById(key, getter_AddRefs(keyElement));
+ if (keyElement) {
+ nsCOMPtr<nsIContent> keyContent (do_QueryInterface(keyElement));
+ // first grab the key equivalent character
+ nsAutoString keyChar(NS_LITERAL_STRING(" "));
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
+ if (!keyChar.EqualsLiteral(" ")) {
+ keyEquiv = [[NSString stringWithCharacters:reinterpret_cast<const unichar*>(keyChar.get())
+ length:keyChar.Length()] lowercaseString];
+ }
+ // now grab the key equivalent modifiers
+ nsAutoString modifiersStr;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
+ uint8_t geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
+ macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers);
+ }
+ }
+ // get the label into NSString-form
+ NSString* labelString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(label.get())
+ length:label.Length()];
+
+ if (!labelString)
+ labelString = @"";
+ if (!keyEquiv)
+ keyEquiv = @"";
+
+ // put together the actual NSMenuItem
+ NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:keyEquiv];
+
+ [newMenuItem setTag:tag];
+ [newMenuItem setTarget:target];
+ [newMenuItem setKeyEquivalentModifierMask:macKeyModifiers];
+
+ MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:this];
+ [newMenuItem setRepresentedObject:info];
+ [info release];
+
+ return newMenuItem;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// build the Application menu shared by all menu bars
+nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // At this point, the application menu is the application menu from
+ // the nib in cocoa widgets. We do not have a way to create an application
+ // menu manually, so we grab the one from the nib and use that.
+ sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
+
+/*
+ We support the following menu items here:
+
+ Menu Item DOM Node ID Notes
+
+ ========================
+ = About This App = <- aboutName
+ ========================
+ = Preferences... = <- menu_preferences
+ ========================
+ = Services > = <- menu_mac_services <- (do not define key equivalent)
+ ========================
+ = Hide App = <- menu_mac_hide_app
+ = Hide Others = <- menu_mac_hide_others
+ = Show All = <- menu_mac_show_all
+ ========================
+ = Quit = <- menu_FileQuitItem
+ ========================
+
+ If any of them are ommitted from the application's DOM, we just don't add
+ them. We always add a "Quit" item, but if an app developer does not provide a
+ DOM node with the right ID for the Quit item, we add it in English. App
+ developers need only add each node with a label and a key equivalent (if they
+ want one). Other attributes are optional. Like so:
+
+ <menuitem id="menu_preferences"
+ label="&preferencesCmdMac.label;"
+ key="open_prefs_key"/>
+
+ We need to use this system for localization purposes, until we have a better way
+ to define the Application menu to be used on Mac OS X.
+*/
+
+ if (sApplicationMenu) {
+ // This code reads attributes we are going to care about from the DOM elements
+
+ NSMenuItem *itemBeingAdded = nil;
+ BOOL addAboutSeparator = FALSE;
+
+ // Add the About menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:),
+ eCommand_ID_About, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addAboutSeparator = TRUE;
+ }
+
+ // Add separator if either the About item or software update item exists
+ if (addAboutSeparator)
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add the Preferences menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:),
+ eCommand_ID_Prefs, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ // Add separator after Preferences menu
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+ }
+
+ // Add Services menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_services"), nil,
+ 0, nil);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+
+ // set this menu item up as the Mac OS X Services menu
+ NSMenu* servicesMenu = [[GeckoServicesNSMenu alloc] initWithTitle:@""];
+ [itemBeingAdded setSubmenu:servicesMenu];
+ [NSApp setServicesMenu:servicesMenu];
+
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ // Add separator after Services menu
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+ }
+
+ BOOL addHideShowSeparator = FALSE;
+
+ // Add menu item to hide this application
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_app"), @selector(menuItemHit:),
+ eCommand_ID_HideApp, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addHideShowSeparator = TRUE;
+ }
+
+ // Add menu item to hide other applications
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(menuItemHit:),
+ eCommand_ID_HideOthers, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addHideShowSeparator = TRUE;
+ }
+
+ // Add menu item to show all applications
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(menuItemHit:),
+ eCommand_ID_ShowAll, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addHideShowSeparator = TRUE;
+ }
+
+ // Add a separator after the hide/show menus if at least one exists
+ if (addHideShowSeparator)
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add quit menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_FileQuitItem"), @selector(menuItemHit:),
+ eCommand_ID_Quit, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+ }
+ else {
+ // the current application does not have a DOM node for "Quit". Add one
+ // anyway, in English.
+ NSMenuItem* defaultQuitItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(menuItemHit:)
+ keyEquivalent:@"q"] autorelease];
+ [defaultQuitItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [defaultQuitItem setTag:eCommand_ID_Quit];
+ [sApplicationMenu addItem:defaultQuitItem];
+ }
+ }
+
+ return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsMenuBarX::SetParent(nsIWidget* aParent)
+{
+ mParentWindow = aParent;
+}
+
+//
+// Objective-C class used to allow us to have keyboard commands
+// look like they are doing something but actually do nothing.
+// We allow mouse actions to work normally.
+//
+
+// Controls whether or not native menu items should invoke their commands.
+static BOOL gMenuItemsExecuteCommands = YES;
+
+@implementation GeckoNSMenu
+
+// Keyboard commands should not cause menu items to invoke their
+// commands when there is a key window because we'd rather send
+// the keyboard command to the window. We still have the menus
+// go through the mechanics so they'll give the proper visual
+// feedback.
+- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
+{
+ // We've noticed that Mac OS X expects this check in subclasses before
+ // calling NSMenu's "performKeyEquivalent:".
+ //
+ // There is no case in which we'd need to do anything or return YES
+ // when we have no items so we can just do this check first.
+ if ([self numberOfItems] <= 0) {
+ return NO;
+ }
+
+ NSWindow *keyWindow = [NSApp keyWindow];
+
+ // If there is no key window then just behave normally. This
+ // probably means that this menu is associated with Gecko's
+ // hidden window.
+ if (!keyWindow) {
+ return [super performKeyEquivalent:theEvent];
+ }
+
+ NSResponder *firstResponder = [keyWindow firstResponder];
+
+ gMenuItemsExecuteCommands = NO;
+ [super performKeyEquivalent:theEvent];
+ gMenuItemsExecuteCommands = YES; // return to default
+
+ // Return YES if we invoked a command and there is now no key window or we changed
+ // the first responder. In this case we do not want to propagate the event because
+ // we don't want it handled again.
+ if (![NSApp keyWindow] || [[NSApp keyWindow] firstResponder] != firstResponder) {
+ return YES;
+ }
+
+ // Return NO so that we can handle the event via NSView's "keyDown:".
+ return NO;
+}
+
+@end
+
+//
+// Objective-C class used as action target for menu items
+//
+
+@implementation NativeMenuItemTarget
+
+// called when some menu item in this menu gets hit
+-(IBAction)menuItemHit:(id)sender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!gMenuItemsExecuteCommands) {
+ return;
+ }
+
+ int tag = [sender tag];
+
+ nsMenuGroupOwnerX* menuGroupOwner = nullptr;
+ nsMenuBarX* menuBar = nullptr;
+ MenuItemInfo* info = [sender representedObject];
+
+ if (info) {
+ menuGroupOwner = [info menuGroupOwner];
+ if (!menuGroupOwner) {
+ return;
+ }
+ if (menuGroupOwner->MenuObjectType() == eMenuBarObjectType) {
+ menuBar = static_cast<nsMenuBarX*>(menuGroupOwner);
+ }
+ }
+
+ // Do special processing if this is for an app-global command.
+ if (tag == eCommand_ID_About) {
+ nsIContent* mostSpecificContent = sAboutItemContent;
+ if (menuBar && menuBar->mAboutItemContent)
+ mostSpecificContent = menuBar->mAboutItemContent;
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ return;
+ }
+ else if (tag == eCommand_ID_Prefs) {
+ nsIContent* mostSpecificContent = sPrefItemContent;
+ if (menuBar && menuBar->mPrefItemContent)
+ mostSpecificContent = menuBar->mPrefItemContent;
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ return;
+ }
+ else if (tag == eCommand_ID_HideApp) {
+ [NSApp hide:sender];
+ return;
+ }
+ else if (tag == eCommand_ID_HideOthers) {
+ [NSApp hideOtherApplications:sender];
+ return;
+ }
+ else if (tag == eCommand_ID_ShowAll) {
+ [NSApp unhideAllApplications:sender];
+ return;
+ }
+ else if (tag == eCommand_ID_Quit) {
+ nsIContent* mostSpecificContent = sQuitItemContent;
+ if (menuBar && menuBar->mQuitItemContent)
+ mostSpecificContent = menuBar->mQuitItemContent;
+ // If we have some content for quit we execute it. Otherwise we send a native app terminate
+ // message. If you want to stop a quit from happening, provide quit content and return
+ // the event as unhandled.
+ if (mostSpecificContent) {
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ }
+ else {
+ nsCOMPtr<nsIAppStartup> appStartup = do_GetService(NS_APPSTARTUP_CONTRACTID);
+ if (appStartup) {
+ appStartup->Quit(nsIAppStartup::eAttemptQuit);
+ }
+ }
+ return;
+ }
+
+ // given the commandID, look it up in our hashtable and dispatch to
+ // that menu item.
+ if (menuGroupOwner) {
+ nsMenuItemX* menuItem = menuGroupOwner->GetMenuItemForCommandID(static_cast<uint32_t>(tag));
+ if (menuItem)
+ menuItem->DoCommand();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// Objective-C class used for menu items on the Services menu to allow Gecko
+// to override their standard behavior in order to stop key equivalents from
+// firing in certain instances. When gMenuItemsExecuteCommands is NO, we return
+// a dummy target and action instead of the actual target and action.
+
+@implementation GeckoServicesNSMenuItem
+
+- (id) target
+{
+ id realTarget = [super target];
+ if (gMenuItemsExecuteCommands)
+ return realTarget;
+ else
+ return realTarget ? self : nil;
+}
+
+- (SEL) action
+{
+ SEL realAction = [super action];
+ if (gMenuItemsExecuteCommands)
+ return realAction;
+ else
+ return realAction ? @selector(_doNothing:) : NULL;
+}
+
+- (void) _doNothing:(id)sender
+{
+}
+
+@end
+
+// Objective-C class used as the Services menu so that Gecko can override the
+// standard behavior of the Services menu in order to stop key equivalents
+// from firing in certain instances.
+
+@implementation GeckoServicesNSMenu
+
+- (void)addItem:(NSMenuItem *)newItem
+{
+ [self _overrideClassOfMenuItem:newItem];
+ [super addItem:newItem];
+}
+
+- (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv
+{
+ NSMenuItem * newItem = [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv];
+ [self _overrideClassOfMenuItem:newItem];
+ return newItem;
+}
+
+- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index
+{
+ [self _overrideClassOfMenuItem:newItem];
+ [super insertItem:newItem atIndex:index];
+}
+
+- (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index
+{
+ NSMenuItem * newItem = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index];
+ [self _overrideClassOfMenuItem:newItem];
+ return newItem;
+}
+
+- (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem
+{
+ if ([menuItem class] == [NSMenuItem class])
+ object_setClass(menuItem, [GeckoServicesNSMenuItem class]);
+}
+
+@end
diff --git a/widget/cocoa/nsMenuBaseX.h b/widget/cocoa/nsMenuBaseX.h
new file mode 100644
index 000000000..5b9f89c56
--- /dev/null
+++ b/widget/cocoa/nsMenuBaseX.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuBaseX_h_
+#define nsMenuBaseX_h_
+
+#import <Foundation/Foundation.h>
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+
+enum nsMenuObjectTypeX {
+ eMenuBarObjectType,
+ eSubmenuObjectType,
+ eMenuItemObjectType,
+ eStandaloneNativeMenuObjectType,
+};
+
+// All menu objects subclass this.
+// Menu bars are owned by their top-level nsIWidgets.
+// All other objects are memory-managed based on the DOM.
+// Content removal deletes them immediately and nothing else should.
+// Do not attempt to hold strong references to them or delete them.
+class nsMenuObjectX
+{
+public:
+ virtual ~nsMenuObjectX() { }
+ virtual nsMenuObjectTypeX MenuObjectType()=0;
+ virtual void* NativeData()=0;
+ nsIContent* Content() { return mContent; }
+
+ /**
+ * Called when an icon of a menu item somewhere in this menu has updated.
+ * Menu objects with parents need to propagate the notification to their
+ * parent.
+ */
+ virtual void IconUpdated() {}
+
+protected:
+ nsCOMPtr<nsIContent> mContent;
+};
+
+
+//
+// Object stored as "representedObject" for all menu items
+//
+
+class nsMenuGroupOwnerX;
+
+@interface MenuItemInfo : NSObject
+{
+ nsMenuGroupOwnerX * mMenuGroupOwner;
+}
+
+- (id) initWithMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner;
+- (nsMenuGroupOwnerX *) menuGroupOwner;
+- (void) setMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner;
+
+@end
+
+
+// Special command IDs that we know Mac OS X does not use for anything else.
+// We use these in place of carbon's IDs for these commands in order to stop
+// Carbon from messing with our event handlers. See bug 346883.
+
+enum {
+ eCommand_ID_About = 1,
+ eCommand_ID_Prefs = 2,
+ eCommand_ID_Quit = 3,
+ eCommand_ID_HideApp = 4,
+ eCommand_ID_HideOthers = 5,
+ eCommand_ID_ShowAll = 6,
+ eCommand_ID_Update = 7,
+ eCommand_ID_Last = 8
+};
+
+#endif // nsMenuBaseX_h_
diff --git a/widget/cocoa/nsMenuGroupOwnerX.h b/widget/cocoa/nsMenuGroupOwnerX.h
new file mode 100644
index 000000000..657f420b5
--- /dev/null
+++ b/widget/cocoa/nsMenuGroupOwnerX.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuGroupOwnerX_h_
+#define nsMenuGroupOwnerX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMenuBaseX.h"
+#include "nsIMutationObserver.h"
+#include "nsHashKeys.h"
+#include "nsDataHashtable.h"
+#include "nsString.h"
+
+class nsMenuItemX;
+class nsChangeObserver;
+class nsIWidget;
+class nsIContent;
+
+class nsMenuGroupOwnerX : public nsMenuObjectX, public nsIMutationObserver
+{
+public:
+ nsMenuGroupOwnerX();
+
+ nsresult Create(nsIContent * aContent);
+
+ void RegisterForContentChanges(nsIContent* aContent,
+ nsChangeObserver* aMenuObject);
+ void UnregisterForContentChanges(nsIContent* aContent);
+ uint32_t RegisterForCommand(nsMenuItemX* aItem);
+ void UnregisterCommand(uint32_t aCommandID);
+ nsMenuItemX* GetMenuItemForCommandID(uint32_t inCommandID);
+ void AddMenuItemInfoToSet(MenuItemInfo* info);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMUTATIONOBSERVER
+
+protected:
+ virtual ~nsMenuGroupOwnerX();
+
+ nsChangeObserver* LookupContentChangeObserver(nsIContent* aContent);
+
+ uint32_t mCurrentCommandID; // unique command id (per menu-bar) to
+ // give to next item that asks
+
+ // stores observers for content change notification
+ nsDataHashtable<nsPtrHashKey<nsIContent>, nsChangeObserver *> mContentToObserverTable;
+
+ // stores mapping of command IDs to menu objects
+ nsDataHashtable<nsUint32HashKey, nsMenuItemX *> mCommandToMenuObjectTable;
+
+ // Stores references to all the MenuItemInfo objects created with weak
+ // references to us. They may live longer than we do, so when we're
+ // destroyed we need to clear all their weak references. This avoids
+ // crashes in -[NativeMenuItemTarget menuItemHit:]. See bug 1131473.
+ NSMutableSet* mInfoSet;
+};
+
+#endif // nsMenuGroupOwner_h_
diff --git a/widget/cocoa/nsMenuGroupOwnerX.mm b/widget/cocoa/nsMenuGroupOwnerX.mm
new file mode 100644
index 000000000..661a52bd8
--- /dev/null
+++ b/widget/cocoa/nsMenuGroupOwnerX.mm
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMenuGroupOwnerX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsObjCExceptions.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/dom/Element.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+
+#include "nsINode.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsMenuGroupOwnerX, nsIMutationObserver)
+
+
+nsMenuGroupOwnerX::nsMenuGroupOwnerX()
+: mCurrentCommandID(eCommand_ID_Last)
+{
+ mInfoSet = [[NSMutableSet setWithCapacity:10] retain];
+}
+
+
+nsMenuGroupOwnerX::~nsMenuGroupOwnerX()
+{
+ MOZ_ASSERT(mContentToObserverTable.Count() == 0, "have outstanding mutation observers!\n");
+
+ // The MenuItemInfo objects in mInfoSet may live longer than we do. So when
+ // we get destroyed we need to invalidate all their mMenuGroupOwner pointers.
+ NSEnumerator* counter = [mInfoSet objectEnumerator];
+ MenuItemInfo* info;
+ while ((info = (MenuItemInfo*) [counter nextObject])) {
+ [info setMenuGroupOwner:nil];
+ }
+ [mInfoSet release];
+}
+
+
+nsresult nsMenuGroupOwnerX::Create(nsIContent* aContent)
+{
+ if (!aContent)
+ return NS_ERROR_INVALID_ARG;
+
+ mContent = aContent;
+
+ return NS_OK;
+}
+
+
+//
+// nsIMutationObserver
+//
+
+
+void nsMenuGroupOwnerX::CharacterDataWillChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+
+void nsMenuGroupOwnerX::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+
+void nsMenuGroupOwnerX::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ ContentInserted(aDocument, aContainer, cur, 0);
+ }
+}
+
+
+void nsMenuGroupOwnerX::NodeWillBeDestroyed(const nsINode * aNode)
+{
+}
+
+
+void nsMenuGroupOwnerX::AttributeWillChange(nsIDocument* aDocument,
+ dom::Element* aContent,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+}
+
+void nsMenuGroupOwnerX::NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove)
+{
+}
+
+void nsMenuGroupOwnerX::AttributeChanged(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aElement);
+ if (obs)
+ obs->ObserveAttributeChanged(aDocument, aElement, aAttribute);
+}
+
+
+void nsMenuGroupOwnerX::ContentRemoved(nsIDocument * aDocument,
+ nsIContent * aContainer,
+ nsIContent * aChild,
+ int32_t aIndexInContainer,
+ nsIContent * aPreviousSibling)
+{
+ if (!aContainer) {
+ return;
+ }
+
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aContainer);
+ if (obs)
+ obs->ObserveContentRemoved(aDocument, aChild, aIndexInContainer);
+ else if (aContainer != mContent) {
+ // We do a lookup on the parent container in case things were removed
+ // under a "menupopup" item. That is basically a wrapper for the contents
+ // of a "menu" node.
+ nsCOMPtr<nsIContent> parent = aContainer->GetParent();
+ if (parent) {
+ obs = LookupContentChangeObserver(parent);
+ if (obs)
+ obs->ObserveContentRemoved(aDocument, aChild, aIndexInContainer);
+ }
+ }
+}
+
+
+void nsMenuGroupOwnerX::ContentInserted(nsIDocument * aDocument,
+ nsIContent * aContainer,
+ nsIContent * aChild,
+ int32_t /* unused */)
+{
+ if (!aContainer) {
+ return;
+ }
+
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aContainer);
+ if (obs)
+ obs->ObserveContentInserted(aDocument, aContainer, aChild);
+ else if (aContainer != mContent) {
+ // We do a lookup on the parent container in case things were removed
+ // under a "menupopup" item. That is basically a wrapper for the contents
+ // of a "menu" node.
+ nsCOMPtr<nsIContent> parent = aContainer->GetParent();
+ if (parent) {
+ obs = LookupContentChangeObserver(parent);
+ if (obs)
+ obs->ObserveContentInserted(aDocument, aContainer, aChild);
+ }
+ }
+}
+
+
+void nsMenuGroupOwnerX::ParentChainChanged(nsIContent *aContent)
+{
+}
+
+
+// For change management, we don't use a |nsSupportsHashtable| because
+// we know that the lifetime of all these items is bounded by the
+// lifetime of the menubar. No need to add any more strong refs to the
+// picture because the containment hierarchy already uses strong refs.
+void nsMenuGroupOwnerX::RegisterForContentChanges(nsIContent *aContent,
+ nsChangeObserver *aMenuObject)
+{
+ if (!mContentToObserverTable.Contains(aContent)) {
+ aContent->AddMutationObserver(this);
+ }
+ mContentToObserverTable.Put(aContent, aMenuObject);
+}
+
+
+void nsMenuGroupOwnerX::UnregisterForContentChanges(nsIContent *aContent)
+{
+ if (mContentToObserverTable.Contains(aContent)) {
+ aContent->RemoveMutationObserver(this);
+ }
+ mContentToObserverTable.Remove(aContent);
+}
+
+
+nsChangeObserver* nsMenuGroupOwnerX::LookupContentChangeObserver(nsIContent* aContent)
+{
+ nsChangeObserver * result;
+ if (mContentToObserverTable.Get(aContent, &result))
+ return result;
+ else
+ return nullptr;
+}
+
+
+// Given a menu item, creates a unique 4-character command ID and
+// maps it to the item. Returns the id for use by the client.
+uint32_t nsMenuGroupOwnerX::RegisterForCommand(nsMenuItemX* inMenuItem)
+{
+ // no real need to check for uniqueness. We always start afresh with each
+ // window at 1. Even if we did get close to the reserved Apple command id's,
+ // those don't start until at least ' ', which is integer 538976288. If
+ // we have that many menu items in one window, I think we have other
+ // problems.
+
+ // make id unique
+ ++mCurrentCommandID;
+
+ mCommandToMenuObjectTable.Put(mCurrentCommandID, inMenuItem);
+
+ return mCurrentCommandID;
+}
+
+
+// Removes the mapping between the given 4-character command ID
+// and its associated menu item.
+void nsMenuGroupOwnerX::UnregisterCommand(uint32_t inCommandID)
+{
+ mCommandToMenuObjectTable.Remove(inCommandID);
+}
+
+
+nsMenuItemX* nsMenuGroupOwnerX::GetMenuItemForCommandID(uint32_t inCommandID)
+{
+ nsMenuItemX * result;
+ if (mCommandToMenuObjectTable.Get(inCommandID, &result))
+ return result;
+ else
+ return nullptr;
+}
+
+void nsMenuGroupOwnerX::AddMenuItemInfoToSet(MenuItemInfo* info)
+{
+ [mInfoSet addObject:info];
+}
diff --git a/widget/cocoa/nsMenuItemIconX.h b/widget/cocoa/nsMenuItemIconX.h
new file mode 100644
index 000000000..7352a94e2
--- /dev/null
+++ b/widget/cocoa/nsMenuItemIconX.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Retrieves and displays icons in native menu items on Mac OS X.
+ */
+
+#ifndef nsMenuItemIconX_h_
+#define nsMenuItemIconX_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "imgINotificationObserver.h"
+
+class nsIURI;
+class nsIContent;
+class imgRequestProxy;
+class nsMenuObjectX;
+
+#import <Cocoa/Cocoa.h>
+
+class nsMenuItemIconX : public imgINotificationObserver
+{
+public:
+ nsMenuItemIconX(nsMenuObjectX* aMenuItem,
+ nsIContent* aContent,
+ NSMenuItem* aNativeMenuItem);
+private:
+ virtual ~nsMenuItemIconX();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ // SetupIcon succeeds if it was able to set up the icon, or if there should
+ // be no icon, in which case it clears any existing icon but still succeeds.
+ nsresult SetupIcon();
+
+ // GetIconURI fails if the item should not have any icon.
+ nsresult GetIconURI(nsIURI** aIconURI);
+
+ // LoadIcon will set a placeholder image and start a load request for the
+ // icon. The request may not complete until after LoadIcon returns.
+ nsresult LoadIcon(nsIURI* aIconURI);
+
+ // Unless we take precautions, we may outlive the object that created us
+ // (mMenuObject, which owns our native menu item (mNativeMenuItem)).
+ // Destroy() should be called from mMenuObject's destructor to prevent
+ // this from happening. See bug 499600.
+ void Destroy();
+
+protected:
+ nsresult OnFrameComplete(imgIRequest* aRequest);
+
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<imgRequestProxy> mIconRequest;
+ nsMenuObjectX* mMenuObject; // [weak]
+ nsIntRect mImageRegionRect;
+ bool mLoadedIcon;
+ bool mSetIcon;
+ NSMenuItem* mNativeMenuItem; // [weak]
+};
+
+#endif // nsMenuItemIconX_h_
diff --git a/widget/cocoa/nsMenuItemIconX.mm b/widget/cocoa/nsMenuItemIconX.mm
new file mode 100644
index 000000000..7589c279e
--- /dev/null
+++ b/widget/cocoa/nsMenuItemIconX.mm
@@ -0,0 +1,466 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Retrieves and displays icons in native menu items on Mac OS X.
+ */
+
+/* exception_defines.h defines 'try' to 'if (true)' which breaks objective-c
+ exceptions and produces errors like: error: unexpected '@' in program'.
+ If we define __EXCEPTIONS exception_defines.h will avoid doing this.
+
+ See bug 666609 for more information.
+
+ We use <limits> to get the libstdc++ version. */
+#include <limits>
+#if __GLIBCXX__ <= 20070719
+#ifndef __EXCEPTIONS
+#define __EXCEPTIONS
+#endif
+#endif
+
+#include "nsMenuItemIconX.h"
+#include "nsObjCExceptions.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMElement.h"
+#include "nsICSSDeclaration.h"
+#include "nsIDOMCSSValue.h"
+#include "nsIDOMCSSPrimitiveValue.h"
+#include "nsIDOMRect.h"
+#include "nsThreadUtils.h"
+#include "nsToolkit.h"
+#include "nsNetUtil.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "nsMenuItemX.h"
+#include "gfxPlatform.h"
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+#include "nsContentUtils.h"
+#include "nsIContentPolicy.h"
+
+using mozilla::dom::Element;
+using mozilla::gfx::SourceSurface;
+
+static const uint32_t kIconWidth = 16;
+static const uint32_t kIconHeight = 16;
+
+typedef NS_STDCALL_FUNCPROTO(nsresult, GetRectSideMethod, nsIDOMRect,
+ GetBottom, (nsIDOMCSSPrimitiveValue**));
+
+NS_IMPL_ISUPPORTS(nsMenuItemIconX, imgINotificationObserver)
+
+nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem,
+ nsIContent* aContent,
+ NSMenuItem* aNativeMenuItem)
+: mContent(aContent)
+, mMenuObject(aMenuItem)
+, mLoadedIcon(false)
+, mSetIcon(false)
+, mNativeMenuItem(aNativeMenuItem)
+{
+ // printf("Creating icon for menu item %d, menu %d, native item is %d\n", aMenuItem, aMenu, aNativeMenuItem);
+}
+
+nsMenuItemIconX::~nsMenuItemIconX()
+{
+ if (mIconRequest)
+ mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+}
+
+// Called from mMenuObjectX's destructor, to prevent us from outliving it
+// (as might otherwise happen if calls to our imgINotificationObserver methods
+// are still outstanding). mMenuObjectX owns our nNativeMenuItem.
+void nsMenuItemIconX::Destroy()
+{
+ if (mIconRequest) {
+ mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+ mMenuObject = nullptr;
+ mNativeMenuItem = nil;
+}
+
+nsresult
+nsMenuItemIconX::SetupIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Still don't have one, then something is wrong, get out of here.
+ if (!mNativeMenuItem) {
+ NS_ERROR("No native menu item");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> iconURI;
+ nsresult rv = GetIconURI(getter_AddRefs(iconURI));
+ if (NS_FAILED(rv)) {
+ // There is no icon for this menu item. An icon might have been set
+ // earlier. Clear it.
+ [mNativeMenuItem setImage:nil];
+
+ return NS_OK;
+ }
+
+ rv = LoadIcon(iconURI);
+ if (NS_FAILED(rv)) {
+ // There is no icon for this menu item, as an error occurred while loading it.
+ // An icon might have been set earlier or the place holder icon may have
+ // been set. Clear it.
+ [mNativeMenuItem setImage:nil];
+ }
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+static int32_t
+GetDOMRectSide(nsIDOMRect* aRect, GetRectSideMethod aMethod)
+{
+ nsCOMPtr<nsIDOMCSSPrimitiveValue> dimensionValue;
+ (aRect->*aMethod)(getter_AddRefs(dimensionValue));
+ if (!dimensionValue)
+ return -1;
+
+ uint16_t primitiveType;
+ nsresult rv = dimensionValue->GetPrimitiveType(&primitiveType);
+ if (NS_FAILED(rv) || primitiveType != nsIDOMCSSPrimitiveValue::CSS_PX)
+ return -1;
+
+ float dimension = 0;
+ rv = dimensionValue->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX,
+ &dimension);
+ if (NS_FAILED(rv))
+ return -1;
+
+ return NSToIntRound(dimension);
+}
+
+nsresult
+nsMenuItemIconX::GetIconURI(nsIURI** aIconURI)
+{
+ if (!mMenuObject)
+ return NS_ERROR_FAILURE;
+
+ // Mac native menu items support having both a checkmark and an icon
+ // simultaneously, but this is unheard of in the cross-platform toolkit,
+ // seemingly because the win32 theme is unable to cope with both at once.
+ // The downside is that it's possible to get a menu item marked with a
+ // native checkmark and a checkmark for an icon. Head off that possibility
+ // by pretending that no icon exists if this is a checkable menu item.
+ if (mMenuObject->MenuObjectType() == eMenuItemObjectType) {
+ nsMenuItemX* menuItem = static_cast<nsMenuItemX*>(mMenuObject);
+ if (menuItem->GetMenuItemType() != eRegularMenuItemType)
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mContent)
+ return NS_ERROR_FAILURE;
+
+ // First, look at the content node's "image" attribute.
+ nsAutoString imageURIString;
+ bool hasImageAttr = mContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::image,
+ imageURIString);
+
+ nsresult rv;
+ nsCOMPtr<nsIDOMCSSValue> cssValue;
+ nsCOMPtr<nsICSSDeclaration> cssStyleDecl;
+ nsCOMPtr<nsIDOMCSSPrimitiveValue> primitiveValue;
+ uint16_t primitiveType;
+ if (!hasImageAttr) {
+ // If the content node has no "image" attribute, get the
+ // "list-style-image" property from CSS.
+ nsCOMPtr<nsIDocument> document = mContent->GetComposedDoc();
+ if (!document)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = document->GetInnerWindow();
+ if (!window)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<Element> domElement = do_QueryInterface(mContent);
+ if (!domElement)
+ return NS_ERROR_FAILURE;
+
+ ErrorResult dummy;
+ cssStyleDecl = window->GetComputedStyle(*domElement, EmptyString(), dummy);
+ dummy.SuppressException();
+ if (!cssStyleDecl)
+ return NS_ERROR_FAILURE;
+
+ NS_NAMED_LITERAL_STRING(listStyleImage, "list-style-image");
+ rv = cssStyleDecl->GetPropertyCSSValue(listStyleImage,
+ getter_AddRefs(cssValue));
+ if (NS_FAILED(rv)) return rv;
+
+ primitiveValue = do_QueryInterface(cssValue);
+ if (!primitiveValue) return NS_ERROR_FAILURE;
+
+ rv = primitiveValue->GetPrimitiveType(&primitiveType);
+ if (NS_FAILED(rv)) return rv;
+ if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_URI)
+ return NS_ERROR_FAILURE;
+
+ rv = primitiveValue->GetStringValue(imageURIString);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Empty the mImageRegionRect initially as the image region CSS could
+ // have been changed and now have an error or have been removed since the
+ // last GetIconURI call.
+ mImageRegionRect.SetEmpty();
+
+ // If this menu item shouldn't have an icon, the string will be empty,
+ // and NS_NewURI will fail.
+ nsCOMPtr<nsIURI> iconURI;
+ rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
+ if (NS_FAILED(rv)) return rv;
+
+ *aIconURI = iconURI;
+ NS_ADDREF(*aIconURI);
+
+ if (!hasImageAttr) {
+ // Check if the icon has a specified image region so that it can be
+ // cropped appropriately before being displayed.
+ NS_NAMED_LITERAL_STRING(imageRegion, "-moz-image-region");
+ rv = cssStyleDecl->GetPropertyCSSValue(imageRegion,
+ getter_AddRefs(cssValue));
+ // Just return NS_OK if there if there is a failure due to no
+ // moz-image region specified so the whole icon will be drawn anyway.
+ if (NS_FAILED(rv)) return NS_OK;
+
+ primitiveValue = do_QueryInterface(cssValue);
+ if (!primitiveValue) return NS_OK;
+
+ rv = primitiveValue->GetPrimitiveType(&primitiveType);
+ if (NS_FAILED(rv)) return NS_OK;
+ if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_RECT)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMRect> imageRegionRect;
+ rv = primitiveValue->GetRectValue(getter_AddRefs(imageRegionRect));
+ if (NS_FAILED(rv)) return NS_OK;
+
+ if (imageRegionRect) {
+ // Return NS_ERROR_FAILURE if the image region is invalid so the image
+ // is not drawn, and behavior is similar to XUL menus.
+ int32_t bottom = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetBottom);
+ int32_t right = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetRight);
+ int32_t top = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetTop);
+ int32_t left = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetLeft);
+
+ if (top < 0 || left < 0 || bottom <= top || right <= left)
+ return NS_ERROR_FAILURE;
+
+ mImageRegionRect.SetRect(left, top, right - left, bottom - top);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuItemIconX::LoadIcon(nsIURI* aIconURI)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mIconRequest) {
+ // Another icon request is already in flight. Kill it.
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+
+ mLoadedIcon = false;
+
+ if (!mContent) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDocument> document = mContent->OwnerDoc();
+
+ nsCOMPtr<nsILoadGroup> loadGroup = document->GetDocumentLoadGroup();
+ if (!loadGroup) return NS_ERROR_FAILURE;
+
+ RefPtr<imgLoader> loader = nsContentUtils::GetImgLoaderForDocument(document);
+ if (!loader) return NS_ERROR_FAILURE;
+
+ if (!mSetIcon) {
+ // Set a completely transparent 16x16 image as the icon on this menu item
+ // as a placeholder. This keeps the menu item text displayed in the same
+ // position that it will be displayed when the real icon is loaded, and
+ // prevents it from jumping around or looking misaligned.
+
+ static bool sInitializedPlaceholder;
+ static NSImage* sPlaceholderIconImage;
+ if (!sInitializedPlaceholder) {
+ sInitializedPlaceholder = true;
+
+ // Note that we only create the one and reuse it forever, so this is not a leak.
+ sPlaceholderIconImage = [[NSImage alloc] initWithSize:NSMakeSize(kIconWidth, kIconHeight)];
+ }
+
+ if (!sPlaceholderIconImage) return NS_ERROR_FAILURE;
+
+ if (mNativeMenuItem)
+ [mNativeMenuItem setImage:sPlaceholderIconImage];
+ }
+
+ nsresult rv = loader->LoadImage(aIconURI, nullptr, nullptr,
+ mozilla::net::RP_Default,
+ nullptr, loadGroup, this,
+ nullptr, nullptr, nsIRequest::LOAD_NORMAL, nullptr,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE, EmptyString(),
+ getter_AddRefs(mIconRequest));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+//
+// imgINotificationObserver
+//
+
+NS_IMETHODIMP
+nsMenuItemIconX::Notify(imgIRequest* aRequest,
+ int32_t aType,
+ const nsIntRect* aData)
+{
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ // Make sure the image loaded successfully.
+ uint32_t status = imgIRequest::STATUS_ERROR;
+ if (NS_FAILED(aRequest->GetImageStatus(&status)) ||
+ (status & imgIRequest::STATUS_ERROR)) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ MOZ_ASSERT(image);
+
+ // Ask the image to decode at its intrinsic size.
+ int32_t width = 0, height = 0;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+ return OnFrameComplete(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ if (mIconRequest && mIconRequest == aRequest) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuItemIconX::OnFrameComplete(imgIRequest* aRequest)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (aRequest != mIconRequest)
+ return NS_ERROR_FAILURE;
+
+ // Only support one frame.
+ if (mLoadedIcon)
+ return NS_OK;
+
+ if (!mNativeMenuItem)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<imgIContainer> imageContainer;
+ aRequest->GetImage(getter_AddRefs(imageContainer));
+ if (!imageContainer) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t origWidth = 0, origHeight = 0;
+ imageContainer->GetWidth(&origWidth);
+ imageContainer->GetHeight(&origHeight);
+
+ // If the image region is invalid, don't draw the image to almost match
+ // the behavior of other platforms.
+ if (!mImageRegionRect.IsEmpty() &&
+ (mImageRegionRect.XMost() > origWidth ||
+ mImageRegionRect.YMost() > origHeight)) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mImageRegionRect.IsEmpty()) {
+ mImageRegionRect.SetRect(0, 0, origWidth, origHeight);
+ }
+
+ RefPtr<SourceSurface> surface =
+ imageContainer->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ CGImageRef origImage = NULL;
+ nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &origImage);
+ if (NS_FAILED(rv) || !origImage) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ bool createSubImage = !(mImageRegionRect.x == 0 && mImageRegionRect.y == 0 &&
+ mImageRegionRect.width == origWidth && mImageRegionRect.height == origHeight);
+
+ CGImageRef finalImage = origImage;
+ if (createSubImage) {
+ // if mImageRegionRect is set using CSS, we need to slice a piece out of the overall
+ // image to use as the icon
+ finalImage = ::CGImageCreateWithImageInRect(origImage,
+ ::CGRectMake(mImageRegionRect.x,
+ mImageRegionRect.y,
+ mImageRegionRect.width,
+ mImageRegionRect.height));
+ ::CGImageRelease(origImage);
+ if (!finalImage) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ NSImage *newImage = nil;
+ rv = nsCocoaUtils::CreateNSImageFromCGImage(finalImage, &newImage);
+ if (NS_FAILED(rv) || !newImage) {
+ [mNativeMenuItem setImage:nil];
+ ::CGImageRelease(finalImage);
+ return NS_ERROR_FAILURE;
+ }
+
+ [newImage setSize:NSMakeSize(kIconWidth, kIconHeight)];
+ [mNativeMenuItem setImage:newImage];
+
+ [newImage release];
+ ::CGImageRelease(finalImage);
+
+ mLoadedIcon = true;
+ mSetIcon = true;
+
+ if (mMenuObject) {
+ mMenuObject->IconUpdated();
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMenuItemX.h b/widget/cocoa/nsMenuItemX.h
new file mode 100644
index 000000000..67ae32c99
--- /dev/null
+++ b/widget/cocoa/nsMenuItemX.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuItemX_h_
+#define nsMenuItemX_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsChangeObserver.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsString;
+class nsMenuItemIconX;
+class nsMenuX;
+
+enum {
+ knsMenuItemNoModifier = 0,
+ knsMenuItemShiftModifier = (1 << 0),
+ knsMenuItemAltModifier = (1 << 1),
+ knsMenuItemControlModifier = (1 << 2),
+ knsMenuItemCommandModifier = (1 << 3)
+};
+
+enum EMenuItemType {
+ eRegularMenuItemType = 0,
+ eCheckboxMenuItemType,
+ eRadioMenuItemType,
+ eSeparatorMenuItemType
+};
+
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuItemX : public nsMenuObjectX,
+ public nsChangeObserver
+{
+public:
+ nsMenuItemX();
+ virtual ~nsMenuItemX();
+
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override {return (void*)mNativeMenuItem;}
+ nsMenuObjectTypeX MenuObjectType() override {return eMenuItemObjectType;}
+
+ // nsMenuItemX
+ nsresult Create(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
+ nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode);
+ nsresult SetChecked(bool aIsChecked);
+ EMenuItemType GetMenuItemType();
+ void DoCommand();
+ nsresult DispatchDOMEvent(const nsString &eventName, bool* preventDefaultCalled);
+ void SetupIcon();
+
+protected:
+ void UncheckRadioSiblings(nsIContent* inCheckedElement);
+ void SetKeyEquiv();
+
+ EMenuItemType mType;
+ // nsMenuItemX objects should always have a valid native menu item.
+ NSMenuItem* mNativeMenuItem; // [strong]
+ nsMenuX* mMenuParent; // [weak]
+ nsMenuGroupOwnerX* mMenuGroupOwner; // [weak]
+ nsCOMPtr<nsIContent> mCommandContent;
+ // The icon object should never outlive its creating nsMenuItemX object.
+ RefPtr<nsMenuItemIconX> mIcon;
+ bool mIsChecked;
+};
+
+#endif // nsMenuItemX_h_
diff --git a/widget/cocoa/nsMenuItemX.mm b/widget/cocoa/nsMenuItemX.mm
new file mode 100644
index 000000000..114b69f43
--- /dev/null
+++ b/widget/cocoa/nsMenuItemX.mm
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMenuItemX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemIconX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+
+#include "nsObjCExceptions.h"
+
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+
+#include "mozilla/dom/Element.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMEvent.h"
+
+nsMenuItemX::nsMenuItemX()
+{
+ mType = eRegularMenuItemType;
+ mNativeMenuItem = nil;
+ mMenuParent = nullptr;
+ mMenuGroupOwner = nullptr;
+ mIsChecked = false;
+
+ MOZ_COUNT_CTOR(nsMenuItemX);
+}
+
+nsMenuItemX::~nsMenuItemX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Prevent the icon object from outliving us.
+ if (mIcon)
+ mIcon->Destroy();
+
+ // autorelease the native menu item so that anything else happening to this
+ // object happens before the native menu item actually dies
+ [mNativeMenuItem autorelease];
+
+ if (mContent)
+ mMenuGroupOwner->UnregisterForContentChanges(mContent);
+ if (mCommandContent)
+ mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
+
+ MOZ_COUNT_DTOR(nsMenuItemX);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuItemX::Create(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
+ nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mType = aItemType;
+ mMenuParent = aParent;
+ mContent = aNode;
+
+ mMenuGroupOwner = aMenuGroupOwner;
+ NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one!");
+
+ mMenuGroupOwner->RegisterForContentChanges(mContent, this);
+
+ nsIDocument *doc = mContent->GetUncomposedDoc();
+
+ // if we have a command associated with this menu item, register for changes
+ // to the command DOM node
+ if (doc) {
+ nsAutoString ourCommand;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::command, ourCommand);
+
+ if (!ourCommand.IsEmpty()) {
+ nsIContent *commandElement = doc->GetElementById(ourCommand);
+
+ if (commandElement) {
+ mCommandContent = commandElement;
+ // register to observe the command DOM element
+ mMenuGroupOwner->RegisterForContentChanges(mCommandContent, this);
+ }
+ }
+ }
+
+ // decide enabled state based on command content if it exists, otherwise do it based
+ // on our own content
+ bool isEnabled;
+ if (mCommandContent)
+ isEnabled = !mCommandContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
+ else
+ isEnabled = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
+
+ // set up the native menu item
+ if (mType == eSeparatorMenuItemType) {
+ mNativeMenuItem = [[NSMenuItem separatorItem] retain];
+ }
+ else {
+ NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(aLabel);
+ mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
+
+ [mNativeMenuItem setEnabled:(BOOL)isEnabled];
+
+ SetChecked(mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters));
+ SetKeyEquiv();
+ }
+
+ mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
+ if (!mIcon)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsMenuItemX::SetChecked(bool aIsChecked)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mIsChecked = aIsChecked;
+
+ // update the content model. This will also handle unchecking our siblings
+ // if we are a radiomenu
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
+ mIsChecked ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"), true);
+
+ // update native menu item
+ if (mIsChecked)
+ [mNativeMenuItem setState:NSOnState];
+ else
+ [mNativeMenuItem setState:NSOffState];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+EMenuItemType nsMenuItemX::GetMenuItemType()
+{
+ return mType;
+}
+
+// Executes the "cached" javaScript command.
+// Returns NS_OK if the command was executed properly, otherwise an error code.
+void nsMenuItemX::DoCommand()
+{
+ // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
+ if (mType == eCheckboxMenuItemType ||
+ (mType == eRadioMenuItemType && !mIsChecked)) {
+ if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
+ nsGkAtoms::_false, eCaseMatters))
+ SetChecked(!mIsChecked);
+ /* the AttributeChanged code will update all the internal state */
+ }
+
+ nsMenuUtilsX::DispatchCommandTo(mContent);
+}
+
+nsresult nsMenuItemX::DispatchDOMEvent(const nsString &eventName, bool *preventDefaultCalled)
+{
+ if (!mContent)
+ return NS_ERROR_FAILURE;
+
+ // get owner document for content
+ nsCOMPtr<nsIDocument> parentDoc = mContent->OwnerDoc();
+
+ // get interface for creating DOM events from content owner document
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(parentDoc);
+ if (!domDoc) {
+ NS_WARNING("Failed to QI parent nsIDocument to nsIDOMDocument");
+ return NS_ERROR_FAILURE;
+ }
+
+ // create DOM event
+ nsCOMPtr<nsIDOMEvent> event;
+ nsresult rv = domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create nsIDOMEvent");
+ return rv;
+ }
+ event->InitEvent(eventName, true, true);
+
+ // mark DOM event as trusted
+ event->SetTrusted(true);
+
+ // send DOM event
+ rv = mContent->DispatchEvent(event, preventDefaultCalled);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to send DOM event via EventTarget");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// Walk the sibling list looking for nodes with the same name and
+// uncheck them all.
+void nsMenuItemX::UncheckRadioSiblings(nsIContent* inCheckedContent)
+{
+ nsAutoString myGroupName;
+ inCheckedContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, myGroupName);
+ if (!myGroupName.Length()) // no groupname, nothing to do
+ return;
+
+ nsCOMPtr<nsIContent> parent = inCheckedContent->GetParent();
+ if (!parent)
+ return;
+
+ // loop over siblings
+ uint32_t count = parent->GetChildCount();
+ for (uint32_t i = 0; i < count; i++) {
+ nsIContent *sibling = parent->GetChildAt(i);
+ if (sibling) {
+ if (sibling != inCheckedContent) { // skip this node
+ // if the current sibling is in the same group, clear it
+ if (sibling->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ myGroupName, eCaseMatters))
+ sibling->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, NS_LITERAL_STRING("false"), true);
+ }
+ }
+ }
+}
+
+void nsMenuItemX::SetKeyEquiv()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Set key shortcut and modifiers
+ nsAutoString keyValue;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
+ if (!keyValue.IsEmpty() && mContent->GetUncomposedDoc()) {
+ nsIContent *keyContent = mContent->GetUncomposedDoc()->GetElementById(keyValue);
+ if (keyContent) {
+ nsAutoString keyChar;
+ bool hasKey = keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
+
+ if (!hasKey || keyChar.IsEmpty()) {
+ nsAutoString keyCodeName;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeName);
+ uint32_t charCode =
+ nsCocoaUtils::ConvertGeckoNameToMacCharCode(keyCodeName);
+ if (charCode) {
+ keyChar.Assign(charCode);
+ }
+ else {
+ keyChar.Assign(NS_LITERAL_STRING(" "));
+ }
+ }
+
+ nsAutoString modifiersStr;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
+ uint8_t modifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
+
+ unsigned int macModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(modifiers);
+ [mNativeMenuItem setKeyEquivalentModifierMask:macModifiers];
+
+ NSString *keyEquivalent = [[NSString stringWithCharacters:(unichar*)keyChar.get()
+ length:keyChar.Length()] lowercaseString];
+ if ([keyEquivalent isEqualToString:@" "])
+ [mNativeMenuItem setKeyEquivalent:@""];
+ else
+ [mNativeMenuItem setKeyEquivalent:keyEquivalent];
+
+ return;
+ }
+ }
+
+ // if the key was removed, clear the key
+ [mNativeMenuItem setKeyEquivalent:@""];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+//
+// nsChangeObserver
+//
+
+void
+nsMenuItemX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aContent)
+ return;
+
+ if (aContent == mContent) { // our own content node changed
+ if (aAttribute == nsGkAtoms::checked) {
+ // if we're a radio menu, uncheck our sibling radio items. No need to
+ // do any of this if we're just a normal check menu.
+ if (mType == eRadioMenuItemType) {
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters))
+ UncheckRadioSiblings(mContent);
+ }
+ mMenuParent->SetRebuild(true);
+ }
+ else if (aAttribute == nsGkAtoms::hidden ||
+ aAttribute == nsGkAtoms::collapsed ||
+ aAttribute == nsGkAtoms::label) {
+ mMenuParent->SetRebuild(true);
+ }
+ else if (aAttribute == nsGkAtoms::key) {
+ SetKeyEquiv();
+ }
+ else if (aAttribute == nsGkAtoms::image) {
+ SetupIcon();
+ }
+ else if (aAttribute == nsGkAtoms::disabled) {
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
+ [mNativeMenuItem setEnabled:NO];
+ else
+ [mNativeMenuItem setEnabled:YES];
+ }
+ }
+ else if (aContent == mCommandContent) {
+ // the only thing that really matters when the menu isn't showing is the
+ // enabled state since it enables/disables keyboard commands
+ if (aAttribute == nsGkAtoms::disabled) {
+ // first we sync our menu item DOM node with the command DOM node
+ nsAutoString commandDisabled;
+ nsAutoString menuDisabled;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled);
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, menuDisabled);
+ if (!commandDisabled.Equals(menuDisabled)) {
+ // The menu's disabled state needs to be updated to match the command.
+ if (commandDisabled.IsEmpty())
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+ else
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled, true);
+ }
+ // now we sync our native menu item with the command DOM node
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
+ [mNativeMenuItem setEnabled:NO];
+ else
+ [mNativeMenuItem setEnabled:YES];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuItemX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, int32_t aIndexInContainer)
+{
+ if (aChild == mCommandContent) {
+ mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
+ mCommandContent = nullptr;
+ }
+
+ mMenuParent->SetRebuild(true);
+}
+
+void nsMenuItemX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
+ nsIContent *aChild)
+{
+ mMenuParent->SetRebuild(true);
+}
+
+void nsMenuItemX::SetupIcon()
+{
+ if (mIcon)
+ mIcon->SetupIcon();
+}
diff --git a/widget/cocoa/nsMenuUtilsX.h b/widget/cocoa/nsMenuUtilsX.h
new file mode 100644
index 000000000..1571cdfb0
--- /dev/null
+++ b/widget/cocoa/nsMenuUtilsX.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuUtilsX_h_
+#define nsMenuUtilsX_h_
+
+#include "nscore.h"
+#include "nsMenuBaseX.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsIContent;
+class nsString;
+class nsMenuBarX;
+
+// Namespace containing utility functions used in our native menu implementation.
+namespace nsMenuUtilsX
+{
+ void DispatchCommandTo(nsIContent* aTargetContent);
+ NSString* GetTruncatedCocoaLabel(const nsString& itemLabel);
+ uint8_t GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute);
+ unsigned int MacModifiersForGeckoModifiers(uint8_t geckoModifiers);
+ nsMenuBarX* GetHiddenWindowMenuBar(); // returned object is not retained
+ NSMenuItem* GetStandardEditMenuItem(); // returned object is not retained
+ bool NodeIsHiddenOrCollapsed(nsIContent* inContent);
+ int CalculateNativeInsertionPoint(nsMenuObjectX* aParent, nsMenuObjectX* aChild);
+} // namespace nsMenuUtilsX
+
+#endif // nsMenuUtilsX_h_
diff --git a/widget/cocoa/nsMenuUtilsX.mm b/widget/cocoa/nsMenuUtilsX.mm
new file mode 100644
index 000000000..db6471712
--- /dev/null
+++ b/widget/cocoa/nsMenuUtilsX.mm
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Event.h"
+#include "nsMenuUtilsX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsStandaloneNativeMenu.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMXULCommandEvent.h"
+#include "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+
+using namespace mozilla;
+
+void nsMenuUtilsX::DispatchCommandTo(nsIContent* aTargetContent)
+{
+ NS_PRECONDITION(aTargetContent, "null ptr");
+
+ nsIDocument* doc = aTargetContent->OwnerDoc();
+ if (doc) {
+ ErrorResult rv;
+ RefPtr<dom::Event> event =
+ doc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"), rv);
+ nsCOMPtr<nsIDOMXULCommandEvent> command = do_QueryObject(event);
+
+ // FIXME: Should probably figure out how to init this with the actual
+ // pressed keys, but this is a big old edge case anyway. -dwh
+ if (command &&
+ NS_SUCCEEDED(command->InitCommandEvent(NS_LITERAL_STRING("command"),
+ true, true,
+ doc->GetInnerWindow(), 0,
+ false, false, false,
+ false, nullptr))) {
+ event->SetTrusted(true);
+ bool dummy;
+ aTargetContent->DispatchEvent(event, &dummy);
+ }
+ }
+}
+
+NSString* nsMenuUtilsX::GetTruncatedCocoaLabel(const nsString& itemLabel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // We want to truncate long strings to some reasonable pixel length but there is no
+ // good API for doing that which works for all OS versions and architectures. For now
+ // we'll do nothing for consistency and depend on good user interface design to limit
+ // string lengths.
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(itemLabel.get())
+ length:itemLabel.Length()];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+uint8_t nsMenuUtilsX::GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute)
+{
+ uint8_t modifiers = knsMenuItemNoModifier;
+ char* str = ToNewCString(modifiersAttribute);
+ char* newStr;
+ char* token = strtok_r(str, ", \t", &newStr);
+ while (token != NULL) {
+ if (strcmp(token, "shift") == 0)
+ modifiers |= knsMenuItemShiftModifier;
+ else if (strcmp(token, "alt") == 0)
+ modifiers |= knsMenuItemAltModifier;
+ else if (strcmp(token, "control") == 0)
+ modifiers |= knsMenuItemControlModifier;
+ else if ((strcmp(token, "accel") == 0) ||
+ (strcmp(token, "meta") == 0)) {
+ modifiers |= knsMenuItemCommandModifier;
+ }
+ token = strtok_r(newStr, ", \t", &newStr);
+ }
+ free(str);
+
+ return modifiers;
+}
+
+unsigned int nsMenuUtilsX::MacModifiersForGeckoModifiers(uint8_t geckoModifiers)
+{
+ unsigned int macModifiers = 0;
+
+ if (geckoModifiers & knsMenuItemShiftModifier)
+ macModifiers |= NSShiftKeyMask;
+ if (geckoModifiers & knsMenuItemAltModifier)
+ macModifiers |= NSAlternateKeyMask;
+ if (geckoModifiers & knsMenuItemControlModifier)
+ macModifiers |= NSControlKeyMask;
+ if (geckoModifiers & knsMenuItemCommandModifier)
+ macModifiers |= NSCommandKeyMask;
+
+ return macModifiers;
+}
+
+nsMenuBarX* nsMenuUtilsX::GetHiddenWindowMenuBar()
+{
+ nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget();
+ if (hiddenWindowWidgetNoCOMPtr)
+ return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)->GetMenuBar();
+ else
+ return nullptr;
+}
+
+// It would be nice if we could localize these edit menu names.
+NSMenuItem* nsMenuUtilsX::GetStandardEditMenuItem()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // In principle we should be able to allocate this once and then always
+ // return the same object. But weird interactions happen between native
+ // app-modal dialogs and Gecko-modal dialogs that open above them. So what
+ // we return here isn't always released before it needs to be added to
+ // another menu. See bmo bug 468393.
+ NSMenuItem* standardEditMenuItem =
+ [[[NSMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""] autorelease];
+ NSMenu* standardEditMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
+ [standardEditMenuItem setSubmenu:standardEditMenu];
+ [standardEditMenu release];
+
+ // Add Undo
+ NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"];
+ [standardEditMenu addItem:undoItem];
+ [undoItem release];
+
+ // Add Redo
+ NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:@"Z"];
+ [standardEditMenu addItem:redoItem];
+ [redoItem release];
+
+ // Add separator
+ [standardEditMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add Cut
+ NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"];
+ [standardEditMenu addItem:cutItem];
+ [cutItem release];
+
+ // Add Copy
+ NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
+ [standardEditMenu addItem:copyItem];
+ [copyItem release];
+
+ // Add Paste
+ NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"];
+ [standardEditMenu addItem:pasteItem];
+ [pasteItem release];
+
+ // Add Delete
+ NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""];
+ [standardEditMenu addItem:deleteItem];
+ [deleteItem release];
+
+ // Add Select All
+ NSMenuItem* selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
+ [standardEditMenu addItem:selectAllItem];
+ [selectAllItem release];
+
+ return standardEditMenuItem;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* inContent)
+{
+ return (inContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters) ||
+ inContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::collapsed,
+ nsGkAtoms::_true, eCaseMatters));
+}
+
+// Determines how many items are visible among the siblings in a menu that are
+// before the given child. This will not count the application menu.
+int nsMenuUtilsX::CalculateNativeInsertionPoint(nsMenuObjectX* aParent,
+ nsMenuObjectX* aChild)
+{
+ int insertionPoint = 0;
+ nsMenuObjectTypeX parentType = aParent->MenuObjectType();
+ if (parentType == eMenuBarObjectType) {
+ nsMenuBarX* menubarParent = static_cast<nsMenuBarX*>(aParent);
+ uint32_t numMenus = menubarParent->GetMenuCount();
+ for (uint32_t i = 0; i < numMenus; i++) {
+ nsMenuX* currMenu = menubarParent->GetMenuAt(i);
+ if (currMenu == aChild)
+ return insertionPoint; // we found ourselves, break out
+ if (currMenu && [currMenu->NativeMenuItem() menu])
+ insertionPoint++;
+ }
+ }
+ else if (parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ nsMenuX* menuParent;
+ if (parentType == eSubmenuObjectType)
+ menuParent = static_cast<nsMenuX*>(aParent);
+ else
+ menuParent = static_cast<nsStandaloneNativeMenu*>(aParent)->GetMenuXObject();
+
+ uint32_t numItems = menuParent->GetItemCount();
+ for (uint32_t i = 0; i < numItems; i++) {
+ // Using GetItemAt instead of GetVisibleItemAt to avoid O(N^2)
+ nsMenuObjectX* currItem = menuParent->GetItemAt(i);
+ if (currItem == aChild)
+ return insertionPoint; // we found ourselves, break out
+ NSMenuItem* nativeItem = nil;
+ nsMenuObjectTypeX currItemType = currItem->MenuObjectType();
+ if (currItemType == eSubmenuObjectType)
+ nativeItem = static_cast<nsMenuX*>(currItem)->NativeMenuItem();
+ else
+ nativeItem = (NSMenuItem*)(currItem->NativeData());
+ if ([nativeItem menu])
+ insertionPoint++;
+ }
+ }
+ return insertionPoint;
+}
diff --git a/widget/cocoa/nsMenuX.h b/widget/cocoa/nsMenuX.h
new file mode 100644
index 000000000..7b5146a0b
--- /dev/null
+++ b/widget/cocoa/nsMenuX.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuX_h_
+#define nsMenuX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsCOMPtr.h"
+#include "nsChangeObserver.h"
+
+class nsMenuX;
+class nsMenuItemIconX;
+class nsMenuItemX;
+class nsIWidget;
+
+// MenuDelegate is used to receive Cocoa notifications for setting
+// up carbon events. Protocol is defined as of 10.6 SDK.
+@interface MenuDelegate : NSObject < NSMenuDelegate >
+{
+ nsMenuX* mGeckoMenu; // weak ref
+}
+- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu;
+@end
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuX : public nsMenuObjectX,
+ public nsChangeObserver
+{
+public:
+ nsMenuX();
+ virtual ~nsMenuX();
+
+ // If > 0, the OS is indexing all the app's menus (triggered by opening
+ // Help menu on Leopard and higher). There are some things that are
+ // unsafe to do while this is happening.
+ static int32_t sIndexingMenuLevel;
+
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override {return (void*)mNativeMenu;}
+ nsMenuObjectTypeX MenuObjectType() override {return eSubmenuObjectType;}
+ void IconUpdated() override { mParent->IconUpdated(); }
+
+ // nsMenuX
+ nsresult Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode);
+ uint32_t GetItemCount();
+ nsMenuObjectX* GetItemAt(uint32_t aPos);
+ nsresult GetVisibleItemCount(uint32_t &aCount);
+ nsMenuObjectX* GetVisibleItemAt(uint32_t aPos);
+ nsEventStatus MenuOpened();
+ void MenuClosed();
+ void SetRebuild(bool aMenuEvent);
+ NSMenuItem* NativeMenuItem();
+ nsresult SetupIcon();
+
+ static bool IsXULHelpMenu(nsIContent* aMenuContent);
+
+protected:
+ void MenuConstruct();
+ nsresult RemoveAll();
+ nsresult SetEnabled(bool aIsEnabled);
+ nsresult GetEnabled(bool* aIsEnabled);
+ void GetMenuPopupContent(nsIContent** aResult);
+ bool OnOpen();
+ bool OnClose();
+ nsresult AddMenuItem(nsMenuItemX* aMenuItem);
+ nsMenuX* AddMenu(mozilla::UniquePtr<nsMenuX> aMenu);
+ void LoadMenuItem(nsIContent* inMenuItemContent);
+ void LoadSubMenu(nsIContent* inMenuContent);
+ GeckoNSMenu* CreateMenuWithGeckoString(nsString& menuTitle);
+
+ nsTArray<mozilla::UniquePtr<nsMenuObjectX>> mMenuObjectsArray;
+ nsString mLabel;
+ uint32_t mVisibleItemsCount; // cache
+ nsMenuObjectX* mParent; // [weak]
+ nsMenuGroupOwnerX* mMenuGroupOwner; // [weak]
+ // The icon object should never outlive its creating nsMenuX object.
+ RefPtr<nsMenuItemIconX> mIcon;
+ GeckoNSMenu* mNativeMenu; // [strong]
+ MenuDelegate* mMenuDelegate; // [strong]
+ // nsMenuX objects should always have a valid native menu item.
+ NSMenuItem* mNativeMenuItem; // [strong]
+ bool mIsEnabled;
+ bool mDestroyHandlerCalled;
+ bool mNeedsRebuild;
+ bool mConstructed;
+ bool mVisible;
+ bool mXBLAttached;
+};
+
+#endif // nsMenuX_h_
diff --git a/widget/cocoa/nsMenuX.mm b/widget/cocoa/nsMenuX.mm
new file mode 100644
index 000000000..757221eac
--- /dev/null
+++ b/widget/cocoa/nsMenuX.mm
@@ -0,0 +1,1051 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsMenuItemIconX.h"
+#include "nsStandaloneNativeMenu.h"
+
+#include "nsObjCExceptions.h"
+
+#include "nsToolkit.h"
+#include "nsCocoaUtils.h"
+#include "nsCOMPtr.h"
+#include "prinrval.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "plstr.h"
+#include "nsGkAtoms.h"
+#include "nsCRT.h"
+#include "nsBaseWidget.h"
+
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDocumentObserver.h"
+#include "nsIComponentManager.h"
+#include "nsIRollupListener.h"
+#include "nsIDOMElement.h"
+#include "nsBindingManager.h"
+#include "nsIServiceManager.h"
+#include "nsXULPopupManager.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "jsapi.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsIXPConnect.h"
+
+#include "mozilla/MouseEvents.h"
+
+using namespace mozilla;
+
+static bool gConstructingMenu = false;
+static bool gMenuMethodsSwizzled = false;
+
+int32_t nsMenuX::sIndexingMenuLevel = 0;
+
+
+//
+// Objective-C class used for representedObject
+//
+
+@implementation MenuItemInfo
+
+- (id) initWithMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner
+{
+ if ((self = [super init]) != nil) {
+ [self setMenuGroupOwner:aMenuGroupOwner];
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [self setMenuGroupOwner:nullptr];
+ [super dealloc];
+}
+
+- (nsMenuGroupOwnerX *) menuGroupOwner
+{
+ return mMenuGroupOwner;
+}
+
+- (void) setMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner
+{
+ // weak reference as the nsMenuGroupOwnerX owns all of its sub-objects
+ mMenuGroupOwner = aMenuGroupOwner;
+ if (aMenuGroupOwner) {
+ aMenuGroupOwner->AddMenuItemInfoToSet(self);
+ }
+}
+
+@end
+
+
+//
+// nsMenuX
+//
+
+nsMenuX::nsMenuX()
+: mVisibleItemsCount(0), mParent(nullptr), mMenuGroupOwner(nullptr),
+ mNativeMenu(nil), mNativeMenuItem(nil), mIsEnabled(true),
+ mDestroyHandlerCalled(false), mNeedsRebuild(true),
+ mConstructed(false), mVisible(true), mXBLAttached(false)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!gMenuMethodsSwizzled) {
+ nsToolkit::SwizzleMethods([NSMenu class], @selector(_addItem:toTable:),
+ @selector(nsMenuX_NSMenu_addItem:toTable:), true);
+ nsToolkit::SwizzleMethods([NSMenu class], @selector(_removeItem:fromTable:),
+ @selector(nsMenuX_NSMenu_removeItem:fromTable:), true);
+ // On SnowLeopard the Shortcut framework (which contains the
+ // SCTGRLIndex class) is loaded on demand, whenever the user first opens
+ // a menu (which normally hasn't happened yet). So we need to load it
+ // here explicitly.
+ dlopen("/System/Library/PrivateFrameworks/Shortcut.framework/Shortcut", RTLD_LAZY);
+ Class SCTGRLIndexClass = ::NSClassFromString(@"SCTGRLIndex");
+ nsToolkit::SwizzleMethods(SCTGRLIndexClass, @selector(indexMenuBarDynamically),
+ @selector(nsMenuX_SCTGRLIndex_indexMenuBarDynamically));
+
+ gMenuMethodsSwizzled = true;
+ }
+
+ mMenuDelegate = [[MenuDelegate alloc] initWithGeckoMenu:this];
+
+ if (!nsMenuBarX::sNativeEventTarget)
+ nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
+
+ MOZ_COUNT_CTOR(nsMenuX);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsMenuX::~nsMenuX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Prevent the icon object from outliving us.
+ if (mIcon)
+ mIcon->Destroy();
+
+ RemoveAll();
+
+ [mNativeMenu setDelegate:nil];
+ [mNativeMenu release];
+ [mMenuDelegate release];
+ // autorelease the native menu item so that anything else happening to this
+ // object happens before the native menu item actually dies
+ [mNativeMenuItem autorelease];
+
+ // alert the change notifier we don't care no more
+ if (mContent)
+ mMenuGroupOwner->UnregisterForContentChanges(mContent);
+
+ MOZ_COUNT_DTOR(nsMenuX);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuX::Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mContent = aNode;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel);
+ mNativeMenu = CreateMenuWithGeckoString(mLabel);
+
+ // register this menu to be notified when changes are made to our content object
+ mMenuGroupOwner = aMenuGroupOwner; // weak ref
+ NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one");
+ mMenuGroupOwner->RegisterForContentChanges(mContent, this);
+
+ mParent = aParent;
+ // our parent could be either a menu bar (if we're toplevel) or a menu (if we're a submenu)
+
+#ifdef DEBUG
+ nsMenuObjectTypeX parentType =
+#endif
+ mParent->MenuObjectType();
+ NS_ASSERTION((parentType == eMenuBarObjectType || parentType == eSubmenuObjectType || parentType == eStandaloneNativeMenuObjectType),
+ "Menu parent not a menu bar, menu, or native menu!");
+
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent))
+ mVisible = false;
+ if (mContent->GetChildCount() == 0)
+ mVisible = false;
+
+ NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
+ mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
+ [mNativeMenuItem setSubmenu:mNativeMenu];
+
+ SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters));
+
+ // We call MenuConstruct here because keyboard commands are dependent upon
+ // native menu items being created. If we only call MenuConstruct when a menu
+ // is actually selected, then we can't access keyboard commands until the
+ // menu gets selected, which is bad.
+ MenuConstruct();
+
+ mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsMenuX::AddMenuItem(nsMenuItemX* aMenuItem)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aMenuItem)
+ return NS_ERROR_INVALID_ARG;
+
+ mMenuObjectsArray.AppendElement(aMenuItem);
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(aMenuItem->Content()))
+ return NS_OK;
+ ++mVisibleItemsCount;
+
+ NSMenuItem* newNativeMenuItem = (NSMenuItem*)aMenuItem->NativeData();
+
+ // add the menu item to this menu
+ [mNativeMenu addItem:newNativeMenuItem];
+
+ // set up target/action
+ [newNativeMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [newNativeMenuItem setAction:@selector(menuItemHit:)];
+
+ // set its command. we get the unique command id from the menubar
+ [newNativeMenuItem setTag:mMenuGroupOwner->RegisterForCommand(aMenuItem)];
+ MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:mMenuGroupOwner];
+ [newNativeMenuItem setRepresentedObject:info];
+ [info release];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsMenuX* nsMenuX::AddMenu(UniquePtr<nsMenuX> aMenu)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // aMenu transfers ownership to mMenuObjectsArray and becomes nullptr, so
+ // we need to keep a raw pointer to access it conveniently.
+ nsMenuX* menu = aMenu.get();
+ mMenuObjectsArray.AppendElement(Move(aMenu));
+
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
+ return menu;
+ }
+
+ ++mVisibleItemsCount;
+
+ // We have to add a menu item and then associate the menu with it
+ NSMenuItem* newNativeMenuItem = menu->NativeMenuItem();
+ if (newNativeMenuItem) {
+ [mNativeMenu addItem:newNativeMenuItem];
+ [newNativeMenuItem setSubmenu:(NSMenu*)menu->NativeData()];
+ }
+
+ return menu;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
+}
+
+// Includes all items, including hidden/collapsed ones
+uint32_t nsMenuX::GetItemCount()
+{
+ return mMenuObjectsArray.Length();
+}
+
+// Includes all items, including hidden/collapsed ones
+nsMenuObjectX* nsMenuX::GetItemAt(uint32_t aPos)
+{
+ if (aPos >= (uint32_t)mMenuObjectsArray.Length())
+ return NULL;
+
+ return mMenuObjectsArray[aPos].get();
+}
+
+// Only includes visible items
+nsresult nsMenuX::GetVisibleItemCount(uint32_t &aCount)
+{
+ aCount = mVisibleItemsCount;
+ return NS_OK;
+}
+
+// Only includes visible items. Note that this is provides O(N) access
+// If you need to iterate or search, consider using GetItemAt and doing your own filtering
+nsMenuObjectX* nsMenuX::GetVisibleItemAt(uint32_t aPos)
+{
+
+ uint32_t count = mMenuObjectsArray.Length();
+ if (aPos >= mVisibleItemsCount || aPos >= count)
+ return NULL;
+
+ // If there are no invisible items, can provide direct access
+ if (mVisibleItemsCount == count)
+ return mMenuObjectsArray[aPos].get();
+
+ // Otherwise, traverse the array until we find the the item we're looking for.
+ nsMenuObjectX* item;
+ uint32_t visibleNodeIndex = 0;
+ for (uint32_t i = 0; i < count; i++) {
+ item = mMenuObjectsArray[i].get();
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(item->Content())) {
+ if (aPos == visibleNodeIndex) {
+ // we found the visible node we're looking for, return it
+ return item;
+ }
+ visibleNodeIndex++;
+ }
+ }
+
+ return NULL;
+}
+
+nsresult nsMenuX::RemoveAll()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mNativeMenu) {
+ // clear command id's
+ int itemCount = [mNativeMenu numberOfItems];
+ for (int i = 0; i < itemCount; i++)
+ mMenuGroupOwner->UnregisterCommand((uint32_t)[[mNativeMenu itemAtIndex:i] tag]);
+ // get rid of Cocoa menu items
+ for (int i = [mNativeMenu numberOfItems] - 1; i >= 0; i--)
+ [mNativeMenu removeItemAtIndex:i];
+ }
+
+ mMenuObjectsArray.Clear();
+ mVisibleItemsCount = 0;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsEventStatus nsMenuX::MenuOpened()
+{
+ // Open the node.
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true);
+
+ // Fire a handler. If we're told to stop, don't build the menu at all
+ bool keepProcessing = OnOpen();
+
+ if (!mNeedsRebuild || !keepProcessing)
+ return nsEventStatus_eConsumeNoDefault;
+
+ if (!mConstructed || mNeedsRebuild) {
+ if (mNeedsRebuild)
+ RemoveAll();
+
+ MenuConstruct();
+ mConstructed = true;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupShown, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+void nsMenuX::MenuClosed()
+{
+ if (mConstructed) {
+ // Don't close if a handler tells us to stop.
+ if (!OnClose())
+ return;
+
+ if (mNeedsRebuild)
+ mConstructed = false;
+
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHidden, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+
+ mDestroyHandlerCalled = true;
+ mConstructed = false;
+ }
+}
+
+void nsMenuX::MenuConstruct()
+{
+ mConstructed = false;
+ gConstructingMenu = true;
+
+ // reset destroy handler flag so that we'll know to fire it next time this menu goes away.
+ mDestroyHandlerCalled = false;
+
+ //printf("nsMenuX::MenuConstruct called for %s = %d \n", NS_LossyConvertUTF16toASCII(mLabel).get(), mNativeMenu);
+
+ // Retrieve our menupopup.
+ nsCOMPtr<nsIContent> menuPopup;
+ GetMenuPopupContent(getter_AddRefs(menuPopup));
+ if (!menuPopup) {
+ gConstructingMenu = false;
+ return;
+ }
+
+ // bug 365405: Manually wrap the menupopup node to make sure it's bounded
+ if (!mXBLAttached) {
+ nsresult rv;
+ nsCOMPtr<nsIXPConnect> xpconnect =
+ do_GetService(nsIXPConnect::GetCID(), &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsIDocument* ownerDoc = menuPopup->OwnerDoc();
+ dom::AutoJSAPI jsapi;
+ if (ownerDoc && jsapi.Init(ownerDoc->GetInnerWindow())) {
+ JSContext* cx = jsapi.cx();
+ JS::RootedObject ignoredObj(cx);
+ xpconnect->WrapNative(cx, JS::CurrentGlobalOrNull(cx), menuPopup,
+ NS_GET_IID(nsISupports), ignoredObj.address());
+ mXBLAttached = true;
+ }
+ }
+ }
+
+ // Iterate over the kids
+ uint32_t count = menuPopup->GetChildCount();
+ for (uint32_t i = 0; i < count; i++) {
+ nsIContent *child = menuPopup->GetChildAt(i);
+ if (child) {
+ // depending on the type, create a menu item, separator, or submenu
+ if (child->IsAnyOfXULElements(nsGkAtoms::menuitem,
+ nsGkAtoms::menuseparator)) {
+ LoadMenuItem(child);
+ } else if (child->IsXULElement(nsGkAtoms::menu)) {
+ LoadSubMenu(child);
+ }
+ }
+ } // for each menu item
+
+ gConstructingMenu = false;
+ mNeedsRebuild = false;
+ // printf("Done building, mMenuObjectsArray.Count() = %d \n", mMenuObjectsArray.Count());
+}
+
+void nsMenuX::SetRebuild(bool aNeedsRebuild)
+{
+ if (!gConstructingMenu)
+ mNeedsRebuild = aNeedsRebuild;
+}
+
+nsresult nsMenuX::SetEnabled(bool aIsEnabled)
+{
+ if (aIsEnabled != mIsEnabled) {
+ // we always want to rebuild when this changes
+ mIsEnabled = aIsEnabled;
+ [mNativeMenuItem setEnabled:(BOOL)mIsEnabled];
+ }
+ return NS_OK;
+}
+
+nsresult nsMenuX::GetEnabled(bool* aIsEnabled)
+{
+ NS_ENSURE_ARG_POINTER(aIsEnabled);
+ *aIsEnabled = mIsEnabled;
+ return NS_OK;
+}
+
+GeckoNSMenu* nsMenuX::CreateMenuWithGeckoString(nsString& menuTitle)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* title = [NSString stringWithCharacters:(UniChar*)menuTitle.get() length:menuTitle.Length()];
+ GeckoNSMenu* myMenu = [[GeckoNSMenu alloc] initWithTitle:title];
+ [myMenu setDelegate:mMenuDelegate];
+
+ // We don't want this menu to auto-enable menu items because then Cocoa
+ // overrides our decisions and things get incorrectly enabled/disabled.
+ [myMenu setAutoenablesItems:NO];
+
+ // we used to install Carbon event handlers here, but since NSMenu* doesn't
+ // create its underlying MenuRef until just before display, we delay until
+ // that happens. Now we install the event handlers when Cocoa notifies
+ // us that a menu is about to display - see the Cocoa MenuDelegate class.
+
+ return myMenu;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void nsMenuX::LoadMenuItem(nsIContent* inMenuItemContent)
+{
+ if (!inMenuItemContent)
+ return;
+
+ nsAutoString menuitemName;
+ inMenuItemContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, menuitemName);
+
+ // printf("menuitem %s \n", NS_LossyConvertUTF16toASCII(menuitemName).get());
+
+ EMenuItemType itemType = eRegularMenuItemType;
+ if (inMenuItemContent->IsXULElement(nsGkAtoms::menuseparator)) {
+ itemType = eSeparatorMenuItemType;
+ }
+ else {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr};
+ switch (inMenuItemContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
+ strings, eCaseMatters)) {
+ case 0: itemType = eCheckboxMenuItemType; break;
+ case 1: itemType = eRadioMenuItemType; break;
+ }
+ }
+
+ // Create the item.
+ nsMenuItemX* menuItem = new nsMenuItemX();
+ if (!menuItem)
+ return;
+
+ nsresult rv = menuItem->Create(this, menuitemName, itemType, mMenuGroupOwner, inMenuItemContent);
+ if (NS_FAILED(rv)) {
+ delete menuItem;
+ return;
+ }
+
+ AddMenuItem(menuItem);
+
+ // This needs to happen after the nsIMenuItem object is inserted into
+ // our item array in AddMenuItem()
+ menuItem->SetupIcon();
+}
+
+void nsMenuX::LoadSubMenu(nsIContent* inMenuContent)
+{
+ auto menu = MakeUnique<nsMenuX>();
+ if (!menu)
+ return;
+
+ nsresult rv = menu->Create(this, mMenuGroupOwner, inMenuContent);
+ if (NS_FAILED(rv))
+ return;
+
+ // |menu|'s ownership is transfer to AddMenu but, if it is successfully
+ // added, we can access it via the returned raw pointer.
+ nsMenuX* menu_ptr = AddMenu(Move(menu));
+
+ // This needs to happen after the nsIMenu object is inserted into
+ // our item array in AddMenu()
+ if (menu_ptr) {
+ menu_ptr->SetupIcon();
+ }
+}
+
+// This menu is about to open. Returns TRUE if we should keep processing the event,
+// FALSE if the handler wants to stop the opening of the menu.
+bool nsMenuX::OnOpen()
+{
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+
+ nsresult rv = NS_OK;
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+ if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault)
+ return false;
+
+ // If the open is going to succeed we need to walk our menu items, checking to
+ // see if any of them have a command attribute. If so, several attributes
+ // must potentially be updated.
+
+ // Get new popup content first since it might have changed as a result of the
+ // eXULPopupShowing event above.
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ if (!popupContent)
+ return true;
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->UpdateMenuItems(popupContent);
+ }
+
+ return true;
+}
+
+// Returns TRUE if we should keep processing the event, FALSE if the handler
+// wants to stop the closing of the menu.
+bool nsMenuX::OnClose()
+{
+ if (mDestroyHandlerCalled)
+ return true;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHiding, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+
+ nsresult rv = NS_OK;
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+
+ mDestroyHandlerCalled = true;
+
+ if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault)
+ return false;
+
+ return true;
+}
+
+// Find the |menupopup| child in the |popup| representing this menu. It should be one
+// of a very few children so we won't be iterating over a bazillion menu items to find
+// it (so the strcmp won't kill us).
+void nsMenuX::GetMenuPopupContent(nsIContent** aResult)
+{
+ if (!aResult)
+ return;
+ *aResult = nullptr;
+
+ // Check to see if we are a "menupopup" node (if we are a native menu).
+ {
+ int32_t dummy;
+ nsCOMPtr<nsIAtom> tag = mContent->OwnerDoc()->BindingManager()->ResolveTag(mContent, &dummy);
+ if (tag == nsGkAtoms::menupopup) {
+ *aResult = mContent;
+ NS_ADDREF(*aResult);
+ return;
+ }
+ }
+
+ // Otherwise check our child nodes.
+
+ uint32_t count = mContent->GetChildCount();
+
+ for (uint32_t i = 0; i < count; i++) {
+ int32_t dummy;
+ nsIContent *child = mContent->GetChildAt(i);
+ nsCOMPtr<nsIAtom> tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy);
+ if (tag == nsGkAtoms::menupopup) {
+ *aResult = child;
+ NS_ADDREF(*aResult);
+ return;
+ }
+ }
+}
+
+NSMenuItem* nsMenuX::NativeMenuItem()
+{
+ return mNativeMenuItem;
+}
+
+bool nsMenuX::IsXULHelpMenu(nsIContent* aMenuContent)
+{
+ bool retval = false;
+ if (aMenuContent) {
+ nsAutoString id;
+ aMenuContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+ if (id.Equals(NS_LITERAL_STRING("helpMenu")))
+ retval = true;
+ }
+ return retval;
+}
+
+//
+// nsChangeObserver
+//
+
+void nsMenuX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent,
+ nsIAtom *aAttribute)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // ignore the |open| attribute, which is by far the most common
+ if (gConstructingMenu || (aAttribute == nsGkAtoms::open))
+ return;
+
+ nsMenuObjectTypeX parentType = mParent->MenuObjectType();
+
+ if (aAttribute == nsGkAtoms::disabled) {
+ SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters));
+ }
+ else if (aAttribute == nsGkAtoms::label) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel);
+
+ // invalidate my parent. If we're a submenu parent, we have to rebuild
+ // the parent menu in order for the changes to be picked up. If we're
+ // a regular menu, just change the title and redraw the menubar.
+ if (parentType == eMenuBarObjectType) {
+ // reuse the existing menu, to avoid rebuilding the root menu bar.
+ NS_ASSERTION(mNativeMenu, "nsMenuX::AttributeChanged: invalid menu handle.");
+ NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
+ [mNativeMenu setTitle:newCocoaLabelString];
+ }
+ else if (parentType == eSubmenuObjectType) {
+ static_cast<nsMenuX*>(mParent)->SetRebuild(true);
+ }
+ else if (parentType == eStandaloneNativeMenuObjectType) {
+ static_cast<nsStandaloneNativeMenu*>(mParent)->GetMenuXObject()->SetRebuild(true);
+ }
+ }
+ else if (aAttribute == nsGkAtoms::hidden || aAttribute == nsGkAtoms::collapsed) {
+ SetRebuild(true);
+
+ bool contentIsHiddenOrCollapsed = nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent);
+
+ // don't do anything if the state is correct already
+ if (contentIsHiddenOrCollapsed != mVisible)
+ return;
+
+ if (contentIsHiddenOrCollapsed) {
+ if (parentType == eMenuBarObjectType ||
+ parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
+ // An exception will get thrown if we try to remove an item that isn't
+ // in the menu.
+ if ([parentMenu indexOfItem:mNativeMenuItem] != -1)
+ [parentMenu removeItem:mNativeMenuItem];
+ mVisible = false;
+ }
+ }
+ else {
+ if (parentType == eMenuBarObjectType ||
+ parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(mParent, this);
+ if (parentType == eMenuBarObjectType) {
+ // Before inserting we need to figure out if we should take the native
+ // application menu into account.
+ nsMenuBarX* mb = static_cast<nsMenuBarX*>(mParent);
+ if (mb->MenuContainsAppMenu())
+ insertionIndex++;
+ }
+ NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
+ [parentMenu insertItem:mNativeMenuItem atIndex:insertionIndex];
+ [mNativeMenuItem setSubmenu:mNativeMenu];
+ mVisible = true;
+ }
+ }
+ }
+ else if (aAttribute == nsGkAtoms::image) {
+ SetupIcon();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild,
+ int32_t aIndexInContainer)
+{
+ if (gConstructingMenu)
+ return;
+
+ SetRebuild(true);
+ mMenuGroupOwner->UnregisterForContentChanges(aChild);
+}
+
+void nsMenuX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
+ nsIContent *aChild)
+{
+ if (gConstructingMenu)
+ return;
+
+ SetRebuild(true);
+}
+
+nsresult nsMenuX::SetupIcon()
+{
+ // In addition to out-of-memory, menus that are children of the menu bar
+ // will not have mIcon set.
+ if (!mIcon)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return mIcon->SetupIcon();
+}
+
+//
+// MenuDelegate Objective-C class, used to set up Carbon events
+//
+
+@implementation MenuDelegate
+
+- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ NS_ASSERTION(geckoMenu, "Cannot initialize native menu delegate with NULL gecko menu! Will crash!");
+ mGeckoMenu = geckoMenu;
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item
+{
+ if (!menu || !item || !mGeckoMenu)
+ return;
+
+ nsMenuObjectX* target = mGeckoMenu->GetVisibleItemAt((uint32_t)[menu indexOfItem:item]);
+ if (target && (target->MenuObjectType() == eMenuItemObjectType)) {
+ nsMenuItemX* targetMenuItem = static_cast<nsMenuItemX*>(target);
+ bool handlerCalledPreventDefault; // but we don't actually care
+ targetMenuItem->DispatchDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), &handlerCalledPreventDefault);
+ }
+}
+
+- (void)menuWillOpen:(NSMenu *)menu
+{
+ if (!mGeckoMenu)
+ return;
+
+ // Don't do anything while the OS is (re)indexing our menus (on Leopard and
+ // higher). This stops the Help menu from being able to search in our
+ // menus, but it also resolves many other problems.
+ if (nsMenuX::sIndexingMenuLevel > 0)
+ return;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ if (rollupListener) {
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget) {
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+ [menu cancelTracking];
+ return;
+ }
+ }
+ mGeckoMenu->MenuOpened();
+}
+
+- (void)menuDidClose:(NSMenu *)menu
+{
+ if (!mGeckoMenu)
+ return;
+
+ // Don't do anything while the OS is (re)indexing our menus (on Leopard and
+ // higher). This stops the Help menu from being able to search in our
+ // menus, but it also resolves many other problems.
+ if (nsMenuX::sIndexingMenuLevel > 0)
+ return;
+
+ mGeckoMenu->MenuClosed();
+}
+
+@end
+
+// OS X Leopard (at least as of 10.5.2) has an obscure bug triggered by some
+// behavior that's present in Mozilla.org browsers but not (as best I can
+// tell) in Apple products like Safari. (It's not yet clear exactly what this
+// behavior is.)
+//
+// The bug is that sometimes you crash on quit in nsMenuX::RemoveAll(), on a
+// call to [NSMenu removeItemAtIndex:]. The crash is caused by trying to
+// access a deleted NSMenuItem object (sometimes (perhaps always?) by trying
+// to send it a _setChangedFlags: message). Though this object was deleted
+// some time ago, it remains registered as a potential target for a particular
+// key equivalent. So when [NSMenu removeItemAtIndex:] removes the current
+// target for that same key equivalent, the OS tries to "activate" the
+// previous target.
+//
+// The underlying reason appears to be that NSMenu's _addItem:toTable: and
+// _removeItem:fromTable: methods (which are used to keep a hashtable of
+// registered key equivalents) don't properly "retain" and "release"
+// NSMenuItem objects as they are added to and removed from the hashtable.
+//
+// Our (hackish) workaround is to shadow the OS's hashtable with another
+// hastable of our own (gShadowKeyEquivDB), and use it to "retain" and
+// "release" NSMenuItem objects as needed. This resolves bmo bugs 422287 and
+// 423669. When (if) Apple fixes this bug, we can remove this workaround.
+
+static NSMutableDictionary *gShadowKeyEquivDB = nil;
+
+// Class for values in gShadowKeyEquivDB.
+
+@interface KeyEquivDBItem : NSObject
+{
+ NSMenuItem *mItem;
+ NSMutableSet *mTables;
+}
+
+- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable;
+- (BOOL)hasTable:(NSMapTable *)aTable;
+- (int)addTable:(NSMapTable *)aTable;
+- (int)removeTable:(NSMapTable *)aTable;
+
+@end
+
+@implementation KeyEquivDBItem
+
+- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!gShadowKeyEquivDB)
+ gShadowKeyEquivDB = [[NSMutableDictionary alloc] init];
+ self = [super init];
+ if (aItem && aTable) {
+ mTables = [[NSMutableSet alloc] init];
+ mItem = [aItem retain];
+ [mTables addObject:[NSValue valueWithPointer:aTable]];
+ } else {
+ mTables = nil;
+ mItem = nil;
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTables)
+ [mTables release];
+ if (mItem)
+ [mItem release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)hasTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [mTables member:[NSValue valueWithPointer:aTable]] ? YES : NO;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// Does nothing if aTable (its index value) is already present in mTables.
+- (int)addTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (aTable)
+ [mTables addObject:[NSValue valueWithPointer:aTable]];
+ return [mTables count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (int)removeTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (aTable) {
+ NSValue *objectToRemove =
+ [mTables member:[NSValue valueWithPointer:aTable]];
+ if (objectToRemove)
+ [mTables removeObject:objectToRemove];
+ }
+ return [mTables count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+@end
+
+@interface NSMenu (MethodSwizzling)
++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable;
++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable;
+@end
+
+@implementation NSMenu (MethodSwizzling)
+
++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (aItem && aTable) {
+ NSValue *key = [NSValue valueWithPointer:aItem];
+ KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key];
+ if (shadowItem) {
+ [shadowItem addTable:aTable];
+ } else {
+ shadowItem = [[KeyEquivDBItem alloc] initWithItem:aItem table:aTable];
+ [gShadowKeyEquivDB setObject:shadowItem forKey:key];
+ // Release after [NSMutableDictionary setObject:forKey:] retains it (so
+ // that it will get dealloced when removeObjectForKey: is called).
+ [shadowItem release];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+
+ [self nsMenuX_NSMenu_addItem:aItem toTable:aTable];
+}
+
++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable
+{
+ [self nsMenuX_NSMenu_removeItem:aItem fromTable:aTable];
+
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (aItem && aTable) {
+ NSValue *key = [NSValue valueWithPointer:aItem];
+ KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key];
+ if (shadowItem && [shadowItem hasTable:aTable]) {
+ if (![shadowItem removeTable:aTable])
+ [gShadowKeyEquivDB removeObjectForKey:key];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// This class is needed to keep track of when the OS is (re)indexing all of
+// our menus. This appears to only happen on Leopard and higher, and can
+// be triggered by opening the Help menu. Some operations are unsafe while
+// this is happening -- notably the calls to [[NSImage alloc]
+// initWithSize:imageRect.size] and [newImage lockFocus] in nsMenuItemIconX::
+// OnStopFrame(). But we don't yet have a complete list, and Apple doesn't
+// yet have any documentation on this subject. (Apple also doesn't yet have
+// any documented way to find the information we seek here.) The "original"
+// of this class (the one whose indexMenuBarDynamically method we hook) is
+// defined in the Shortcut framework in /System/Library/PrivateFrameworks.
+@interface NSObject (SCTGRLIndexMethodSwizzling)
+- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically;
+@end
+
+@implementation NSObject (SCTGRLIndexMethodSwizzling)
+
+- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically
+{
+ // This method appears to be called (once) whenever the OS (re)indexes our
+ // menus. sIndexingMenuLevel is a int32_t just in case it might be
+ // reentered. As it's running, it spawns calls to two undocumented
+ // HIToolbox methods (_SimulateMenuOpening() and _SimulateMenuClosed()),
+ // which "simulate" the opening and closing of our menus without actually
+ // displaying them.
+ ++nsMenuX::sIndexingMenuLevel;
+ [self nsMenuX_SCTGRLIndex_indexMenuBarDynamically];
+ --nsMenuX::sIndexingMenuLevel;
+}
+
+@end
diff --git a/widget/cocoa/nsNativeThemeCocoa.h b/widget/cocoa/nsNativeThemeCocoa.h
new file mode 100644
index 000000000..23f2bc4d3
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeCocoa.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNativeThemeCocoa_h_
+#define nsNativeThemeCocoa_h_
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsNativeTheme.h"
+
+@class CellDrawView;
+@class NSProgressBarCell;
+@class ContextAwareSearchFieldCell;
+class nsDeviceContext;
+struct SegmentedControlRenderSettings;
+
+namespace mozilla {
+class EventStates;
+} // namespace mozilla
+
+class nsNativeThemeCocoa : private nsNativeTheme,
+ public nsITheme
+{
+public:
+ enum {
+ eThemeGeometryTypeTitlebar = eThemeGeometryTypeUnknown + 1,
+ eThemeGeometryTypeToolbar,
+ eThemeGeometryTypeToolbox,
+ eThemeGeometryTypeWindowButtons,
+ eThemeGeometryTypeFullscreenButton,
+ eThemeGeometryTypeMenu,
+ eThemeGeometryTypeHighlightedMenuItem,
+ eThemeGeometryTypeVibrancyLight,
+ eThemeGeometryTypeVibrancyDark,
+ eThemeGeometryTypeTooltip,
+ eThemeGeometryTypeSheet,
+ eThemeGeometryTypeSourceList,
+ eThemeGeometryTypeSourceListSelection,
+ eThemeGeometryTypeActiveSourceListSelection
+ };
+
+ nsNativeThemeCocoa();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+ NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType, nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult, bool* aIsOverridable) override;
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+ NS_IMETHOD ThemeChanged() override;
+ bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, uint8_t aWidgetType) override;
+ bool WidgetIsContainer(uint8_t aWidgetType) override;
+ bool ThemeDrawsFocusForWidget(uint8_t aWidgetType) override;
+ bool ThemeNeedsComboboxDropmarker() override;
+ virtual bool WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) override;
+ virtual bool NeedToClearBackgroundBehindWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+ virtual bool WidgetProvidesFontSmoothingBackgroundColor(nsIFrame* aFrame, uint8_t aWidgetType,
+ nscolor* aColor) override;
+ virtual ThemeGeometryType ThemeGeometryTypeForWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+ virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) override;
+
+ void DrawProgress(CGContextRef context, const HIRect& inBoxRect,
+ bool inIsIndeterminate, bool inIsHorizontal,
+ double inValue, double inMaxValue, nsIFrame* aFrame);
+
+ static void DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped);
+
+protected:
+ virtual ~nsNativeThemeCocoa();
+
+ nsIntMargin DirectionAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame);
+ nsIFrame* SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter);
+ CGRect SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
+ nsIFrame* aCurrent, nsIFrame* aRight);
+ bool IsWindowSheet(nsIFrame* aFrame);
+
+ // HITheme drawing routines
+ void DrawFrame(CGContextRef context, HIThemeFrameKind inKind,
+ const HIRect& inBoxRect, bool inReadOnly,
+ mozilla::EventStates inState);
+ void DrawMeter(CGContextRef context, const HIRect& inBoxRect,
+ nsIFrame* aFrame);
+ void DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
+ mozilla::EventStates inState, nsIFrame* aFrame,
+ const SegmentedControlRenderSettings& aSettings);
+ void DrawTabPanel(CGContextRef context, const HIRect& inBoxRect, nsIFrame* aFrame);
+ void DrawScale(CGContextRef context, const HIRect& inBoxRect,
+ mozilla::EventStates inState, bool inDirection,
+ bool inIsReverse, int32_t inCurrentValue, int32_t inMinValue,
+ int32_t inMaxValue, nsIFrame* aFrame);
+ void DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
+ const HIRect& inBoxRect, bool inSelected,
+ mozilla::EventStates inState, nsIFrame* aFrame);
+ void DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame, mozilla::EventStates inState);
+ void DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ mozilla::EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame, float aOriginalHeight);
+ void DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
+ mozilla::EventStates inState, nsIFrame* aFrame,
+ const NSSize& aIconSize, NSString* aImageName,
+ bool aCenterHorizontally);
+ void DrawButton(CGContextRef context, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, bool inIsDefault,
+ ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
+ mozilla::EventStates inState, nsIFrame* aFrame);
+ void DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect,
+ mozilla::EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame);
+ void DrawDropdown(CGContextRef context, const HIRect& inBoxRect,
+ mozilla::EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame);
+ void DrawSpinButtons(CGContextRef context, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ mozilla::EventStates inState, nsIFrame* aFrame);
+ void DrawSpinButton(CGContextRef context, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ mozilla::EventStates inState,
+ nsIFrame* aFrame, uint8_t aWidgetType);
+ void DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
+ NSWindow* aWindow);
+ void DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame *aFrame);
+ void DrawResizer(CGContextRef cgContext, const HIRect& aRect, nsIFrame *aFrame);
+
+ // Scrollbars
+ void GetScrollbarPressStates(nsIFrame *aFrame,
+ mozilla::EventStates aButtonStates[]);
+ nsIFrame* GetParentScrollbarFrame(nsIFrame *aFrame);
+ bool IsParentScrollbarRolledOver(nsIFrame* aFrame);
+
+private:
+ NSButtonCell* mDisclosureButtonCell;
+ NSButtonCell* mHelpButtonCell;
+ NSButtonCell* mPushButtonCell;
+ NSButtonCell* mRadioButtonCell;
+ NSButtonCell* mCheckboxCell;
+ ContextAwareSearchFieldCell* mSearchFieldCell;
+ NSPopUpButtonCell* mDropdownCell;
+ NSComboBoxCell* mComboBoxCell;
+ NSProgressBarCell* mProgressBarCell;
+ NSLevelIndicatorCell* mMeterBarCell;
+ CellDrawView* mCellDrawView;
+};
+
+#endif // nsNativeThemeCocoa_h_
diff --git a/widget/cocoa/nsNativeThemeCocoa.mm b/widget/cocoa/nsNativeThemeCocoa.mm
new file mode 100644
index 000000000..3c8695442
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -0,0 +1,3931 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNativeThemeCocoa.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "nsChildView.h"
+#include "nsDeviceContext.h"
+#include "nsLayoutUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsNumberControlFrame.h"
+#include "nsRangeFrame.h"
+#include "nsRenderingContext.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsThemeConstants.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIFrame.h"
+#include "nsIAtom.h"
+#include "nsNameSpaceManager.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaWindow.h"
+#include "nsNativeThemeColors.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLMeterElement.h"
+#include "nsLookAndFeel.h"
+#include "VibrancyManager.h"
+
+#include "gfxContext.h"
+#include "gfxQuartzSurface.h"
+#include "gfxQuartzNativeDrawing.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using mozilla::dom::HTMLMeterElement;
+
+#define DRAW_IN_FRAME_DEBUG 0
+#define SCROLLBARS_VISUAL_DEBUG 0
+
+// private Quartz routines needed here
+extern "C" {
+ CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
+ CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
+ typedef CFTypeRef CUIRendererRef;
+ void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options, CFDictionaryRef* result);
+}
+
+// Workaround for NSCell control tint drawing
+// Without this workaround, NSCells are always drawn with the clear control tint
+// as long as they're not attached to an NSControl which is a subview of an active window.
+// XXXmstange Why doesn't Webkit need this?
+@implementation NSCell (ControlTintWorkaround)
+- (int)_realControlTint { return [self controlTint]; }
+@end
+
+// The purpose of this class is to provide objects that can be used when drawing
+// NSCells using drawWithFrame:inView: without causing any harm. The only
+// messages that will be sent to such an object are "isFlipped" and
+// "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
+// on 10.4 (see bug 465069); currentEditor (which isn't even a method of
+// NSView) will be called when drawing search fields, and we only provide it in
+// order to prevent "unrecognized selector" exceptions.
+// There's no need to pass the actual NSView that we're drawing into to
+// drawWithFrame:inView:. What's more, doing so even causes unnecessary
+// invalidations as soon as we draw a focusring!
+@interface CellDrawView : NSView
+
+@end;
+
+@implementation CellDrawView
+
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (NSText*)currentEditor
+{
+ return nil;
+}
+
+@end
+
+// These two classes don't actually add any behavior over NSButtonCell. Their
+// purpose is to make it easy to distinguish NSCell objects that are used for
+// drawing radio buttons / checkboxes from other cell types.
+// The class names are made up, there are no classes with these names in AppKit.
+// The reason we need them is that calling [cell setButtonType:NSRadioButton]
+// doesn't leave an easy-to-check "marker" on the cell object - there is no
+// -[NSButtonCell buttonType] method.
+@interface RadioButtonCell : NSButtonCell
+@end;
+@implementation RadioButtonCell @end;
+@interface CheckboxCell : NSButtonCell
+@end;
+@implementation CheckboxCell @end;
+
+static void
+DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView)
+{
+ if ([aCell showsFirstResponder]) {
+ CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ CGContextSaveGState(cgContext);
+
+ // It's important to set the focus ring style before we enter the
+ // transparency layer so that the transparency layer only contains
+ // the normal button mask without the focus ring, and the conversion
+ // to the focus ring shape happens only when the transparency layer is
+ // ended.
+ NSSetFocusRingStyle(NSFocusRingOnly);
+
+ // We need to draw the whole button into a transparency layer because
+ // many button types are composed of multiple parts, and if these parts
+ // were drawn while the focus ring style was active, each individual part
+ // would produce a focus ring for itself. But we only want one focus ring
+ // for the whole button. The transparency layer is a way to merge the
+ // individual button parts together before the focus ring shape is
+ // calculated.
+ CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
+ [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
+ CGContextEndTransparencyLayer(cgContext);
+
+ CGContextRestoreGState(cgContext);
+ }
+}
+
+static bool
+FocusIsDrawnByDrawWithFrame(NSCell* aCell)
+{
+#if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
+ // When building with the 10.8 SDK or higher, focus rings don't draw as part
+ // of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call
+ // to -[NSCell drawFocusRingMaskWithFrame:inView:]; .
+ // See the NSButtonCell section under
+ // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes
+ return false;
+#else
+ if (!nsCocoaFeatures::OnYosemiteOrLater()) {
+ // When building with the 10.7 SDK or lower, focus rings always draw as
+ // part of -[NSCell drawWithFrame:inView:] if the build is run on 10.9 or
+ // lower.
+ return true;
+ }
+
+ // On 10.10, whether the focus ring is drawn as part of
+ // -[NSCell drawWithFrame:inView:] depends on the cell type.
+ // Radio buttons and checkboxes draw their own focus rings, other cell
+ // types need -[NSCell drawFocusRingMaskWithFrame:inView:].
+ return [aCell isKindOfClass:[RadioButtonCell class]] ||
+ [aCell isKindOfClass:[CheckboxCell class]];
+#endif
+}
+
+static void
+DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView)
+{
+ [aCell drawWithFrame:aWithFrame inView:aInView];
+
+ if (!FocusIsDrawnByDrawWithFrame(aCell)) {
+ DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
+ }
+}
+
+/**
+ * NSProgressBarCell is used to draw progress bars of any size.
+ */
+@interface NSProgressBarCell : NSCell
+{
+ /*All instance variables are private*/
+ double mValue;
+ double mMax;
+ bool mIsIndeterminate;
+ bool mIsHorizontal;
+}
+
+- (void)setValue:(double)value;
+- (double)value;
+- (void)setMax:(double)max;
+- (double)max;
+- (void)setIndeterminate:(bool)aIndeterminate;
+- (bool)isIndeterminate;
+- (void)setHorizontal:(bool)aIsHorizontal;
+- (bool)isHorizontal;
+- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
+@end
+
+@implementation NSProgressBarCell
+
+- (void)setMax:(double)aMax
+{
+ mMax = aMax;
+}
+
+- (double)max
+{
+ return mMax;
+}
+
+- (void)setValue:(double)aValue
+{
+ mValue = aValue;
+}
+
+- (double)value
+{
+ return mValue;
+}
+
+- (void)setIndeterminate:(bool)aIndeterminate
+{
+ mIsIndeterminate = aIndeterminate;
+}
+
+- (bool)isIndeterminate
+{
+ return mIsIndeterminate;
+}
+
+- (void)setHorizontal:(bool)aIsHorizontal
+{
+ mIsHorizontal = aIsHorizontal;
+}
+
+- (bool)isHorizontal
+{
+ return mIsHorizontal;
+}
+
+- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
+{
+ CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+
+ HIThemeTrackDrawInfo tdi;
+
+ tdi.version = 0;
+ tdi.min = 0;
+
+ tdi.value = INT32_MAX * (mValue / mMax);
+ tdi.max = INT32_MAX;
+ tdi.bounds = NSRectToCGRect(cellFrame);
+ tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
+ tdi.enableState = [self controlTint] == NSClearControlTint ? kThemeTrackInactive
+ : kThemeTrackActive;
+
+ NSControlSize size = [self controlSize];
+ if (size == NSRegularControlSize) {
+ tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar
+ : kThemeLargeProgressBar;
+ } else {
+ NS_ASSERTION(size == NSSmallControlSize,
+ "We shouldn't have another size than small and regular for the moment");
+ tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
+ : kThemeMediumProgressBar;
+ }
+
+ int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
+ int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
+ tdi.trackInfo.progress.phase = uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) /
+ milliSecondsPerStep);
+
+ HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
+}
+
+@end
+
+@interface ContextAwareSearchFieldCell : NSSearchFieldCell
+{
+ nsIFrame* mContext;
+}
+
+// setContext: stores the searchfield nsIFrame so that it can be consulted
+// during painting. Please reset this by calling setContext:nullptr as soon as
+// you're done with painting because we don't want to keep a dangling pointer.
+- (void)setContext:(nsIFrame*)aContext;
+@end
+
+@implementation ContextAwareSearchFieldCell
+
+- (id)initTextCell:(NSString*)aString
+{
+ if ((self = [super initTextCell:aString])) {
+ mContext = nullptr;
+ }
+ return self;
+}
+
+- (void)setContext:(nsIFrame*)aContext
+{
+ mContext = aContext;
+}
+
+static BOOL IsToolbarStyleContainer(nsIFrame* aFrame)
+{
+ nsIContent* content = aFrame->GetContent();
+ if (!content)
+ return NO;
+
+ if (content->IsAnyOfXULElements(nsGkAtoms::toolbar,
+ nsGkAtoms::toolbox,
+ nsGkAtoms::statusbar))
+ return YES;
+
+ switch (aFrame->StyleDisplay()->mAppearance) {
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+- (BOOL)_isToolbarMode
+{
+ // On 10.7, searchfields have two different styles, depending on whether
+ // the searchfield is on top of of window chrome. This function is called on
+ // 10.7 during drawing in order to determine which style to use.
+ for (nsIFrame* frame = mContext; frame; frame = frame->GetParent()) {
+ if (IsToolbarStyleContainer(frame)) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+@end
+
+// Workaround for Bug 542048
+// On 64-bit, NSSearchFieldCells don't draw focus rings.
+#if defined(__x86_64__)
+
+@interface SearchFieldCellWithFocusRing : ContextAwareSearchFieldCell {} @end
+
+@implementation SearchFieldCellWithFocusRing
+
+- (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView
+{
+ [super drawWithFrame:rect inView:controlView];
+
+ if (FocusIsDrawnByDrawWithFrame(self)) {
+ // For some reason, -[NSSearchFieldCell drawWithFrame:inView] doesn't draw a
+ // focus ring in 64 bit mode, no matter what SDK is used or what OS X version
+ // we're running on. But if FocusIsDrawnByDrawWithFrame(self), then our
+ // caller expects us to draw a focus ring. So we just do that here.
+ DrawFocusRingForCellIfNeeded(self, rect, controlView);
+ }
+}
+
+- (void)drawFocusRingMaskWithFrame:(NSRect)rect inView:(NSView*)controlView
+{
+ // By default this draws nothing. I don't know why.
+ // We just draw the search field again. It's a great mask shape for its own
+ // focus ring.
+ [super drawWithFrame:rect inView:controlView];
+}
+
+@end
+
+#endif
+
+#define HITHEME_ORIENTATION kHIThemeOrientationNormal
+
+static CGFloat kMaxFocusRingWidth = 0; // initialized by the nsNativeThemeCocoa constructor
+
+// These enums are for indexing into the margin array.
+enum {
+ leopardOSorlater = 0, // 10.6 - 10.9
+ yosemiteOSorlater = 1 // 10.10+
+};
+
+enum {
+ miniControlSize,
+ smallControlSize,
+ regularControlSize
+};
+
+enum {
+ leftMargin,
+ topMargin,
+ rightMargin,
+ bottomMargin
+};
+
+static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
+ if (cocoaControlSize == NSMiniControlSize)
+ return miniControlSize;
+ else if (cocoaControlSize == NSSmallControlSize)
+ return smallControlSize;
+ else
+ return regularControlSize;
+}
+
+static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
+ if (enumControlSize == miniControlSize)
+ return NSMiniControlSize;
+ else if (enumControlSize == smallControlSize)
+ return NSSmallControlSize;
+ else
+ return NSRegularControlSize;
+}
+
+static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize)
+{
+ if (aControlSize == NSRegularControlSize)
+ return @"regular";
+ else if (aControlSize == NSSmallControlSize)
+ return @"small";
+ else
+ return @"mini";
+}
+
+static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4])
+{
+ if (!marginSet)
+ return;
+
+ static int osIndex = nsCocoaFeatures::OnYosemiteOrLater() ?
+ yosemiteOSorlater : leopardOSorlater;
+ size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
+ const float* buttonMargins = marginSet[osIndex][controlSize];
+ rect->origin.x -= buttonMargins[leftMargin];
+ rect->origin.y -= buttonMargins[bottomMargin];
+ rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
+ rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
+}
+
+static ChildView* ChildViewForFrame(nsIFrame* aFrame)
+{
+ if (!aFrame)
+ return nil;
+
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ if (!widget)
+ return nil;
+
+ NSView* view = (NSView*)widget->GetNativeData(NS_NATIVE_WIDGET);
+ return [view isKindOfClass:[ChildView class]] ? (ChildView*)view : nil;
+}
+
+static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
+ nsIWidget** aTopLevelWidget = NULL)
+{
+ if (!aFrame)
+ return nil;
+
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ if (!widget)
+ return nil;
+
+ nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
+ if (aTopLevelWidget)
+ *aTopLevelWidget = topLevelWidget;
+
+ return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
+}
+
+static NSSize
+WindowButtonsSize(nsIFrame* aFrame)
+{
+ NSWindow* window = NativeWindowForFrame(aFrame);
+ if (!window) {
+ // Return fallback values.
+ return NSMakeSize(54, 16);
+ }
+
+ NSRect buttonBox = NSZeroRect;
+ NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
+ if (closeButton) {
+ buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
+ }
+ NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
+ if (minimizeButton) {
+ buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
+ }
+ NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
+ if (zoomButton) {
+ buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
+ }
+ return buttonBox.size;
+}
+
+static BOOL FrameIsInActiveWindow(nsIFrame* aFrame)
+{
+ nsIWidget* topLevelWidget = NULL;
+ NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
+ if (!topLevelWidget || !win)
+ return YES;
+
+ // XUL popups, e.g. the toolbar customization popup, can't become key windows,
+ // but controls in these windows should still get the active look.
+ if (topLevelWidget->WindowType() == eWindowType_popup)
+ return YES;
+ if ([win isSheet])
+ return [win isKeyWindow];
+ return [win isMainWindow] && ![win attachedSheet];
+}
+
+// Toolbar controls and content controls respond to different window
+// activeness states.
+static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl)
+{
+ if (aIsToolbarControl)
+ return [NativeWindowForFrame(aFrame) isMainWindow];
+ return FrameIsInActiveWindow(aFrame);
+}
+
+static bool IsInSourceList(nsIFrame* aFrame) {
+ for (nsIFrame* frame = aFrame->GetParent(); frame; frame = frame->GetParent()) {
+ if (frame->StyleDisplay()->mAppearance == NS_THEME_MAC_SOURCE_LIST) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
+
+nsNativeThemeCocoa::nsNativeThemeCocoa()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ kMaxFocusRingWidth = nsCocoaFeatures::OnYosemiteOrLater() ? 7 : 4;
+
+ // provide a local autorelease pool, as this is called during startup
+ // before the main event-loop pool is in place
+ nsAutoreleasePool pool;
+
+ mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
+ [mDisclosureButtonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
+ [mDisclosureButtonCell setButtonType:NSPushOnPushOffButton];
+ [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
+
+ mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
+ [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
+ [mHelpButtonCell setButtonType:NSMomentaryPushInButton];
+ [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
+
+ mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
+ [mPushButtonCell setButtonType:NSMomentaryPushInButton];
+ [mPushButtonCell setHighlightsBy:NSPushInCellMask];
+
+ mRadioButtonCell = [[RadioButtonCell alloc] initTextCell:@""];
+ [mRadioButtonCell setButtonType:NSRadioButton];
+
+ mCheckboxCell = [[CheckboxCell alloc] initTextCell:@""];
+ [mCheckboxCell setButtonType:NSSwitchButton];
+ [mCheckboxCell setAllowsMixedState:YES];
+
+#if defined(__x86_64__)
+ mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
+#else
+ mSearchFieldCell = [[ContextAwareSearchFieldCell alloc] initTextCell:@""];
+#endif
+ [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
+ [mSearchFieldCell setBezeled:YES];
+ [mSearchFieldCell setEditable:YES];
+ [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
+
+ mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
+
+ mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
+ [mComboBoxCell setBezeled:YES];
+ [mComboBoxCell setEditable:YES];
+ [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
+
+ mProgressBarCell = [[NSProgressBarCell alloc] init];
+
+ mMeterBarCell = [[NSLevelIndicatorCell alloc]
+ initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
+
+ mCellDrawView = [[CellDrawView alloc] init];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsNativeThemeCocoa::~nsNativeThemeCocoa()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mMeterBarCell release];
+ [mProgressBarCell release];
+ [mDisclosureButtonCell release];
+ [mHelpButtonCell release];
+ [mPushButtonCell release];
+ [mRadioButtonCell release];
+ [mCheckboxCell release];
+ [mSearchFieldCell release];
+ [mDropdownCell release];
+ [mComboBoxCell release];
+ [mCellDrawView release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Limit on the area of the target rect (in pixels^2) in
+// DrawCellWithScaling() and DrawButton() and above which we
+// don't draw the object into a bitmap buffer. This is to avoid crashes in
+// [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
+// CGContextDrawImage(), and also to avoid very poor drawing performance in
+// CGContextDrawImage() when it scales the bitmap (particularly if xscale or
+// yscale is less than but near 1 -- e.g. 0.9). This value was determined
+// by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
+// different amounts of RAM.
+#define BITMAP_MAX_AREA 500000
+
+static int
+GetBackingScaleFactorForRendering(CGContextRef cgContext)
+{
+ CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
+ CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
+ float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
+ fabs(transformedUserSpacePixel.size.height));
+ return maxScale > 1.0 ? 2 : 1;
+}
+
+/*
+ * Draw the given NSCell into the given cgContext.
+ *
+ * destRect - the size and position of the resulting control rectangle
+ * controlSize - the NSControlSize which will be given to the NSCell before
+ * asking it to render
+ * naturalSize - The natural dimensions of this control.
+ * If the control rect size is not equal to either of these, a scale
+ * will be applied to the context so that rendering the control at the
+ * natural size will result in it filling the destRect space.
+ * If a control has no natural dimensions in either/both axes, pass 0.0f.
+ * minimumSize - The minimum dimensions of this control.
+ * If the control rect size is less than the minimum for a given axis,
+ * a scale will be applied to the context so that the minimum is used
+ * for drawing. If a control has no minimum dimensions in either/both
+ * axes, pass 0.0f.
+ * marginSet - an array of margins; a multidimensional array of [2][3][4],
+ * with the first dimension being the OS version (Tiger or Leopard),
+ * the second being the control size (mini, small, regular), and the third
+ * being the 4 margin values (left, top, right, bottom).
+ * view - The NSView that we're drawing into. As far as I can tell, it doesn't
+ * matter if this is really the right view; it just has to return YES when
+ * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
+ * mirrorHorizontal - whether to mirror the cell horizontally
+ */
+static void DrawCellWithScaling(NSCell *cell,
+ CGContextRef cgContext,
+ const HIRect& destRect,
+ NSControlSize controlSize,
+ NSSize naturalSize,
+ NSSize minimumSize,
+ const float marginSet[][3][4],
+ NSView* view,
+ BOOL mirrorHorizontal)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
+
+ if (naturalSize.width != 0.0f)
+ drawRect.size.width = naturalSize.width;
+ if (naturalSize.height != 0.0f)
+ drawRect.size.height = naturalSize.height;
+
+ // Keep aspect ratio when scaling if one dimension is free.
+ if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
+ drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
+ if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
+ drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
+
+ // Honor minimum sizes.
+ if (drawRect.size.width < minimumSize.width)
+ drawRect.size.width = minimumSize.width;
+ if (drawRect.size.height < minimumSize.height)
+ drawRect.size.height = minimumSize.height;
+
+ [NSGraphicsContext saveGraphicsState];
+
+ // Only skip the buffer if the area of our cell (in pixels^2) is too large.
+ if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
+ // Inflate the rect Gecko gave us by the margin for the control.
+ InflateControlRect(&drawRect, controlSize, marginSet);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+
+ DrawCellIncludingFocusRing(cell, drawRect, view);
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+ else {
+ float w = ceil(drawRect.size.width);
+ float h = ceil(drawRect.size.height);
+ NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
+
+ // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
+ InflateControlRect(&tmpRect, controlSize, marginSet);
+
+ // and then, expand by kMaxFocusRingWidth size to make sure we can capture any focus ring
+ w += kMaxFocusRingWidth * 2.0;
+ h += kMaxFocusRingWidth * 2.0;
+
+ int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
+ CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
+ CGContextRef ctx = CGBitmapContextCreate(NULL,
+ (int) w * backingScaleFactor, (int) h * backingScaleFactor,
+ 8, (int) w * backingScaleFactor * 4,
+ rgb, kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(rgb);
+
+ // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
+ // This is the first flip transform, applied to cgContext.
+ CGContextScaleCTM(cgContext, 1.0f, -1.0f);
+ CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
+ if (mirrorHorizontal) {
+ CGContextScaleCTM(cgContext, -1.0f, 1.0f);
+ CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
+ }
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
+
+ CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
+
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
+
+ // This is the second flip transform, applied to ctx.
+ CGContextScaleCTM(ctx, 1.0f, -1.0f);
+ CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
+
+ DrawCellIncludingFocusRing(cell, tmpRect, view);
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+
+ CGImageRef img = CGBitmapContextCreateImage(ctx);
+
+ // Drop the image into the original destination rectangle, scaling to fit
+ // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
+ // doesn't extend beyond the overflow rect
+ float xscale = destRect.size.width / drawRect.size.width;
+ float yscale = destRect.size.height / drawRect.size.height;
+ float scaledFocusRingX = xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
+ float scaledFocusRingY = yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
+ CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX,
+ destRect.origin.y - scaledFocusRingY,
+ destRect.size.width + scaledFocusRingX * 2,
+ destRect.size.height + scaledFocusRingY * 2),
+ img);
+
+ CGImageRelease(img);
+ CGContextRelease(ctx);
+ }
+
+ [NSGraphicsContext restoreGraphicsState];
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, destRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+struct CellRenderSettings {
+ // The natural dimensions of the control.
+ // If a control has no natural dimensions in either/both axes, set to 0.0f.
+ NSSize naturalSizes[3];
+
+ // The minimum dimensions of the control.
+ // If a control has no minimum dimensions in either/both axes, set to 0.0f.
+ NSSize minimumSizes[3];
+
+ // A three-dimensional array,
+ // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and above),
+ // the second being the control size (mini, small, regular), and the third
+ // being the 4 margin values (left, top, right, bottom).
+ float margins[2][3][4];
+};
+
+/*
+ * This is a helper method that returns the required NSControlSize given a size
+ * and the size of the three controls plus a tolerance.
+ * size - The width or the height of the element to draw.
+ * sizes - An array with the all the width/height of the element for its
+ * different sizes.
+ * tolerance - The tolerance as passed to DrawCellWithSnapping.
+ * NOTE: returns NSRegularControlSize if all values in 'sizes' are zero.
+ */
+static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance)
+{
+ for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
+ if (sizes[i] == 0) {
+ continue;
+ }
+
+ CGFloat next = 0;
+ // Find next value.
+ for (uint32_t j = i+1; j <= regularControlSize; ++j) {
+ if (sizes[j] != 0) {
+ next = sizes[j];
+ break;
+ }
+ }
+
+ // If it's the latest value, we pick it.
+ if (next == 0) {
+ return CocoaSizeForEnum(i);
+ }
+
+ if (size <= sizes[i] + tolerance && size < next) {
+ return CocoaSizeForEnum(i);
+ }
+ }
+
+ // If we are here, that means sizes[] was an array with only empty values
+ // or the algorithm above is wrong.
+ // The former can happen but the later would be wrong.
+ NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
+ "We found no control! We shouldn't be there!");
+ return CocoaSizeForEnum(regularControlSize);
+}
+
+/*
+ * Draw the given NSCell into the given cgContext with a nice control size.
+ *
+ * This function is similar to DrawCellWithScaling, but it decides what
+ * control size to use based on the destRect's size.
+ * Scaling is only applied when the difference between the destRect's size
+ * and the next smaller natural size is greater than snapTolerance. Otherwise
+ * it snaps to the next smaller control size without scaling because unscaled
+ * controls look nicer.
+ */
+static void DrawCellWithSnapping(NSCell *cell,
+ CGContextRef cgContext,
+ const HIRect& destRect,
+ const CellRenderSettings settings,
+ float verticalAlignFactor,
+ NSView* view,
+ BOOL mirrorHorizontal,
+ float snapTolerance = 2.0f)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
+ const NSSize *sizes = settings.naturalSizes;
+ const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
+ const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
+ const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
+
+ HIRect drawRect = destRect;
+
+ CGFloat controlWidths[3] = { miniSize.width, smallSize.width, regularSize.width };
+ NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
+ CGFloat controlHeights[3] = { miniSize.height, smallSize.height, regularSize.height };
+ NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
+
+ NSControlSize controlSize = NSRegularControlSize;
+ size_t sizeIndex = 0;
+
+ // At some sizes, don't scale but snap.
+ const NSControlSize smallerControlSize =
+ EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ?
+ controlSizeX : controlSizeY;
+ const size_t smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
+ const NSSize size = sizes[smallerControlSizeIndex];
+ float diffWidth = size.width ? rectWidth - size.width : 0.0f;
+ float diffHeight = size.height ? rectHeight - size.height : 0.0f;
+ if (diffWidth >= 0.0f && diffHeight >= 0.0f &&
+ diffWidth <= snapTolerance && diffHeight <= snapTolerance) {
+ // Snap to the smaller control size.
+ controlSize = smallerControlSize;
+ sizeIndex = smallerControlSizeIndex;
+ MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
+
+ // Resize and center the drawRect.
+ if (sizes[sizeIndex].width) {
+ drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
+ drawRect.size.width = sizes[sizeIndex].width;
+ }
+ if (sizes[sizeIndex].height) {
+ drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
+ drawRect.size.height = sizes[sizeIndex].height;
+ }
+ } else {
+ // Use the larger control size.
+ controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ?
+ controlSizeX : controlSizeY;
+ sizeIndex = EnumSizeForCocoaSize(controlSize);
+ }
+
+ [cell setControlSize:controlSize];
+
+ MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
+ const NSSize minimumSize = settings.minimumSizes[sizeIndex];
+ DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
+ minimumSize, settings.margins, view, mirrorHorizontal);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@interface NSWindow(CoreUIRendererPrivate)
++ (CUIRendererRef)coreUIRenderer;
+@end
+
+static id
+GetAquaAppearance()
+{
+ // We only need NSAppearance on 10.10 and up.
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
+ if (NSAppearanceClass &&
+ [NSAppearanceClass respondsToSelector:@selector(appearanceNamed:)]) {
+ return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
+ withObject:@"NSAppearanceNameAqua"];
+ }
+ }
+ return nil;
+}
+
+@interface NSObject(NSAppearanceCoreUIRendering)
+- (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options;
+@end
+
+static void
+RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions, bool aSkipAreaCheck = false)
+{
+ id appearance = GetAquaAppearance();
+
+ if (!aSkipAreaCheck && aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
+ return;
+ }
+
+ if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
+ // Render through NSAppearance on Mac OS 10.10 and up. This will call
+ // CUIDraw with a CoreUI renderer that will give us the correct 10.10
+ // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
+ // renders 10.9-style widgets on 10.10.
+ [appearance _drawInRect:aRect context:cgContext options:aOptions];
+ } else {
+ // 10.9 and below
+ CUIRendererRef renderer = [NSWindow respondsToSelector:@selector(coreUIRenderer)]
+ ? [NSWindow coreUIRenderer] : nil;
+ CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
+ }
+}
+
+static float VerticalAlignFactor(nsIFrame *aFrame)
+{
+ if (!aFrame)
+ return 0.5f; // default: center
+
+ const nsStyleCoord& va = aFrame->StyleDisplay()->mVerticalAlign;
+ uint8_t intval = (va.GetUnit() == eStyleUnit_Enumerated)
+ ? va.GetIntValue()
+ : NS_STYLE_VERTICAL_ALIGN_MIDDLE;
+ switch (intval) {
+ case NS_STYLE_VERTICAL_ALIGN_TOP:
+ case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
+ return 0.0f;
+
+ case NS_STYLE_VERTICAL_ALIGN_SUB:
+ case NS_STYLE_VERTICAL_ALIGN_SUPER:
+ case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
+ case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
+ return 0.5f;
+
+ case NS_STYLE_VERTICAL_ALIGN_BASELINE:
+ case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
+ case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
+ return 1.0f;
+
+ default:
+ NS_NOTREACHED("invalid vertical-align");
+ return 0.5f;
+ }
+}
+
+// These are the sizes that Gecko needs to request to draw if it wants
+// to get a standard-sized Aqua radio button drawn. Note that the rects
+// that draw these are actually a little bigger.
+static const CellRenderSettings radioSettings = {
+ {
+ NSMakeSize(11, 11), // mini
+ NSMakeSize(13, 13), // small
+ NSMakeSize(16, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {0, 1, 1, 1}, // small
+ {0, 0, 0, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 2}, // small
+ {0, 0, 0, 0} // regular
+ }
+ }
+};
+
+static const CellRenderSettings checkboxSettings = {
+ {
+ NSMakeSize(11, 11), // mini
+ NSMakeSize(13, 13), // small
+ NSMakeSize(16, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 1, 0, 0}, // mini
+ {0, 1, 0, 1}, // small
+ {0, 1, 0, 1} // regular
+ },
+ { // Yosemite
+ {0, 1, 0, 0}, // mini
+ {0, 1, 0, 1}, // small
+ {0, 1, 0, 1} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
+ const HIRect& inBoxRect, bool inSelected,
+ EventStates inState, nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
+ NSCellStateValue state = inSelected ? NSOnState : NSOffState;
+
+ // Check if we have an indeterminate checkbox
+ if (inCheckbox && GetIndeterminate(aFrame))
+ state = NSMixedState;
+
+ [cell setEnabled:!IsDisabled(aFrame, inState)];
+ [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS)];
+ [cell setState:state];
+ [cell setHighlighted:inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
+ [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
+
+ // Ensure that the control is square.
+ float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
+ HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
+ inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
+ length, length);
+
+ DrawCellWithSnapping(cell, cgContext, drawRect,
+ inCheckbox ? checkboxSettings : radioSettings,
+ VerticalAlignFactor(aFrame), mCellDrawView, NO);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings searchFieldSettings = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 19), // small
+ NSMakeSize(0, 22) // regular
+ },
+ {
+ NSMakeSize(32, 0), // mini
+ NSMakeSize(38, 0), // small
+ NSMakeSize(44, 0) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame, EventStates inState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ ContextAwareSearchFieldCell* cell = mSearchFieldCell;
+ [cell setContext:aFrame];
+ [cell setEnabled:!IsDisabled(aFrame, inState)];
+ // NOTE: this could probably use inState
+ [cell setShowsFirstResponder:IsFocused(aFrame)];
+
+ // When using the 10.11 SDK, the default string will be shown if we don't
+ // set the placeholder string.
+ [cell setPlaceholderString:@""];
+
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
+ VerticalAlignFactor(aFrame), mCellDrawView,
+ IsFrameRTL(aFrame));
+
+ [cell setContext:nullptr];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const NSSize kCheckmarkSize = NSMakeSize(11, 11);
+static const NSSize kMenuarrowSize = NSMakeSize(9, 10);
+static const NSSize kMenuScrollArrowSize = NSMakeSize(10, 8);
+static NSString* kCheckmarkImage = @"MenuOnState";
+static NSString* kMenuarrowRightImage = @"MenuSubmenu";
+static NSString* kMenuarrowLeftImage = @"MenuSubmenuLeft";
+static NSString* kMenuDownScrollArrowImage = @"MenuScrollDown";
+static NSString* kMenuUpScrollArrowImage = @"MenuScrollUp";
+static const CGFloat kMenuIconIndent = 6.0f;
+
+void
+nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
+ EventStates inState, nsIFrame* aFrame,
+ const NSSize& aIconSize, NSString* aImageName,
+ bool aCenterHorizontally)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Adjust size and position of our drawRect.
+ CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - aIconSize.width);
+ CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - aIconSize.height);
+ CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
+ CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
+ CGRect drawRect = CGRectMake(
+ aRect.origin.x + (aCenterHorizontally ? ceil(paddingX / 2) :
+ IsFrameRTL(aFrame) ? paddingEndX : paddingStartX),
+ aRect.origin.y + ceil(paddingY / 2),
+ aIconSize.width, aIconSize.height);
+
+ NSString* state = IsDisabled(aFrame, inState) ? @"disabled" :
+ (CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ? @"pressed" : @"normal");
+
+ NSString* imageName = aImageName;
+ if (!nsCocoaFeatures::OnElCapitanOrLater()) {
+ // Pre-10.11, image names are prefixed with "image."
+ imageName = [@"image." stringByAppendingString:aImageName];
+ }
+
+ RenderWithCoreUI(drawRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIBackgroundTypeMenu", @"backgroundTypeKey",
+ imageName, @"imageNameKey",
+ state, @"state",
+ @"image", @"widget",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil]);
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, drawRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
+static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
+
+static const CellRenderSettings pushButtonSettings = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 19), // small
+ NSMakeSize(0, 22) // regular
+ },
+ {
+ NSMakeSize(18, 0), // mini
+ NSMakeSize(26, 0), // small
+ NSMakeSize(30, 0) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {4, 0, 4, 1}, // small
+ {5, 0, 5, 2} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {4, 0, 4, 1}, // small
+ {5, 0, 5, 2} // regular
+ }
+ }
+};
+
+// The height at which we start doing square buttons instead of rounded buttons
+// Rounded buttons look bad if drawn at a height greater than 26, so at that point
+// we switch over to doing square buttons which looks fine at any size.
+#define DO_SQUARE_BUTTON_HEIGHT 26
+
+void
+nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame, float aOriginalHeight)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ BOOL isActive = FrameIsInActiveWindow(aFrame);
+ BOOL isDisabled = IsDisabled(aFrame, inState);
+
+ NSButtonCell* cell = (aWidgetType == NS_THEME_BUTTON) ? mPushButtonCell :
+ (aWidgetType == NS_THEME_MAC_HELP_BUTTON) ? mHelpButtonCell : mDisclosureButtonCell;
+ [cell setEnabled:!isDisabled];
+ [cell setHighlighted:isActive &&
+ inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
+ [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS) && !isDisabled && isActive];
+
+ if (aWidgetType != NS_THEME_BUTTON) { // Help button or disclosure button.
+ NSSize buttonSize = NSMakeSize(0, 0);
+ if (aWidgetType == NS_THEME_MAC_HELP_BUTTON) {
+ buttonSize = kHelpButtonSize;
+ } else { // Disclosure button.
+ buttonSize = kDisclosureButtonSize;
+ [cell setState:(aWidgetType == NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED) ? NSOffState : NSOnState];
+ }
+
+ DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
+ NSZeroSize, buttonSize, NULL, mCellDrawView,
+ false); // Don't mirror icon in RTL.
+ } else {
+ // If the button is tall enough, draw the square button style so that
+ // buttons with non-standard content look good. Otherwise draw normal
+ // rounded aqua buttons.
+ // This comparison is done based on the height that is calculated without
+ // the top, because the snapped height can be affected by the top of the
+ // rect and that may result in different height depending on the top value.
+ if (aOriginalHeight > DO_SQUARE_BUTTON_HEIGHT) {
+ [cell setBezelStyle:NSShadowlessSquareBezelStyle];
+ DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
+ NSZeroSize, NSMakeSize(14, 0), NULL, mCellDrawView,
+ IsFrameRTL(aFrame));
+ } else {
+ [cell setBezelStyle:NSRoundedBezelStyle];
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
+ mCellDrawView, IsFrameRTL(aFrame), 1.0f);
+ }
+ }
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeFrameDrawInfo fdi;
+ fdi.version = 0;
+ fdi.kind = kHIThemeFrameTextFieldSquare;
+ fdi.state = kThemeStateActive;
+ fdi.isFocused = TRUE;
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ HIThemeDrawFrame(&inBoxRect, &fdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData);
+
+static void
+RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
+ RenderHIThemeControlFunction aFunc, void* aData,
+ BOOL mirrorHorizontally = NO)
+{
+ CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
+ CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
+
+ bool drawDirect;
+ HIRect drawRect = aRect;
+ drawRect.origin = CGPointZero;
+
+ if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
+ savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
+ drawDirect = TRUE;
+ } else {
+ drawDirect = FALSE;
+ }
+
+ // Fall back to no bitmap buffer if the area of our control (in pixels^2)
+ // is too large.
+ if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
+ aFunc(aCGContext, drawRect, aData);
+ } else {
+ // Inflate the buffer to capture focus rings.
+ int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
+ int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
+
+ int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ CGContextRef bitmapctx = CGBitmapContextCreate(NULL,
+ w * backingScaleFactor,
+ h * backingScaleFactor,
+ 8,
+ w * backingScaleFactor * 4,
+ colorSpace,
+ kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(colorSpace);
+
+ CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
+ CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
+
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(bitmapctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
+
+ // HITheme always wants to draw into a flipped context, or things
+ // get confused.
+ CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
+ CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
+
+ aFunc(bitmapctx, drawRect, aData);
+
+ CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
+
+ CGAffineTransform ctm = CGContextGetCTM(aCGContext);
+
+ // We need to unflip, so that we can do a DrawImage without getting a flipped image.
+ CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
+ CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
+
+ if (mirrorHorizontally) {
+ CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
+ CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
+ }
+
+ HIRect inflatedDrawRect = CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
+ CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
+
+ CGContextSetCTM(aCGContext, ctm);
+
+ CGImageRelease(bitmap);
+ CGContextRelease(bitmapctx);
+ }
+
+ CGContextSetCTM(aCGContext, savedCTM);
+}
+
+static void
+RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
+{
+ HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
+ HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
+}
+
+void
+nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, bool inIsDefault,
+ ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
+ EventStates inState, nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ BOOL isActive = FrameIsInActiveWindow(aFrame);
+ BOOL isDisabled = IsDisabled(aFrame, inState);
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = inKind;
+ bdi.value = inValue;
+ bdi.adornment = inAdornment;
+
+ if (isDisabled) {
+ bdi.state = kThemeStateUnavailable;
+ }
+ else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
+ bdi.state = kThemeStatePressed;
+ }
+ else {
+ if (inKind == kThemeArrowButton)
+ bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable
+ else if (!isActive && inKind == kThemeListHeaderButton)
+ bdi.state = kThemeStateInactive;
+ else
+ bdi.state = kThemeStateActive;
+ }
+
+ if (inState.HasState(NS_EVENT_STATE_FOCUS) && isActive)
+ bdi.adornment |= kThemeAdornmentFocus;
+
+ if (inIsDefault && !isDisabled &&
+ !inState.HasState(NS_EVENT_STATE_ACTIVE)) {
+ bdi.adornment |= kThemeAdornmentDefault;
+ bdi.animation.time.start = 0;
+ bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
+ }
+
+ HIRect drawFrame = inBoxRect;
+
+ if (inKind == kThemePushButton) {
+ drawFrame.size.height -= 2;
+ if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) {
+ bdi.kind = kThemePushButtonMini;
+ }
+ else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) {
+ bdi.kind = kThemePushButtonSmall;
+ drawFrame.origin.y -= 1;
+ drawFrame.origin.x += 1;
+ drawFrame.size.width -= 2;
+ }
+ }
+ else if (inKind == kThemeListHeaderButton) {
+ CGContextClipToRect(cgContext, inBoxRect);
+ // Always remove the top border.
+ drawFrame.origin.y -= 1;
+ drawFrame.size.height += 1;
+ // Remove the left border in LTR mode and the right border in RTL mode.
+ drawFrame.size.width += 1;
+ bool isLast = IsLastTreeHeaderCell(aFrame);
+ if (isLast)
+ drawFrame.size.width += 1; // Also remove the other border.
+ if (!IsFrameRTL(aFrame) || isLast)
+ drawFrame.origin.x -= 1;
+ }
+
+ RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
+ IsFrameRTL(aFrame));
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings dropdownSettings = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 19), // small
+ NSMakeSize(0, 22) // regular
+ },
+ {
+ NSMakeSize(18, 0), // mini
+ NSMakeSize(38, 0), // small
+ NSMakeSize(44, 0) // regular
+ },
+ {
+ { // Leopard
+ {1, 1, 2, 1}, // mini
+ {3, 0, 3, 1}, // small
+ {3, 0, 3, 0} // regular
+ },
+ { // Yosemite
+ {1, 1, 2, 1}, // mini
+ {3, 0, 3, 1}, // small
+ {3, 0, 3, 0} // regular
+ }
+ }
+};
+
+static const CellRenderSettings editableMenulistSettings = {
+ {
+ NSMakeSize(0, 15), // mini
+ NSMakeSize(0, 18), // small
+ NSMakeSize(0, 21) // regular
+ },
+ {
+ NSMakeSize(18, 0), // mini
+ NSMakeSize(38, 0), // small
+ NSMakeSize(44, 0) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 2, 2}, // mini
+ {0, 0, 3, 2}, // small
+ {0, 1, 3, 3} // regular
+ },
+ { // Yosemite
+ {0, 0, 2, 2}, // mini
+ {0, 0, 3, 2}, // small
+ {0, 1, 3, 3} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)];
+
+ BOOL isEditable = (aWidgetType == NS_THEME_MENULIST_TEXTFIELD);
+ NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
+
+ [cell setEnabled:!IsDisabled(aFrame, inState)];
+ [cell setShowsFirstResponder:(IsFocused(aFrame) || inState.HasState(NS_EVENT_STATE_FOCUS))];
+ [cell setHighlighted:IsOpenButton(aFrame)];
+ [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
+
+ const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
+ 0.5f, mCellDrawView, IsFrameRTL(aFrame));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings spinnerSettings = {
+ {
+ NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
+ NSMakeSize(15, 22), // small
+ NSMakeSize(19, 27) // regular
+ },
+ {
+ NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
+ NSMakeSize(15, 22), // small
+ NSMakeSize(19, 27) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ EventStates inState, nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = inKind;
+ bdi.value = kThemeButtonOff;
+ bdi.adornment = inAdornment;
+
+ if (IsDisabled(aFrame, inState))
+ bdi.state = kThemeStateUnavailable;
+ else
+ bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
+
+ HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
+ ThemeButtonKind inKind,
+ const HIRect& inBoxRect,
+ ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ EventStates inState,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aWidgetType == NS_THEME_SPINNER_UPBUTTON ||
+ aWidgetType == NS_THEME_SPINNER_DOWNBUTTON);
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = inKind;
+ bdi.value = kThemeButtonOff;
+ bdi.adornment = inAdornment;
+
+ if (IsDisabled(aFrame, inState))
+ bdi.state = kThemeStateUnavailable;
+ else
+ bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
+
+ // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
+ // together as a single unit (presumably because when one button is active,
+ // the appearance of both changes (in different ways)). Here we have to paint
+ // both buttons, using clip to hide the one we don't want to paint.
+ HIRect drawRect = inBoxRect;
+ drawRect.size.height *= 2;
+ if (aWidgetType == NS_THEME_SPINNER_DOWNBUTTON) {
+ drawRect.origin.y -= inBoxRect.size.height;
+ }
+
+ // Shift the drawing a little to the left, since cocoa paints with more
+ // blank space around the visual buttons than we'd like:
+ drawRect.origin.x -= 1;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
+ const HIRect& inBoxRect, bool inDisabled,
+ EventStates inState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeFrameDrawInfo fdi;
+ fdi.version = 0;
+ fdi.kind = inKind;
+
+ // We don't ever set an inactive state for this because it doesn't
+ // look right (see other apps).
+ fdi.state = inDisabled ? kThemeStateUnavailable : kThemeStateActive;
+
+ // for some reason focus rings on listboxes draw incorrectly
+ if (inKind == kHIThemeFrameListBox)
+ fdi.isFocused = 0;
+ else
+ fdi.isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
+
+ // HIThemeDrawFrame takes the rect for the content area of the frame, not
+ // the bounding rect for the frame. Here we reduce the size of the rect we
+ // will pass to make it the size of the content.
+ HIRect drawRect = inBoxRect;
+ if (inKind == kHIThemeFrameTextFieldSquare) {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
+ drawRect.origin.x += frameOutset;
+ drawRect.origin.y += frameOutset;
+ drawRect.size.width -= frameOutset * 2;
+ drawRect.size.height -= frameOutset * 2;
+ }
+ else if (inKind == kHIThemeFrameListBox) {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
+ drawRect.origin.x += frameOutset;
+ drawRect.origin.y += frameOutset;
+ drawRect.size.width -= frameOutset * 2;
+ drawRect.size.height -= frameOutset * 2;
+ }
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings progressSettings[2][2] = {
+ // Vertical progress bar.
+ {
+ // Determined settings.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(10, 0), // small
+ NSMakeSize(16, 0) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ }
+ }
+ },
+ // There is no horizontal margin in regular undetermined size.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(10, 0), // small
+ NSMakeSize(16, 0) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 0, 1, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 0, 1, 0} // regular
+ }
+ }
+ }
+ },
+ // Horizontal progress bar.
+ {
+ // Determined settings.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(0, 10), // small
+ NSMakeSize(0, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ }
+ }
+ },
+ // There is no horizontal margin in regular undetermined size.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(0, 10), // small
+ NSMakeSize(0, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {0, 1, 0, 1} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {0, 1, 0, 1} // regular
+ }
+ }
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
+ bool inIsIndeterminate, bool inIsHorizontal,
+ double inValue, double inMaxValue,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSProgressBarCell* cell = mProgressBarCell;
+
+ [cell setValue:inValue];
+ [cell setMax:inMaxValue];
+ [cell setIndeterminate:inIsIndeterminate];
+ [cell setHorizontal:inIsHorizontal];
+ [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint]
+ : NSClearControlTint)];
+
+ DrawCellWithSnapping(cell, cgContext, inBoxRect,
+ progressSettings[inIsHorizontal][inIsIndeterminate],
+ VerticalAlignFactor(aFrame), mCellDrawView,
+ IsFrameRTL(aFrame));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings meterSetting = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 16), // small
+ NSMakeSize(0, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {1, 1, 1, 1}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ },
+ { // Yosemite
+ {1, 1, 1, 1}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK
+
+ NS_PRECONDITION(aFrame, "aFrame should not be null here!");
+
+ // When using -moz-meterbar on an non meter element, we will not be able to
+ // get all the needed information so we just draw an empty meter.
+ nsIContent* content = aFrame->GetContent();
+ if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
+ DrawCellWithSnapping(mMeterBarCell, cgContext, inBoxRect,
+ meterSetting, VerticalAlignFactor(aFrame),
+ mCellDrawView, IsFrameRTL(aFrame));
+ return;
+ }
+
+ HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
+ double value = meterElement->Value();
+ double min = meterElement->Min();
+ double max = meterElement->Max();
+
+ NSLevelIndicatorCell* cell = mMeterBarCell;
+
+ [cell setMinValue:min];
+ [cell setMaxValue:max];
+ [cell setDoubleValue:value];
+
+ /**
+ * The way HTML and Cocoa defines the meter/indicator widget are different.
+ * So, we are going to use a trick to get the Cocoa widget showing what we
+ * are expecting: we set the warningValue or criticalValue to the current
+ * value when we want to have the widget to be in the warning or critical
+ * state.
+ */
+ EventStates states = aFrame->GetContent()->AsElement()->State();
+
+ // Reset previously set warning and critical values.
+ [cell setWarningValue:max+1];
+ [cell setCriticalValue:max+1];
+
+ if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
+ [cell setWarningValue:value];
+ } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
+ [cell setCriticalValue:value];
+ }
+
+ HIRect rect = CGRectStandardize(inBoxRect);
+ BOOL vertical = IsVerticalMeter(aFrame);
+
+ CGContextSaveGState(cgContext);
+
+ if (vertical) {
+ /**
+ * Cocoa doesn't provide a vertical meter bar so to show one, we have to
+ * show a rotated horizontal meter bar.
+ * Given that we want to show a vertical meter bar, we assume that the rect
+ * has vertical dimensions but we can't correctly draw a meter widget inside
+ * such a rectangle so we need to inverse width and height (and re-position)
+ * to get a rectangle with horizontal dimensions.
+ * Finally, we want to show a vertical meter so we want to rotate the result
+ * so it is vertical. We do that by changing the context.
+ */
+ CGFloat tmp = rect.size.width;
+ rect.size.width = rect.size.height;
+ rect.size.height = tmp;
+ rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
+ rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
+
+ CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
+ CGContextRotateCTM(cgContext, -M_PI / 2.f);
+ CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
+ }
+
+ DrawCellWithSnapping(cell, cgContext, rect,
+ meterSetting, VerticalAlignFactor(aFrame),
+ mCellDrawView, !vertical && IsFrameRTL(aFrame));
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+void
+nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeTabPaneDrawInfo tpdi;
+
+ tpdi.version = 1;
+ tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive;
+ tpdi.direction = kThemeTabNorth;
+ tpdi.size = kHIThemeTabSizeNormal;
+ tpdi.kind = kHIThemeTabKindNormal;
+
+ HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, bool inIsVertical,
+ bool inIsReverse, int32_t inCurrentValue,
+ int32_t inMinValue, int32_t inMaxValue,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeTrackDrawInfo tdi;
+
+ tdi.version = 0;
+ tdi.kind = kThemeMediumSlider;
+ tdi.bounds = inBoxRect;
+ tdi.min = inMinValue;
+ tdi.max = inMaxValue;
+ tdi.value = inCurrentValue;
+ tdi.attributes = kThemeTrackShowThumb;
+ if (!inIsVertical)
+ tdi.attributes |= kThemeTrackHorizontal;
+ if (inIsReverse)
+ tdi.attributes |= kThemeTrackRightToLeft;
+ if (inState.HasState(NS_EVENT_STATE_FOCUS))
+ tdi.attributes |= kThemeTrackHasFocus;
+ if (IsDisabled(aFrame, inState))
+ tdi.enableState = kThemeTrackDisabled;
+ else
+ tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
+ tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
+ tdi.trackInfo.slider.pressState = 0;
+
+ HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsIFrame*
+nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter)
+{
+ // Usually a separator is drawn by the segment to the right of the
+ // separator, but pressed and selected segments have higher priority.
+ if (!aBefore || !aAfter)
+ return nullptr;
+ if (IsSelectedButton(aAfter))
+ return aAfter;
+ if (IsSelectedButton(aBefore) || IsPressedButton(aBefore))
+ return aBefore;
+ return aAfter;
+}
+
+CGRect
+nsNativeThemeCocoa::SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
+ nsIFrame* aCurrent, nsIFrame* aRight)
+{
+ // A separator between two segments should always be located in the leftmost
+ // pixel column of the segment to the right of the separator, regardless of
+ // who ends up drawing it.
+ // CoreUI draws the separators inside the drawing rect.
+ if (aLeft && SeparatorResponsibility(aLeft, aCurrent) == aLeft) {
+ // The left button draws the separator, so we need to make room for it.
+ aRect.origin.x += 1;
+ aRect.size.width -= 1;
+ }
+ if (SeparatorResponsibility(aCurrent, aRight) == aCurrent) {
+ // We draw the right separator, so we need to extend the draw rect into the
+ // segment to our right.
+ aRect.size.width += 1;
+ }
+ return aRect;
+}
+
+static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast)
+{
+ if (aIsFirst) {
+ if (aIsLast)
+ return @"kCUISegmentPositionOnly";
+ return @"kCUISegmentPositionFirst";
+ }
+ if (aIsLast)
+ return @"kCUISegmentPositionLast";
+ return @"kCUISegmentPositionMiddle";
+}
+
+struct SegmentedControlRenderSettings {
+ const CGFloat* heights;
+ const NSString* widgetName;
+ const BOOL ignoresPressedWhenSelected;
+ const BOOL isToolbarControl;
+};
+
+static const CGFloat tabHeights[3] = { 17, 20, 23 };
+
+static const SegmentedControlRenderSettings tabRenderSettings = {
+ tabHeights, @"tab", YES, NO
+};
+
+static const CGFloat toolbarButtonHeights[3] = { 15, 18, 22 };
+
+static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
+ toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve", NO, YES
+};
+
+void
+nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, nsIFrame* aFrame,
+ const SegmentedControlRenderSettings& aSettings)
+{
+ BOOL isActive = IsActive(aFrame, aSettings.isToolbarControl);
+ BOOL isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
+ BOOL isSelected = IsSelectedButton(aFrame);
+ BOOL isPressed = IsPressedButton(aFrame);
+ if (isSelected && aSettings.ignoresPressedWhenSelected) {
+ isPressed = NO;
+ }
+
+ BOOL isRTL = IsFrameRTL(aFrame);
+ nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
+ nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
+ CGRect drawRect = SeparatorAdjustedRect(inBoxRect, left, aFrame, right);
+ BOOL drawLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
+ BOOL drawRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
+ NSControlSize controlSize = FindControlSize(drawRect.size.height, aSettings.heights, 4.0f);
+
+ RenderWithCoreUI(drawRect, cgContext, [NSDictionary dictionaryWithObjectsAndKeys:
+ aSettings.widgetName, @"widget",
+ (isActive ? @"kCUIPresentationStateActiveKey" : @"kCUIPresentationStateInactive"), @"kCUIPresentationStateKey",
+ ToolbarButtonPosition(!left, !right), @"kCUIPositionKey",
+ [NSNumber numberWithBool:drawLeftSeparator], @"kCUISegmentLeadingSeparatorKey",
+ [NSNumber numberWithBool:drawRightSeparator], @"kCUISegmentTrailingSeparatorKey",
+ [NSNumber numberWithBool:isSelected], @"value",
+ (isPressed ? @"pressed" : (isActive ? @"normal" : @"inactive")), @"state",
+ [NSNumber numberWithBool:isFocused], @"focus",
+ CUIControlSizeForCocoaSize(controlSize), @"size",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ @"up", @"direction",
+ nil]);
+}
+
+void
+nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame* aFrame,
+ EventStates aButtonStates[])
+{
+ static nsIContent::AttrValuesArray attributeValues[] = {
+ &nsGkAtoms::scrollbarUpTop,
+ &nsGkAtoms::scrollbarDownTop,
+ &nsGkAtoms::scrollbarUpBottom,
+ &nsGkAtoms::scrollbarDownBottom,
+ nullptr
+ };
+
+ // Get the state of any scrollbar buttons in our child frames
+ for (nsIFrame *childFrame : aFrame->PrincipalChildList()) {
+ nsIContent *childContent = childFrame->GetContent();
+ if (!childContent) continue;
+ int32_t attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sbattr,
+ attributeValues, eCaseMatters);
+ if (attrIndex < 0) continue;
+
+ aButtonStates[attrIndex] = GetContentState(childFrame, NS_THEME_BUTTON);
+ }
+}
+
+nsIFrame*
+nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)
+{
+ // Walk our parents to find a scrollbar frame
+ nsIFrame *scrollbarFrame = aFrame;
+ do {
+ if (scrollbarFrame->GetType() == nsGkAtoms::scrollbarFrame) break;
+ } while ((scrollbarFrame = scrollbarFrame->GetParent()));
+
+ // We return null if we can't find a parent scrollbar frame
+ return scrollbarFrame;
+}
+
+static bool
+ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
+{
+ if (![aWindow isKindOfClass:[ToolbarWindow class]])
+ return false;
+
+ ToolbarWindow* win = (ToolbarWindow*)aWindow;
+ float unifiedToolbarHeight = [win unifiedToolbarHeight];
+ return inBoxRect.origin.x == 0 &&
+ inBoxRect.size.width >= [win frame].size.width &&
+ CGRectGetMaxY(inBoxRect) <= unifiedToolbarHeight;
+}
+
+// By default, kCUIWidgetWindowFrame drawing draws rounded corners in the
+// upper corners. Depending on the context type, it fills the background in
+// the corners with black or leaves it transparent. Unfortunately, this corner
+// rounding interacts poorly with the window corner masking we apply during
+// titlebar drawing and results in small remnants of the corner background
+// appearing at the rounded edge.
+// So we draw square corners.
+static void
+DrawNativeTitlebarToolbarWithSquareCorners(CGContextRef aContext, const CGRect& aRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped)
+{
+ // We extend the draw rect horizontally and clip away the rounded corners.
+ const CGFloat extendHorizontal = 10;
+ CGRect drawRect = CGRectInset(aRect, -extendHorizontal, 0);
+ CGContextSaveGState(aContext);
+ CGContextClipToRect(aContext, aRect);
+
+ RenderWithCoreUI(drawRect, aContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIWidgetWindowFrame", @"widget",
+ @"regularwin", @"windowtype",
+ (aIsMain ? @"normal" : @"inactive"), @"state",
+ [NSNumber numberWithDouble:aUnifiedHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey",
+ [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey",
+ [NSNumber numberWithBool:aIsFlipped], @"is.flipped",
+ nil]);
+
+ CGContextRestoreGState(aContext);
+}
+
+void
+nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
+ NSWindow* aWindow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ CGFloat unifiedHeight = std::max([(ToolbarWindow*)aWindow unifiedToolbarHeight],
+ inBoxRect.size.height);
+ BOOL isMain = [aWindow isMainWindow];
+ CGFloat titlebarHeight = unifiedHeight - inBoxRect.size.height;
+ CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight,
+ inBoxRect.size.width, inBoxRect.size.height + titlebarHeight);
+ DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, unifiedHeight, isMain, YES);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame *aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (inBoxRect.size.height < 2.0f)
+ return;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ // kCUIWidgetWindowFrame draws a complete window frame with both title bar
+ // and bottom bar. We only want the bottom bar, so we extend the draw rect
+ // upwards to make space for the title bar, and then we clip it away.
+ CGRect drawRect = inBoxRect;
+ const int extendUpwards = 40;
+ drawRect.origin.y -= extendUpwards;
+ drawRect.size.height += extendUpwards;
+ RenderWithCoreUI(drawRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIWidgetWindowFrame", @"widget",
+ @"regularwin", @"windowtype",
+ (IsActive(aFrame, YES) ? @"normal" : @"inactive"), @"state",
+ [NSNumber numberWithInt:inBoxRect.size.height], @"kCUIWindowFrameBottomBarHeightKey",
+ [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawBottomBarSeparatorKey",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil]);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped)
+{
+ CGFloat unifiedHeight = std::max(aUnifiedHeight, aTitlebarRect.size.height);
+ DrawNativeTitlebarToolbarWithSquareCorners(aContext, aTitlebarRect, unifiedHeight, aIsMain, aIsFlipped);
+}
+
+static void
+RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
+{
+ HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
+ HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
+}
+
+void
+nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
+ nsIFrame *aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeGrowBoxDrawInfo drawInfo;
+ drawInfo.version = 0;
+ drawInfo.state = kThemeStateActive;
+ drawInfo.kind = kHIThemeGrowBoxKindNormal;
+ drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
+ drawInfo.size = kHIThemeGrowBoxSizeNormal;
+
+ RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
+ IsFrameRTL(aFrame));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static void
+DrawVibrancyBackground(CGContextRef cgContext, CGRect inBoxRect,
+ nsIFrame* aFrame, nsITheme::ThemeGeometryType aThemeGeometryType,
+ int aCornerRadiusIfOpaque = 0)
+{
+ ChildView* childView = ChildViewForFrame(aFrame);
+ if (childView) {
+ NSRect rect = NSRectFromCGRect(inBoxRect);
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+ [NSGraphicsContext saveGraphicsState];
+
+ NSColor* fillColor = [childView vibrancyFillColorForThemeGeometryType:aThemeGeometryType];
+ if ([fillColor alphaComponent] == 1.0 && aCornerRadiusIfOpaque > 0) {
+ // The fillColor being opaque means that the system-wide pref "reduce
+ // transparency" is set. In that scenario, we still go through all the
+ // vibrancy rendering paths (VibrancyManager::SystemSupportsVibrancy()
+ // will still return true), but the result just won't look "vibrant".
+ // However, there's one unfortunate change of behavior that this pref
+ // has: It stops the window server from applying window masks. We use
+ // a window mask to get rounded corners on menus. So since the mask
+ // doesn't work in "reduce vibrancy" mode, we need to do our own rounded
+ // corner clipping here.
+ [[NSBezierPath bezierPathWithRoundedRect:rect
+ xRadius:aCornerRadiusIfOpaque
+ yRadius:aCornerRadiusIfOpaque] addClip];
+ }
+
+ [fillColor set];
+ NSRectFill(rect);
+
+ [NSGraphicsContext restoreGraphicsState];
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+}
+
+bool
+nsNativeThemeCocoa::IsParentScrollbarRolledOver(nsIFrame* aFrame)
+{
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ return nsLookAndFeel::UseOverlayScrollbars()
+ ? CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
+ : GetContentState(scrollbarFrame, NS_THEME_NONE).HasState(NS_EVENT_STATE_HOVER);
+}
+
+static bool
+IsHiDPIContext(nsPresContext* aContext)
+{
+ return nsPresContext::AppUnitsPerCSSPixel() >=
+ 2 * aContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ // setup to draw into the correct port
+ int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ gfx::Rect nativeDirtyRect = NSRectToRect(aDirtyRect, p2a);
+ gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
+ nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
+ float nativeWidgetHeight = round(nativeWidgetRect.Height());
+ nativeWidgetRect.Round();
+ if (nativeWidgetRect.IsEmpty())
+ return NS_OK; // Don't attempt to draw invisible widgets.
+
+ AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
+
+ bool hidpi = IsHiDPIContext(aFrame->PresContext());
+ if (hidpi) {
+ // Use high-resolution drawing.
+ nativeWidgetRect.Scale(0.5f);
+ nativeWidgetHeight *= 0.5f;
+ nativeDirtyRect.Scale(0.5f);
+ aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(2.0f, 2.0f));
+ }
+
+ gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, nativeDirtyRect);
+
+ CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
+ if (cgContext == nullptr) {
+ // The Quartz surface handles 0x0 surfaces by internally
+ // making all operations no-ops; there's no cgcontext created for them.
+ // Unfortunately, this means that callers that want to render
+ // directly to the CGContext need to be aware of this quirk.
+ return NS_OK;
+ }
+
+ if (hidpi) {
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(2, 2));
+ }
+
+#if 0
+ if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) {
+ fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n",
+ aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height);
+ fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n",
+ mat._11, mat._12, mat._21, mat._22, mat._31, mat._32);
+ fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n",
+ mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty);
+ CGAffineTransform mm = CGContextGetCTM(cgContext);
+ fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n",
+ mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty);
+ }
+#endif
+
+ CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(),
+ nativeWidgetRect.Width(), nativeWidgetRect.Height());
+
+#if 0
+ fprintf(stderr, " --> macRect %f %f %f %f\n",
+ macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height);
+ CGRect bounds = CGContextGetClipBoundingBox(cgContext);
+ fprintf(stderr, " --> clip bounds: %f %f %f %f\n",
+ bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
+
+ //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1);
+ //CGContextFillRect(cgContext, bounds);
+#endif
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ switch (aWidgetType) {
+ case NS_THEME_DIALOG: {
+ if (IsWindowSheet(aFrame)) {
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ HIThemeSetFill(kThemeBrushSheetBackgroundTransparent, NULL, cgContext, HITHEME_ORIENTATION);
+ CGContextFillRect(cgContext, macRect);
+ }
+ } else {
+ HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
+ CGContextFillRect(cgContext, macRect);
+ }
+
+ }
+ break;
+
+ case NS_THEME_MENUPOPUP:
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ DrawVibrancyBackground(cgContext, macRect, aFrame, eThemeGeometryTypeMenu, 4);
+ } else {
+ HIThemeMenuDrawInfo mdi;
+ memset(&mdi, 0, sizeof(mdi));
+ mdi.version = 0;
+ mdi.menuType = IsDisabled(aFrame, eventState) ?
+ static_cast<ThemeMenuType>(kThemeMenuTypeInactive) :
+ static_cast<ThemeMenuType>(kThemeMenuTypePopUp);
+
+ bool isLeftOfParent = false;
+ if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) {
+ mdi.menuType = kThemeMenuTypeHierarchical;
+ }
+
+ // The rounded corners draw outside the frame.
+ CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4,
+ macRect.size.width, macRect.size.height - 8);
+ HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
+ }
+ break;
+
+ case NS_THEME_MENUARROW: {
+ bool isRTL = IsFrameRTL(aFrame);
+ DrawMenuIcon(cgContext, macRect, eventState, aFrame, kMenuarrowSize,
+ isRTL ? kMenuarrowLeftImage : kMenuarrowRightImage, true);
+ }
+ break;
+
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM: {
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ bool isDisabled = IsDisabled(aFrame, eventState);
+ bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain?
+ HIThemeMenuItemDrawInfo drawInfo;
+ memset(&drawInfo, 0, sizeof(drawInfo));
+ drawInfo.version = 0;
+ drawInfo.itemType = kThemeMenuItemPlain;
+ drawInfo.state = (isDisabled ?
+ static_cast<ThemeMenuState>(kThemeMenuDisabled) :
+ isSelected ?
+ static_cast<ThemeMenuState>(kThemeMenuSelected) :
+ static_cast<ThemeMenuState>(kThemeMenuActive));
+
+ // XXX pass in the menu rect instead of always using the item rect
+ HIRect ignored;
+ HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored);
+ }
+
+ if (aWidgetType == NS_THEME_CHECKMENUITEM) {
+ DrawMenuIcon(cgContext, macRect, eventState, aFrame, kCheckmarkSize, kCheckmarkImage, false);
+ }
+ }
+ break;
+
+ case NS_THEME_MENUSEPARATOR: {
+ ThemeMenuState menuState;
+ if (IsDisabled(aFrame, eventState)) {
+ menuState = kThemeMenuDisabled;
+ }
+ else {
+ menuState = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
+ kThemeMenuSelected : kThemeMenuActive;
+ }
+
+ HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState };
+ HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION);
+ }
+ break;
+
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ DrawMenuIcon(cgContext, macRect, eventState, aFrame, kMenuScrollArrowSize,
+ aWidgetType == NS_THEME_BUTTON_ARROW_UP ?
+ kMenuUpScrollArrowImage : kMenuDownScrollArrowImage, true);
+ break;
+
+ case NS_THEME_TOOLTIP:
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ DrawVibrancyBackground(cgContext, macRect, aFrame, ThemeGeometryTypeForWidget(aFrame, aWidgetType));
+ } else {
+ CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
+ CGContextFillRect(cgContext, macRect);
+ }
+ break;
+
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO: {
+ bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
+ DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox),
+ eventState, aFrame);
+ }
+ break;
+
+ case NS_THEME_BUTTON:
+ if (IsDefaultButton(aFrame)) {
+ // Check whether the default button is in a document that does not
+ // match the :-moz-window-inactive pseudoclass. This activeness check
+ // is different from the other "active window" checks in this file
+ // because we absolutely need the button's default button appearance to
+ // be in sync with its text color, and the text color is changed by
+ // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
+ // default buttons in active windows have blue background and white
+ // text, and default buttons in inactive windows have white background
+ // and black text.)
+ EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ bool isInActiveWindow = !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+ if (!IsDisabled(aFrame, eventState) && isInActiveWindow &&
+ !QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) {
+ NS_WARNING("Unable to animate button!");
+ }
+ DrawButton(cgContext, kThemePushButton, macRect, isInActiveWindow,
+ kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
+ } else if (IsButtonTypeMenu(aFrame)) {
+ DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
+ } else {
+ DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame,
+ nativeWidgetHeight);
+ }
+ break;
+
+ case NS_THEME_FOCUS_OUTLINE:
+ DrawFocusOutline(cgContext, macRect, eventState, aWidgetType, aFrame);
+ break;
+
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame,
+ nativeWidgetHeight);
+ break;
+
+ case NS_THEME_BUTTON_BEVEL:
+ DrawButton(cgContext, kThemeMediumBevelButton, macRect,
+ IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone,
+ eventState, aFrame);
+ break;
+
+ case NS_THEME_SPINNER: {
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsHTMLElement()) {
+ // In HTML the theming for the spin buttons is drawn individually into
+ // their own backgrounds instead of being drawn into the background of
+ // their spinner parent as it is for XUL.
+ break;
+ }
+ ThemeDrawState state = kThemeStateActive;
+ if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
+ NS_LITERAL_STRING("up"), eCaseMatters)) {
+ state = kThemeStatePressedUp;
+ }
+ else if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
+ NS_LITERAL_STRING("down"), eCaseMatters)) {
+ state = kThemeStatePressedDown;
+ }
+
+ DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state,
+ kThemeAdornmentNone, eventState, aFrame);
+ }
+ break;
+
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON: {
+ nsNumberControlFrame* numberControlFrame =
+ nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+ if (numberControlFrame) {
+ ThemeDrawState state = kThemeStateActive;
+ if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
+ state = kThemeStatePressedUp;
+ } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
+ state = kThemeStatePressedDown;
+ }
+ DrawSpinButton(cgContext, kThemeIncDecButtonMini, macRect, state,
+ kThemeAdornmentNone, eventState, aFrame, aWidgetType);
+ }
+ }
+ break;
+
+ case NS_THEME_TOOLBARBUTTON:
+ DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings);
+ break;
+
+ case NS_THEME_SEPARATOR: {
+ HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
+ HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
+ }
+ break;
+
+ case NS_THEME_TOOLBAR: {
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ if (ToolbarCanBeUnified(cgContext, macRect, win)) {
+ DrawUnifiedToolbar(cgContext, macRect, win);
+ break;
+ }
+ BOOL isMain = [win isMainWindow];
+ CGRect drawRect = macRect;
+
+ // top border
+ drawRect.size.height = 1.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain);
+
+ // background
+ drawRect.origin.y += drawRect.size.height;
+ drawRect.size.height = macRect.size.height - 2.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, isMain);
+
+ // bottom border
+ drawRect.origin.y += drawRect.size.height;
+ drawRect.size.height = 1.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, isMain);
+ }
+ break;
+
+ case NS_THEME_WINDOW_TITLEBAR: {
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ BOOL isMain = [win isMainWindow];
+ float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]] ?
+ [(ToolbarWindow*)win unifiedToolbarHeight] : macRect.size.height;
+ DrawNativeTitlebar(cgContext, macRect, unifiedToolbarHeight, isMain, YES);
+ }
+ break;
+
+ case NS_THEME_STATUSBAR:
+ DrawStatusBar(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
+ break;
+
+ case NS_THEME_MENULIST_BUTTON:
+ DrawButton(cgContext, kThemeArrowButton, macRect, false, kThemeButtonOn,
+ kThemeAdornmentArrowDownArrow, eventState, aFrame);
+ break;
+
+ case NS_THEME_GROUPBOX: {
+ HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary };
+ HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
+ break;
+ }
+
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_NUMBER_INPUT:
+ // HIThemeSetFill is not available on 10.3
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(cgContext, macRect);
+
+ // XUL textboxes set the native appearance on the containing box, while
+ // concrete focus is set on the html:input element within it. We can
+ // though, check the focused attribute of xul textboxes in this case.
+ // On Mac, focus rings are always shown for textboxes, so we do not need
+ // to check the window's focus ring state here
+ if (aFrame->GetContent()->IsXULElement() && IsFocused(aFrame)) {
+ eventState |= NS_EVENT_STATE_FOCUS;
+ }
+
+ DrawFrame(cgContext, kHIThemeFrameTextFieldSquare, macRect,
+ IsDisabled(aFrame, eventState) || IsReadOnly(aFrame), eventState);
+ break;
+
+ case NS_THEME_SEARCHFIELD:
+ DrawSearchField(cgContext, macRect, aFrame, eventState);
+ break;
+
+ case NS_THEME_PROGRESSBAR:
+ {
+ double value = GetProgressValue(aFrame);
+ double maxValue = GetProgressMaxValue(aFrame);
+ // Don't request repaints for scrollbars at 100% because those don't animate.
+ if (value < maxValue) {
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("Unable to animate progressbar!");
+ }
+ }
+ DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
+ !IsVerticalProgress(aFrame),
+ value, maxValue, aFrame);
+ break;
+ }
+
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
+ false, GetProgressValue(aFrame),
+ GetProgressMaxValue(aFrame), aFrame);
+ break;
+
+ case NS_THEME_METERBAR:
+ DrawMeter(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_METERCHUNK:
+ // Do nothing: progress and meter bars cases will draw chunks.
+ break;
+
+ case NS_THEME_TREETWISTY:
+ DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
+ kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame);
+ break;
+
+ case NS_THEME_TREETWISTYOPEN:
+ DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
+ kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame);
+ break;
+
+ case NS_THEME_TREEHEADERCELL: {
+ TreeSortDirection sortDirection = GetTreeSortDirection(aFrame);
+ DrawButton(cgContext, kThemeListHeaderButton, macRect, false,
+ sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn,
+ sortDirection == eTreeSortDirection_Ascending ?
+ kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame);
+ }
+ break;
+
+ case NS_THEME_TREEITEM:
+ case NS_THEME_TREEVIEW:
+ // HIThemeSetFill is not available on 10.3
+ // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION);
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(cgContext, macRect);
+ break;
+
+ case NS_THEME_TREEHEADER:
+ // do nothing, taken care of by individual header cells
+ case NS_THEME_TREEHEADERSORTARROW:
+ // do nothing, taken care of by treeview header
+ case NS_THEME_TREELINE:
+ // do nothing, these lines don't exist on macos
+ break;
+
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL: {
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
+ if (!maxpos)
+ maxpos = 100;
+
+ bool reverse = aFrame->GetContent()->
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ NS_LITERAL_STRING("reverse"), eCaseMatters);
+ DrawScale(cgContext, macRect, eventState,
+ (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse,
+ curpos, minpos, maxpos, aFrame);
+ }
+ break;
+
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ // do nothing, drawn by scale
+ break;
+
+ case NS_THEME_RANGE: {
+ nsRangeFrame *rangeFrame = do_QueryFrame(aFrame);
+ if (!rangeFrame) {
+ break;
+ }
+ // DrawScale requires integer min, max and value. This is purely for
+ // drawing, so we normalize to a range 0-1000 here.
+ int32_t value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
+ int32_t min = 0;
+ int32_t max = 1000;
+ bool isVertical = !IsRangeHorizontal(aFrame);
+ bool reverseDir = isVertical || rangeFrame->IsRightToLeft();
+ DrawScale(cgContext, macRect, eventState, isVertical, reverseDir,
+ value, min, max, aFrame);
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR:
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: {
+ BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
+ BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL);
+ BOOL isRolledOver = IsParentScrollbarRolledOver(aFrame);
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ bool isSmall = (scrollbarFrame && scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
+ if (isOverlay && !isRolledOver) {
+ if (isHorizontal) {
+ macRect.origin.y += 4;
+ macRect.size.height -= 4;
+ } else {
+ if (aFrame->StyleVisibility()->mDirection !=
+ NS_STYLE_DIRECTION_RTL) {
+ macRect.origin.x += 4;
+ }
+ macRect.size.width -= 4;
+ }
+ }
+ const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
+ NSMutableDictionary* options = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ (isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
+ (isSmall ? @"small" : @"regular"), @"size",
+ (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
+ (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
+ [NSNumber numberWithBool:YES], @"indiconly",
+ [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil];
+ if (isRolledOver) {
+ [options setObject:@"rollover" forKey:@"state"];
+ }
+ RenderWithCoreUI(macRect, cgContext, options, true);
+ }
+ break;
+
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+#if SCROLLBARS_VISUAL_DEBUG
+ CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
+ CGContextFillRect(cgContext, macRect);
+#endif
+ break;
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+#if SCROLLBARS_VISUAL_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
+ CGContextFillRect(cgContext, macRect);
+#endif
+ break;
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL: {
+ BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
+ if (!isOverlay || IsParentScrollbarRolledOver(aFrame)) {
+ BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL);
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ bool isSmall = (scrollbarFrame && scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
+ const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
+ RenderWithCoreUI(macRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ (isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
+ (isSmall ? @"small" : @"regular"), @"size",
+ (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
+ (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
+ [NSNumber numberWithBool:YES], @"noindicator",
+ [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil],
+ true);
+ }
+ }
+ break;
+
+ case NS_THEME_TEXTFIELD_MULTILINE: {
+ // we have to draw this by hand because there is no HITheme value for it
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+
+ CGContextFillRect(cgContext, macRect);
+
+ // #737373 for the top border, #999999 for the rest.
+ float x = macRect.origin.x, y = macRect.origin.y;
+ float w = macRect.size.width, h = macRect.size.height;
+ CGContextSetRGBFillColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
+ CGContextSetRGBFillColor(cgContext, 0.6, 0.6, 0.6, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
+
+ // draw a focus ring
+ if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+ CGContextSaveGState(cgContext);
+ NSSetFocusRingStyle(NSFocusRingOnly);
+ NSRectFill(NSRectFromCGRect(macRect));
+ CGContextRestoreGState(cgContext);
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+ }
+ break;
+
+ case NS_THEME_LISTBOX: {
+ // We have to draw this by hand because kHIThemeFrameListBox drawing
+ // is buggy on 10.5, see bug 579259.
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(cgContext, macRect);
+
+ // #8E8E8E for the top border, #BEBEBE for the rest.
+ float x = macRect.origin.x, y = macRect.origin.y;
+ float w = macRect.size.width, h = macRect.size.height;
+ CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
+ CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
+ }
+ break;
+
+ case NS_THEME_MAC_SOURCE_LIST: {
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ CGGradientRef backgroundGradient;
+ CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
+ CGFloat activeGradientColors[8] = { 0.9137, 0.9294, 0.9490, 1.0,
+ 0.8196, 0.8471, 0.8784, 1.0 };
+ CGFloat inactiveGradientColors[8] = { 0.9686, 0.9686, 0.9686, 1.0,
+ 0.9216, 0.9216, 0.9216, 1.0 };
+ CGPoint start = macRect.origin;
+ CGPoint end = CGPointMake(macRect.origin.x,
+ macRect.origin.y + macRect.size.height);
+ BOOL isActive = FrameIsInActiveWindow(aFrame);
+ backgroundGradient =
+ CGGradientCreateWithColorComponents(rgb, isActive ? activeGradientColors
+ : inactiveGradientColors, NULL, 2);
+ CGContextDrawLinearGradient(cgContext, backgroundGradient, start, end, 0);
+ CGGradientRelease(backgroundGradient);
+ CGColorSpaceRelease(rgb);
+ }
+ }
+ break;
+
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION: {
+ // If we're in XUL tree, we need to rely on the source list's clear
+ // background display item. If we cleared the background behind the
+ // selections, the source list would not pick up the right font
+ // smoothing background. So, to simplify a bit, we only support vibrancy
+ // if we're in a source list.
+ if (VibrancyManager::SystemSupportsVibrancy() && IsInSourceList(aFrame)) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ BOOL isActiveSelection =
+ aWidgetType == NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION;
+ RenderWithCoreUI(macRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:isActiveSelection], @"focus",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ @"kCUIVariantGradientSideBarSelection", @"kCUIVariantKey",
+ (FrameIsInActiveWindow(aFrame) ? @"normal" : @"inactive"), @"state",
+ @"gradient", @"widget",
+ nil]);
+ }
+ }
+ break;
+
+ case NS_THEME_TAB:
+ DrawSegment(cgContext, macRect, eventState, aFrame, tabRenderSettings);
+ break;
+
+ case NS_THEME_TABPANELS:
+ DrawTabPanel(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_RESIZER:
+ DrawResizer(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK: {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ break;
+ }
+ }
+
+ if (hidpi) {
+ // Reset the base CTM.
+ CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsIntMargin
+nsNativeThemeCocoa::DirectionAwareMargin(const nsIntMargin& aMargin,
+ nsIFrame* aFrame)
+{
+ // Assuming aMargin was originally specified for a horizontal LTR context,
+ // reinterpret the values as logical, and then map to physical coords
+ // according to aFrame's actual writing mode.
+ WritingMode wm = aFrame->GetWritingMode();
+ nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom,
+ aMargin.left).GetPhysicalMargin(wm);
+ return nsIntMargin(m.top, m.right, m.bottom, m.left);
+}
+
+static const nsIntMargin kAquaDropdownBorder(1, 22, 2, 5);
+static const nsIntMargin kAquaComboboxBorder(3, 20, 3, 4);
+static const nsIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ aResult->SizeTo(0, 0, 0, 0);
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ {
+ if (IsButtonTypeMenu(aFrame)) {
+ *aResult = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
+ } else {
+ *aResult = DirectionAwareMargin(nsIntMargin(1, 7, 3, 7), aFrame);
+ }
+ break;
+ }
+
+ case NS_THEME_TOOLBARBUTTON:
+ {
+ *aResult = DirectionAwareMargin(nsIntMargin(1, 4, 1, 4), aFrame);
+ break;
+ }
+
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ {
+ // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight
+ // assume a border width of 2px.
+ aResult->SizeTo(2, 2, 2, 2);
+ break;
+ }
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ *aResult = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
+ break;
+
+ case NS_THEME_MENULIST_TEXTFIELD:
+ *aResult = DirectionAwareMargin(kAquaComboboxBorder, aFrame);
+ break;
+
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
+
+ SInt32 textPadding = 0;
+ ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
+
+ frameOutset += textPadding;
+
+ aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
+ break;
+ }
+
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ aResult->SizeTo(1, 1, 1, 1);
+ break;
+
+ case NS_THEME_SEARCHFIELD:
+ *aResult = DirectionAwareMargin(kAquaSearchfieldBorder, aFrame);
+ break;
+
+ case NS_THEME_LISTBOX:
+ {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
+ aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
+ break;
+ }
+
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ {
+ bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL);
+ if (nsLookAndFeel::UseOverlayScrollbars()) {
+ if (!nsCocoaFeatures::OnYosemiteOrLater()) {
+ // Pre-10.10, we have to center the thumb rect in the middle of the
+ // scrollbar. Starting with 10.10, the expected rect for thumb
+ // rendering is the full width of the scrollbar.
+ if (isHorizontal) {
+ aResult->top = 2;
+ aResult->bottom = 1;
+ } else {
+ aResult->left = 2;
+ aResult->right = 1;
+ }
+ }
+ // Leave a bit of space at the start and the end on all OS X versions.
+ if (isHorizontal) {
+ aResult->left = 1;
+ aResult->right = 1;
+ } else {
+ aResult->top = 1;
+ aResult->bottom = 1;
+ }
+ }
+
+ break;
+ }
+
+ case NS_THEME_STATUSBAR:
+ aResult->SizeTo(1, 0, 0, 0);
+ break;
+ }
+
+ if (IsHiDPIContext(aFrame->PresContext())) {
+ *aResult = *aResult + *aResult; // doubled
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Return false here to indicate that CSS padding values should be used. There is
+// no reason to make a distinction between padding and border values, just specify
+// whatever values you want in GetWidgetBorder and only use this to return true
+// if you want to override CSS padding values.
+bool
+nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ // We don't want CSS padding being used for certain widgets.
+ // See bug 381639 for an example of why.
+ switch (aWidgetType) {
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ }
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType, nsRect* aOverflowRect)
+{
+ int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_SEARCHFIELD:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ case NS_THEME_TAB:
+ {
+ // We assume that the above widgets can draw a focus ring that will be less than
+ // or equal to 4 pixels thick.
+ nsIntMargin extraSize = nsIntMargin(kMaxFocusRingWidth,
+ kMaxFocusRingWidth,
+ kMaxFocusRingWidth,
+ kMaxFocusRingWidth);
+ nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
+ NSIntPixelsToAppUnits(extraSize.right, p2a),
+ NSIntPixelsToAppUnits(extraSize.bottom, p2a),
+ NSIntPixelsToAppUnits(extraSize.left, p2a));
+ aOverflowRect->Inflate(m);
+ return true;
+ }
+ case NS_THEME_PROGRESSBAR:
+ {
+ // Progress bars draw a 2 pixel white shadow under their progress indicators
+ nsMargin m(0, 0, NSIntPixelsToAppUnits(2, p2a), 0);
+ aOverflowRect->Inflate(m);
+ return true;
+ }
+ case NS_THEME_FOCUS_OUTLINE:
+ {
+ aOverflowRect->Inflate(NSIntPixelsToAppUnits(2, p2a));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static const int32_t kRegularScrollbarThumbMinSize = 26;
+static const int32_t kSmallScrollbarThumbMinSize = 26;
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ aResult->SizeTo(0,0);
+ *aIsOverridable = true;
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ {
+ aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
+ pushButtonSettings.naturalSizes[miniControlSize].height);
+ break;
+ }
+
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ {
+ aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MENUARROW:
+ {
+ aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ {
+ aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MAC_HELP_BUTTON:
+ {
+ aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_TOOLBARBUTTON:
+ {
+ aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
+ break;
+ }
+
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ {
+ SInt32 buttonHeight = 0, buttonWidth = 0;
+ if (aFrame->GetContent()->IsXULElement()) {
+ ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
+ ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
+ } else {
+ NSSize size =
+ spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)];
+ buttonWidth = size.width;
+ buttonHeight = size.height;
+ if (aWidgetType != NS_THEME_SPINNER) {
+ // the buttons are half the height of the spinner
+ buttonHeight /= 2;
+ }
+ }
+ aResult->SizeTo(buttonWidth, buttonHeight);
+ *aIsOverridable = true;
+ break;
+ }
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ {
+ SInt32 popupHeight = 0;
+ ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
+ aResult->SizeTo(0, popupHeight);
+ break;
+ }
+
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_SEARCHFIELD:
+ {
+ // at minimum, we should be tall enough for 9pt text.
+ // I'm using hardcoded values here because the appearance manager
+ // values for the frame size are incorrect.
+ aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
+ break;
+ }
+
+ case NS_THEME_WINDOW_BUTTON_BOX: {
+ NSSize size = WindowButtonsSize(aFrame);
+ aResult->SizeTo(size.width, size.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MAC_FULLSCREEN_BUTTON: {
+ if ([NativeWindowForFrame(aFrame) respondsToSelector:@selector(toggleFullScreen:)] &&
+ !nsCocoaFeatures::OnYosemiteOrLater()) {
+ // This value is hardcoded because it's needed before we can measure the
+ // position and size of the fullscreen button.
+ aResult->SizeTo(16, 17);
+ }
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_PROGRESSBAR:
+ {
+ SInt32 barHeight = 0;
+ ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
+ aResult->SizeTo(0, barHeight);
+ break;
+ }
+
+ case NS_THEME_TREETWISTY:
+ case NS_THEME_TREETWISTYOPEN:
+ {
+ SInt32 twistyHeight = 0, twistyWidth = 0;
+ ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
+ ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
+ aResult->SizeTo(twistyWidth, twistyHeight);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_TREEHEADER:
+ case NS_THEME_TREEHEADERCELL:
+ {
+ SInt32 headerHeight = 0;
+ ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
+ aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
+ break;
+ }
+
+ case NS_THEME_TAB:
+ {
+ aResult->SizeTo(0, tabHeights[miniControlSize]);
+ break;
+ }
+
+ case NS_THEME_RANGE:
+ {
+ // The Mac Appearance Manager API (the old API we're currently using)
+ // doesn't define constants to obtain a minimum size for sliders. We use
+ // the "thickness" of a slider that has default dimensions for both the
+ // minimum width and height to get something sane and so that paint
+ // invalidation works.
+ SInt32 size = 0;
+ if (IsRangeHorizontal(aFrame)) {
+ ::GetThemeMetric(kThemeMetricHSliderHeight, &size);
+ } else {
+ ::GetThemeMetric(kThemeMetricVSliderWidth, &size);
+ }
+ aResult->SizeTo(size, size);
+ *aIsOverridable = true;
+ break;
+ }
+
+ case NS_THEME_RANGE_THUMB:
+ {
+ SInt32 width = 0;
+ SInt32 height = 0;
+ ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
+ ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
+ aResult->SizeTo(width, height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_SCALE_HORIZONTAL:
+ {
+ SInt32 scaleHeight = 0;
+ ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
+ aResult->SizeTo(scaleHeight, scaleHeight);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_SCALE_VERTICAL:
+ {
+ SInt32 scaleWidth = 0;
+ ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
+ aResult->SizeTo(scaleWidth, scaleWidth);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ {
+ // Find our parent scrollbar frame in order to find out whether we're in
+ // a small or a large scrollbar.
+ nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (!scrollbarFrame)
+ return NS_ERROR_FAILURE;
+
+ bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
+ bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL);
+ int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
+ minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ {
+ *aIsOverridable = false;
+
+ if (nsLookAndFeel::UseOverlayScrollbars()) {
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (scrollbarFrame &&
+ scrollbarFrame->StyleDisplay()->mAppearance ==
+ NS_THEME_SCROLLBAR_SMALL) {
+ aResult->SizeTo(14, 14);
+ }
+ else {
+ aResult->SizeTo(16, 16);
+ }
+ break;
+ }
+
+ // yeah, i know i'm cheating a little here, but i figure that it
+ // really doesn't matter if the scrollbar is vertical or horizontal
+ // and the width metric is a really good metric for every piece
+ // of the scrollbar.
+
+ nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (!scrollbarFrame) return NS_ERROR_FAILURE;
+
+ int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
+ kThemeMetricSmallScrollBarWidth :
+ kThemeMetricScrollBarWidth;
+ SInt32 scrollbarWidth = 0;
+ ::GetThemeMetric(themeMetric, &scrollbarWidth);
+ aResult->SizeTo(scrollbarWidth, scrollbarWidth);
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ {
+ int32_t themeMetric = kThemeMetricScrollBarWidth;
+
+ if (aFrame) {
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (scrollbarFrame &&
+ scrollbarFrame->StyleDisplay()->mAppearance ==
+ NS_THEME_SCROLLBAR_SMALL) {
+ // XXX We're interested in the width of non-disappearing scrollbars
+ // to leave enough space for a dropmarker in non-native styled
+ // comboboxes (bug 869314). It isn't clear to me if comboboxes can
+ // ever have small scrollbars.
+ themeMetric = kThemeMetricSmallScrollBarWidth;
+ }
+ }
+
+ SInt32 scrollbarWidth = 0;
+ ::GetThemeMetric(themeMetric, &scrollbarWidth);
+ aResult->SizeTo(scrollbarWidth, scrollbarWidth);
+ break;
+ }
+
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ {
+ nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (!scrollbarFrame) return NS_ERROR_FAILURE;
+
+ // Since there is no NS_THEME_SCROLLBARBUTTON_UP_SMALL we need to ask the parent what appearance style it has.
+ int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
+ kThemeMetricSmallScrollBarWidth :
+ kThemeMetricScrollBarWidth;
+ SInt32 scrollbarWidth = 0;
+ ::GetThemeMetric(themeMetric, &scrollbarWidth);
+
+ // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
+ if (aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT)
+ aResult->SizeTo(scrollbarWidth+1, scrollbarWidth);
+ else
+ aResult->SizeTo(scrollbarWidth, scrollbarWidth+1);
+
+ *aIsOverridable = false;
+ break;
+ }
+ case NS_THEME_RESIZER:
+ {
+ HIThemeGrowBoxDrawInfo drawInfo;
+ drawInfo.version = 0;
+ drawInfo.state = kThemeStateActive;
+ drawInfo.kind = kHIThemeGrowBoxKindNormal;
+ drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
+ drawInfo.size = kHIThemeGrowBoxSizeNormal;
+ HIPoint pnt = { 0, 0 };
+ HIRect bounds;
+ HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
+ aResult->SizeTo(bounds.size.width, bounds.size.height);
+ *aIsOverridable = false;
+ }
+ }
+
+ if (IsHiDPIContext(aPresContext)) {
+ *aResult = *aResult * 2;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue)
+{
+ // Some widget types just never change state.
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_DIALOG:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_METERBAR:
+ case NS_THEME_METERCHUNK:
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ // XXXdwh Not sure what can really be done here. Can at least guess for
+ // specific widgets that they're highly unlikely to have certain states.
+ // For example, a toolbar doesn't care about any states.
+ if (!aAttribute) {
+ // Hover/focus/active changed. Always repaint.
+ *aShouldRepaint = true;
+ } else {
+ // Check the attribute to see if it's relevant.
+ // disabled, checked, dlgtype, default, etc.
+ *aShouldRepaint = false;
+ if (aAttribute == nsGkAtoms::disabled ||
+ aAttribute == nsGkAtoms::checked ||
+ aAttribute == nsGkAtoms::selected ||
+ aAttribute == nsGkAtoms::visuallyselected ||
+ aAttribute == nsGkAtoms::menuactive ||
+ aAttribute == nsGkAtoms::sortDirection ||
+ aAttribute == nsGkAtoms::focused ||
+ aAttribute == nsGkAtoms::_default ||
+ aAttribute == nsGkAtoms::open ||
+ aAttribute == nsGkAtoms::hover)
+ *aShouldRepaint = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::ThemeChanged()
+{
+ // This is unimplemented because we don't care if gecko changes its theme
+ // and Mac OS X doesn't have themes.
+ return NS_OK;
+}
+
+bool
+nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ // We don't have CSS set up to render non-native scrollbars on Mac OS X so we
+ // render natively even if native theme support is disabled.
+ if (aWidgetType != NS_THEME_SCROLLBAR &&
+ aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
+ return false;
+
+ // if this is a dropdown button in a combobox the answer is always no
+ if (aWidgetType == NS_THEME_MENULIST_BUTTON) {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (parentFrame && (parentFrame->GetType() == nsGkAtoms::comboboxControlFrame))
+ return false;
+ }
+
+ switch (aWidgetType) {
+ // Combobox dropdowns don't support native theming in vertical mode.
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_MENULIST_TEXT:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ MOZ_FALLTHROUGH;
+
+ case NS_THEME_LISTBOX:
+
+ case NS_THEME_DIALOG:
+ case NS_THEME_WINDOW:
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_MAC_FULLSCREEN_BUTTON:
+ case NS_THEME_TOOLTIP:
+
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_CHECKBOX_CONTAINER:
+ case NS_THEME_RADIO:
+ case NS_THEME_RADIO_CONTAINER:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ case NS_THEME_BUTTON:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_BEVEL:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_SEARCHFIELD:
+ case NS_THEME_TOOLBOX:
+ //case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_METERBAR:
+ case NS_THEME_METERCHUNK:
+ case NS_THEME_SEPARATOR:
+
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TAB:
+
+ case NS_THEME_TREETWISTY:
+ case NS_THEME_TREETWISTYOPEN:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TREEHEADER:
+ case NS_THEME_TREEHEADERCELL:
+ case NS_THEME_TREEHEADERSORTARROW:
+ case NS_THEME_TREEITEM:
+ case NS_THEME_TREELINE:
+ case NS_THEME_MAC_SOURCE_LIST:
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+
+ case NS_THEME_RANGE:
+
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
+
+ case NS_THEME_RESIZER:
+ {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame)
+ return true;
+
+ // Note that IsWidgetStyled is not called for resizers on Mac. This is
+ // because for scrollable containers, the native resizer looks better
+ // when (non-overlay) scrollbars are present even when the style is
+ // overriden, and the custom transparent resizer looks better when
+ // scrollbars are not present.
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
+ return (!nsLookAndFeel::UseOverlayScrollbars() &&
+ scrollFrame && scrollFrame->GetScrollbarVisibility());
+ }
+
+ case NS_THEME_FOCUS_OUTLINE:
+ return true;
+
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ return VibrancyManager::SystemSupportsVibrancy();
+ }
+
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::WidgetIsContainer(uint8_t aWidgetType)
+{
+ // flesh this out at some point
+ switch (aWidgetType) {
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_RADIO:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_METERBAR:
+ case NS_THEME_RANGE:
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ return false;
+ }
+ return true;
+}
+
+bool
+nsNativeThemeCocoa::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
+{
+ if (aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_MENULIST_TEXTFIELD ||
+ aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_MAC_HELP_BUTTON ||
+ aWidgetType == NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN ||
+ aWidgetType == NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED ||
+ aWidgetType == NS_THEME_RADIO ||
+ aWidgetType == NS_THEME_RANGE ||
+ aWidgetType == NS_THEME_CHECKBOX)
+ return true;
+
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker()
+{
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_DIALOG:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_SEPARATOR:
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TREELINE:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_RESIZER:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool
+nsNativeThemeCocoa::IsWindowSheet(nsIFrame* aFrame)
+{
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ id winDelegate = [win delegate];
+ nsIWidget* widget = [(WindowDelegate *)winDelegate geckoWidget];
+ if (!widget) {
+ return false;
+ }
+ return (widget->WindowType() == eWindowType_sheet);
+}
+
+bool
+nsNativeThemeCocoa::NeedToClearBackgroundBehindWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MAC_SOURCE_LIST:
+ // If we're in a XUL tree, we don't want to clear the background behind the
+ // selections below, since that would make our source list to not pick up
+ // the right font smoothing background. But since we don't call this method
+ // in nsTreeBodyFrame::BuildDisplayList, we never get here.
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ return true;
+ case NS_THEME_DIALOG:
+ return IsWindowSheet(aFrame);
+ default:
+ return false;
+ }
+}
+
+static nscolor ConvertNSColor(NSColor* aColor)
+{
+ NSColor* deviceColor = [aColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ return NS_RGBA((unsigned int)([deviceColor redComponent] * 255.0),
+ (unsigned int)([deviceColor greenComponent] * 255.0),
+ (unsigned int)([deviceColor blueComponent] * 255.0),
+ (unsigned int)([deviceColor alphaComponent] * 255.0));
+}
+
+bool
+nsNativeThemeCocoa::WidgetProvidesFontSmoothingBackgroundColor(nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nscolor* aColor)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MAC_SOURCE_LIST:
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_DIALOG:
+ {
+ if ((aWidgetType == NS_THEME_DIALOG && !IsWindowSheet(aFrame)) ||
+ ((aWidgetType == NS_THEME_MAC_SOURCE_LIST_SELECTION ||
+ aWidgetType == NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION) &&
+ !IsInSourceList(aFrame))) {
+ return false;
+ }
+ ChildView* childView = ChildViewForFrame(aFrame);
+ if (childView) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ NSColor* color = [childView vibrancyFontSmoothingBackgroundColorForThemeGeometryType:type];
+ *aColor = ConvertNSColor(color);
+ return true;
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+}
+
+nsITheme::ThemeGeometryType
+nsNativeThemeCocoa::ThemeGeometryTypeForWidget(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ return eThemeGeometryTypeTitlebar;
+ case NS_THEME_TOOLBAR:
+ return eThemeGeometryTypeToolbar;
+ case NS_THEME_TOOLBOX:
+ return eThemeGeometryTypeToolbox;
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ return eThemeGeometryTypeWindowButtons;
+ case NS_THEME_MAC_FULLSCREEN_BUTTON:
+ return eThemeGeometryTypeFullscreenButton;
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ return eThemeGeometryTypeVibrancyLight;
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ return eThemeGeometryTypeVibrancyDark;
+ case NS_THEME_TOOLTIP:
+ return eThemeGeometryTypeTooltip;
+ case NS_THEME_MENUPOPUP:
+ return eThemeGeometryTypeMenu;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM: {
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ bool isDisabled = IsDisabled(aFrame, eventState);
+ bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu;
+ }
+ case NS_THEME_DIALOG:
+ return IsWindowSheet(aFrame) ? eThemeGeometryTypeSheet : eThemeGeometryTypeUnknown;
+ case NS_THEME_MAC_SOURCE_LIST:
+ return eThemeGeometryTypeSourceList;
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection
+ : eThemeGeometryTypeUnknown;
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+ return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection
+ : eThemeGeometryTypeUnknown;
+ default:
+ return eThemeGeometryTypeUnknown;
+ }
+}
+
+nsITheme::Transparency
+nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_TOOLTIP:
+ return eTransparent;
+
+ case NS_THEME_DIALOG:
+ return IsWindowSheet(aFrame) ? eTransparent : eOpaque;
+
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR:
+ return nsLookAndFeel::UseOverlayScrollbars() ? eTransparent : eOpaque;
+
+ case NS_THEME_STATUSBAR:
+ // Knowing that scrollbars and statusbars are opaque improves
+ // performance, because we create layers for them.
+ return eOpaque;
+
+ case NS_THEME_TOOLBAR:
+ return eOpaque;
+
+ default:
+ return eUnknownTransparency;
+ }
+}
diff --git a/widget/cocoa/nsNativeThemeColors.h b/widget/cocoa/nsNativeThemeColors.h
new file mode 100644
index 000000000..b1691b516
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeColors.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNativeThemeColors_h_
+#define nsNativeThemeColors_h_
+
+#include "nsCocoaFeatures.h"
+#import <Cocoa/Cocoa.h>
+
+enum ColorName {
+ toolbarTopBorderGrey,
+ toolbarFillGrey,
+ toolbarBottomBorderGrey,
+};
+
+static const int sLionThemeColors[][2] = {
+ /* { active window, inactive window } */
+ // toolbar:
+ { 0xD0, 0xF0 }, // top separator line
+ { 0xB2, 0xE1 }, // fill color
+ { 0x59, 0x87 }, // bottom separator line
+};
+
+static const int sYosemiteThemeColors[][2] = {
+ /* { active window, inactive window } */
+ // toolbar:
+ { 0xBD, 0xDF }, // top separator line
+ { 0xD3, 0xF6 }, // fill color
+ { 0xB3, 0xD1 }, // bottom separator line
+};
+
+__attribute__((unused))
+static int NativeGreyColorAsInt(ColorName name, BOOL isMain)
+{
+ if (nsCocoaFeatures::OnYosemiteOrLater())
+ return sYosemiteThemeColors[name][isMain ? 0 : 1];
+ return sLionThemeColors[name][isMain ? 0 : 1];
+}
+
+__attribute__((unused))
+static float NativeGreyColorAsFloat(ColorName name, BOOL isMain)
+{
+ return NativeGreyColorAsInt(name, isMain) / 255.0f;
+}
+
+__attribute__((unused))
+static void DrawNativeGreyColorInRect(CGContextRef context, ColorName name,
+ CGRect rect, BOOL isMain)
+{
+ float grey = NativeGreyColorAsFloat(name, isMain);
+ CGContextSetRGBFillColor(context, grey, grey, grey, 1.0f);
+ CGContextFillRect(context, rect);
+}
+
+#endif // nsNativeThemeColors_h_
diff --git a/widget/cocoa/nsPIWidgetCocoa.idl b/widget/cocoa/nsPIWidgetCocoa.idl
new file mode 100644
index 000000000..a8fd8149c
--- /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 000000000..470f17d99
--- /dev/null
+++ b/widget/cocoa/nsPrintDialogX.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintDialog_h_
+#define nsPrintDialog_h_
+
+#include "nsIPrintDialogService.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsIPrintSettings;
+class nsIStringBundle;
+
+class nsPrintDialogServiceX : public nsIPrintDialogService
+{
+public:
+ nsPrintDialogServiceX();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init() override;
+ NS_IMETHOD Show(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aSettings,
+ nsIWebBrowserPrint *aWebBrowserPrint) override;
+ NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aSettings) override;
+
+protected:
+ virtual ~nsPrintDialogServiceX();
+};
+
+@interface PrintPanelAccessoryView : NSView
+{
+ nsIPrintSettings* mSettings;
+ nsIStringBundle* mPrintBundle;
+ NSButton* mPrintSelectionOnlyCheckbox;
+ NSButton* mShrinkToFitCheckbox;
+ NSButton* mPrintBGColorsCheckbox;
+ NSButton* mPrintBGImagesCheckbox;
+ NSButtonCell* mAsLaidOutRadio;
+ NSButtonCell* mSelectedFrameRadio;
+ NSButtonCell* mSeparateFramesRadio;
+ NSPopUpButton* mHeaderLeftList;
+ NSPopUpButton* mHeaderCenterList;
+ NSPopUpButton* mHeaderRightList;
+ NSPopUpButton* mFooterLeftList;
+ NSPopUpButton* mFooterCenterList;
+ NSPopUpButton* mFooterRightList;
+}
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings;
+
+- (void)exportSettings;
+
+@end
+
+@interface PrintPanelAccessoryController : NSViewController <NSPrintPanelAccessorizing>
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings;
+
+- (void)exportSettings;
+
+@end
+
+#endif // nsPrintDialog_h_
diff --git a/widget/cocoa/nsPrintDialogX.mm b/widget/cocoa/nsPrintDialogX.mm
new file mode 100644
index 000000000..a6d58d5bf
--- /dev/null
+++ b/widget/cocoa/nsPrintDialogX.mm
@@ -0,0 +1,682 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsPrintDialogX.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsPrintSettingsX.h"
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIWebProgressListener.h"
+#include "nsIStringBundle.h"
+#include "nsIWebBrowserPrint.h"
+#include "nsCRT.h"
+
+#import <Cocoa/Cocoa.h>
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsPrintDialogServiceX, nsIPrintDialogService)
+
+nsPrintDialogServiceX::nsPrintDialogServiceX()
+{
+}
+
+nsPrintDialogServiceX::~nsPrintDialogServiceX()
+{
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::Show(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aSettings,
+ nsIWebBrowserPrint *aWebBrowserPrint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_PRECONDITION(aSettings, "aSettings must not be null");
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aSettings));
+ if (!settingsX)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc
+ = do_GetService("@mozilla.org/gfx/printsettings-service;1");
+
+ // Set the print job title
+ char16_t** docTitles;
+ uint32_t titleCount;
+ nsresult rv = aWebBrowserPrint->EnumerateDocumentNames(&titleCount, &docTitles);
+ if (NS_SUCCEEDED(rv) && titleCount > 0) {
+ CFStringRef cfTitleString = CFStringCreateWithCharacters(NULL, reinterpret_cast<const UniChar*>(docTitles[0]),
+ NS_strlen(docTitles[0]));
+ if (cfTitleString) {
+ ::PMPrintSettingsSetJobName(settingsX->GetPMPrintSettings(), cfTitleString);
+ CFRelease(cfTitleString);
+ }
+ for (int32_t i = titleCount - 1; i >= 0; i--) {
+ free(docTitles[i]);
+ }
+ free(docTitles);
+ docTitles = NULL;
+ titleCount = 0;
+ }
+
+ // Read default print settings from prefs
+ printSettingsSvc->InitPrintSettingsFromPrefs(settingsX, true,
+ nsIPrintSettings::kInitSaveNativeData);
+ NSPrintInfo* printInfo = settingsX->GetCocoaPrintInfo();
+
+ // Put the print info into the current print operation, since that's where
+ // [panel runModal] will look for it. We create the view because otherwise
+ // we'll get unrelated warnings printed to the console.
+ NSView* tmpView = [[NSView alloc] init];
+ NSPrintOperation* printOperation = [NSPrintOperation printOperationWithView:tmpView printInfo:printInfo];
+ [NSPrintOperation setCurrentOperation:printOperation];
+
+ NSPrintPanel* panel = [NSPrintPanel printPanel];
+ [panel setOptions:NSPrintPanelShowsCopies
+ | NSPrintPanelShowsPageRange
+ | NSPrintPanelShowsPaperSize
+ | NSPrintPanelShowsOrientation
+ | NSPrintPanelShowsScaling ];
+ PrintPanelAccessoryController* viewController =
+ [[PrintPanelAccessoryController alloc] initWithSettings:aSettings];
+ [panel addAccessoryController:viewController];
+ [viewController release];
+
+ // Show the dialog.
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int button = [panel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ NSPrintInfo* copy = [[[NSPrintOperation currentOperation] printInfo] copy];
+ if (!copy) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ [NSPrintOperation setCurrentOperation:nil];
+ [tmpView release];
+
+ if (button != NSFileHandlingPanelOKButton)
+ return NS_ERROR_ABORT;
+
+ settingsX->SetCocoaPrintInfo(copy);
+ settingsX->InitUnwriteableMargin();
+
+ // Save settings unless saving is pref'd off
+ if (Preferences::GetBool("print.save_print_settings", false)) {
+ printSettingsSvc->SavePrintSettingsToPrefs(settingsX, true,
+ nsIPrintSettings::kInitSaveNativeData);
+ }
+
+ // Get coordinate space resolution for converting paper size units to inches
+ NSWindow *win = [[NSApplication sharedApplication] mainWindow];
+ if (win) {
+ NSDictionary *devDesc = [win deviceDescription];
+ if (devDesc) {
+ NSSize res = [[devDesc objectForKey: NSDeviceResolution] sizeValue];
+ float scale = [win backingScaleFactor];
+ if (scale > 0) {
+ settingsX->SetInchesScale(res.width / scale, res.height / scale);
+ }
+ }
+ }
+
+ // Export settings.
+ [viewController exportSettings];
+
+ // If "ignore scaling" is checked, overwrite scaling factor with 1.
+ bool isShrinkToFitChecked;
+ settingsX->GetShrinkToFit(&isShrinkToFitChecked);
+ if (isShrinkToFitChecked) {
+ NSMutableDictionary* dict = [copy dictionary];
+ if (dict) {
+ [dict setObject: [NSNumber numberWithFloat: 1]
+ forKey: NSPrintScalingFactor];
+ }
+ // Set the scaling factor to 100% in the NSPrintInfo
+ // object so that it will not affect the paper size
+ // retrieved from the PMPageFormat routines.
+ [copy setScalingFactor:1.0];
+ } else {
+ aSettings->SetScaling([copy scalingFactor]);
+ }
+
+ // Set the adjusted paper size now that we've updated
+ // the scaling factor.
+ settingsX->InitAdjustedPaperSize();
+
+ [copy release];
+
+ int16_t pageRange;
+ aSettings->GetPrintRange(&pageRange);
+ if (pageRange != nsIPrintSettings::kRangeSelection) {
+ PMPrintSettings nativePrintSettings = settingsX->GetPMPrintSettings();
+ UInt32 firstPage, lastPage;
+ OSStatus status = ::PMGetFirstPage(nativePrintSettings, &firstPage);
+ if (status == noErr) {
+ status = ::PMGetLastPage(nativePrintSettings, &lastPage);
+ if (status == noErr && lastPage != UINT32_MAX) {
+ aSettings->SetPrintRange(nsIPrintSettings::kRangeSpecifiedPageRange);
+ aSettings->SetStartPageRange(firstPage);
+ aSettings->SetEndPageRange(lastPage);
+ }
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::ShowPageSetup(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aNSSettings)
+{
+ NS_PRECONDITION(aParent, "aParent must not be null");
+ NS_PRECONDITION(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aNSSettings));
+ if (!settingsX)
+ return NS_ERROR_FAILURE;
+
+ NSPrintInfo* printInfo = settingsX->GetCocoaPrintInfo();
+ NSPageLayout *pageLayout = [NSPageLayout pageLayout];
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int button = [pageLayout runModalWithPrintInfo:printInfo];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ return button == NSFileHandlingPanelOKButton ? NS_OK : NS_ERROR_ABORT;
+}
+
+// Accessory view
+
+@interface PrintPanelAccessoryView (Private)
+
+- (NSString*)localizedString:(const char*)aKey;
+
+- (int16_t)chosenFrameSetting;
+
+- (const char*)headerFooterStringForList:(NSPopUpButton*)aList;
+
+- (void)exportHeaderFooterSettings;
+
+- (void)initBundle;
+
+- (NSTextField*)label:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment;
+
+- (void)addLabel:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment;
+
+- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect;
+
+- (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect;
+
+- (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect;
+
+- (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect
+ selectedItem:(const char16_t*)aCurrentString;
+
+- (void)addOptionsSection;
+
+- (void)addAppearanceSection;
+
+- (void)addFramesSection;
+
+- (void)addHeaderFooterSection;
+
+- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox;
+
+- (NSString*)framesSummaryValue;
+
+- (NSString*)headerSummaryValue;
+
+- (NSString*)footerSummaryValue;
+
+@end
+
+static const char sHeaderFooterTags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
+
+@implementation PrintPanelAccessoryView
+
+// Public methods
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings
+{
+ [super initWithFrame:NSMakeRect(0, 0, 540, 270)];
+
+ mSettings = aSettings;
+ [self initBundle];
+ [self addOptionsSection];
+ [self addAppearanceSection];
+ [self addFramesSection];
+ [self addHeaderFooterSection];
+
+ return self;
+}
+
+- (void)exportSettings
+{
+ mSettings->SetPrintRange([mPrintSelectionOnlyCheckbox state] == NSOnState ?
+ (int16_t)nsIPrintSettings::kRangeSelection :
+ (int16_t)nsIPrintSettings::kRangeAllPages);
+ mSettings->SetShrinkToFit([mShrinkToFitCheckbox state] == NSOnState);
+ mSettings->SetPrintBGColors([mPrintBGColorsCheckbox state] == NSOnState);
+ mSettings->SetPrintBGImages([mPrintBGImagesCheckbox state] == NSOnState);
+ mSettings->SetPrintFrameType([self chosenFrameSetting]);
+
+ [self exportHeaderFooterSettings];
+}
+
+- (void)dealloc
+{
+ NS_IF_RELEASE(mPrintBundle);
+ [super dealloc];
+}
+
+// Localization
+
+- (void)initBundle
+{
+ nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", &mPrintBundle);
+}
+
+- (NSString*)localizedString:(const char*)aKey
+{
+ if (!mPrintBundle)
+ return @"";
+
+ nsXPIDLString intlString;
+ mPrintBundle->GetStringFromName(NS_ConvertUTF8toUTF16(aKey).get(), getter_Copies(intlString));
+ NSMutableString* s = [NSMutableString stringWithUTF8String:NS_ConvertUTF16toUTF8(intlString).get()];
+
+ // Remove all underscores (they're used in the GTK dialog for accesskeys).
+ [s replaceOccurrencesOfString:@"_" withString:@"" options:0 range:NSMakeRange(0, [s length])];
+ return s;
+}
+
+// Widget helpers
+
+- (NSTextField*)label:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment
+{
+ NSTextField* label = [[[NSTextField alloc] initWithFrame:aRect] autorelease];
+ [label setStringValue:[self localizedString:aLabel]];
+ [label setEditable:NO];
+ [label setSelectable:NO];
+ [label setBezeled:NO];
+ [label setBordered:NO];
+ [label setDrawsBackground:NO];
+ [label setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+ [label setAlignment:aAlignment];
+ return label;
+}
+
+- (void)addLabel:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment
+{
+ NSTextField* label = [self label:aLabel withFrame:aRect alignment:aAlignment];
+ [self addSubview:label];
+}
+
+- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect
+{
+ [self addLabel:aLabel withFrame:aRect alignment:NSRightTextAlignment];
+}
+
+- (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect
+{
+ [self addLabel:aLabel withFrame:aRect alignment:NSCenterTextAlignment];
+}
+
+- (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect
+{
+ aRect.origin.y += 4.0f;
+ NSButton* checkbox = [[[NSButton alloc] initWithFrame:aRect] autorelease];
+ [checkbox setButtonType:NSSwitchButton];
+ [checkbox setTitle:[self localizedString:aLabel]];
+ [checkbox setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+ [checkbox sizeToFit];
+ return checkbox;
+}
+
+- (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect
+ selectedItem:(const char16_t*)aCurrentString
+{
+ NSPopUpButton* list = [[[NSPopUpButton alloc] initWithFrame:aRect pullsDown:NO] autorelease];
+ [list setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [[list cell] setControlSize:NSSmallControlSize];
+ NSArray* items =
+ [NSArray arrayWithObjects:[self localizedString:"headerFooterBlank"],
+ [self localizedString:"headerFooterTitle"],
+ [self localizedString:"headerFooterURL"],
+ [self localizedString:"headerFooterDate"],
+ [self localizedString:"headerFooterPage"],
+ [self localizedString:"headerFooterPageTotal"],
+ nil];
+ [list addItemsWithTitles:items];
+
+ NS_ConvertUTF16toUTF8 currentStringUTF8(aCurrentString);
+ for (unsigned int i = 0; i < ArrayLength(sHeaderFooterTags); i++) {
+ if (!strcmp(currentStringUTF8.get(), sHeaderFooterTags[i])) {
+ [list selectItemAtIndex:i];
+ break;
+ }
+ }
+
+ return list;
+}
+
+// Build sections
+
+- (void)addOptionsSection
+{
+ // Title
+ [self addLabel:"optionsTitleMac" withFrame:NSMakeRect(0, 240, 151, 22)];
+
+ // "Print Selection Only"
+ mPrintSelectionOnlyCheckbox = [self checkboxWithLabel:"selectionOnly"
+ andFrame:NSMakeRect(156, 240, 0, 0)];
+
+ bool canPrintSelection;
+ mSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB,
+ &canPrintSelection);
+ [mPrintSelectionOnlyCheckbox setEnabled:canPrintSelection];
+
+ int16_t printRange;
+ mSettings->GetPrintRange(&printRange);
+ if (printRange == nsIPrintSettings::kRangeSelection) {
+ [mPrintSelectionOnlyCheckbox setState:NSOnState];
+ }
+
+ [self addSubview:mPrintSelectionOnlyCheckbox];
+
+ // "Shrink To Fit"
+ mShrinkToFitCheckbox = [self checkboxWithLabel:"shrinkToFit"
+ andFrame:NSMakeRect(156, 218, 0, 0)];
+
+ bool shrinkToFit;
+ mSettings->GetShrinkToFit(&shrinkToFit);
+ [mShrinkToFitCheckbox setState:(shrinkToFit ? NSOnState : NSOffState)];
+
+ [self addSubview:mShrinkToFitCheckbox];
+}
+
+- (void)addAppearanceSection
+{
+ // Title
+ [self addLabel:"appearanceTitleMac" withFrame:NSMakeRect(0, 188, 151, 22)];
+
+ // "Print Background Colors"
+ mPrintBGColorsCheckbox = [self checkboxWithLabel:"printBGColors"
+ andFrame:NSMakeRect(156, 188, 0, 0)];
+
+ bool geckoBool;
+ mSettings->GetPrintBGColors(&geckoBool);
+ [mPrintBGColorsCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
+
+ [self addSubview:mPrintBGColorsCheckbox];
+
+ // "Print Background Images"
+ mPrintBGImagesCheckbox = [self checkboxWithLabel:"printBGImages"
+ andFrame:NSMakeRect(156, 166, 0, 0)];
+
+ mSettings->GetPrintBGImages(&geckoBool);
+ [mPrintBGImagesCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
+
+ [self addSubview:mPrintBGImagesCheckbox];
+}
+
+- (void)addFramesSection
+{
+ // Title
+ [self addLabel:"framesTitleMac" withFrame:NSMakeRect(0, 124, 151, 22)];
+
+ // Radio matrix
+ NSButtonCell *radio = [[NSButtonCell alloc] init];
+ [radio setButtonType:NSRadioButton];
+ [radio setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+ NSMatrix *matrix = [[NSMatrix alloc] initWithFrame:NSMakeRect(156, 81, 400, 66)
+ mode:NSRadioModeMatrix
+ prototype:(NSCell*)radio
+ numberOfRows:3
+ numberOfColumns:1];
+ [radio release];
+ [matrix setCellSize:NSMakeSize(400, 21)];
+ [self addSubview:matrix];
+ [matrix release];
+ NSArray *cellArray = [matrix cells];
+ mAsLaidOutRadio = [cellArray objectAtIndex:0];
+ mSelectedFrameRadio = [cellArray objectAtIndex:1];
+ mSeparateFramesRadio = [cellArray objectAtIndex:2];
+ [mAsLaidOutRadio setTitle:[self localizedString:"asLaidOut"]];
+ [mSelectedFrameRadio setTitle:[self localizedString:"selectedFrame"]];
+ [mSeparateFramesRadio setTitle:[self localizedString:"separateFrames"]];
+
+ // Radio enabled state
+ int16_t frameUIFlag;
+ mSettings->GetHowToEnableFrameUI(&frameUIFlag);
+ if (frameUIFlag == nsIPrintSettings::kFrameEnableNone) {
+ [mAsLaidOutRadio setEnabled:NO];
+ [mSelectedFrameRadio setEnabled:NO];
+ [mSeparateFramesRadio setEnabled:NO];
+ } else if (frameUIFlag == nsIPrintSettings::kFrameEnableAsIsAndEach) {
+ [mSelectedFrameRadio setEnabled:NO];
+ }
+
+ // Radio values
+ int16_t printFrameType;
+ mSettings->GetPrintFrameType(&printFrameType);
+ switch (printFrameType) {
+ case nsIPrintSettings::kFramesAsIs:
+ [mAsLaidOutRadio setState:NSOnState];
+ break;
+ case nsIPrintSettings::kSelectedFrame:
+ [mSelectedFrameRadio setState:NSOnState];
+ break;
+ case nsIPrintSettings::kEachFrameSep:
+ [mSeparateFramesRadio setState:NSOnState];
+ break;
+ }
+}
+
+- (void)addHeaderFooterSection
+{
+ // Labels
+ [self addLabel:"pageHeadersTitleMac" withFrame:NSMakeRect(0, 44, 151, 22)];
+ [self addLabel:"pageFootersTitleMac" withFrame:NSMakeRect(0, 0, 151, 22)];
+ [self addCenteredLabel:"left" withFrame:NSMakeRect(156, 22, 100, 22)];
+ [self addCenteredLabel:"center" withFrame:NSMakeRect(256, 22, 100, 22)];
+ [self addCenteredLabel:"right" withFrame:NSMakeRect(356, 22, 100, 22)];
+
+ // Lists
+ nsXPIDLString sel;
+
+ mSettings->GetHeaderStrLeft(getter_Copies(sel));
+ mHeaderLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderLeftList];
+
+ mSettings->GetHeaderStrCenter(getter_Copies(sel));
+ mHeaderCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderCenterList];
+
+ mSettings->GetHeaderStrRight(getter_Copies(sel));
+ mHeaderRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderRightList];
+
+ mSettings->GetFooterStrLeft(getter_Copies(sel));
+ mFooterLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterLeftList];
+
+ mSettings->GetFooterStrCenter(getter_Copies(sel));
+ mFooterCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterCenterList];
+
+ mSettings->GetFooterStrRight(getter_Copies(sel));
+ mFooterRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterRightList];
+}
+
+// Export settings
+
+- (int16_t)chosenFrameSetting
+{
+ if ([mAsLaidOutRadio state] == NSOnState)
+ return nsIPrintSettings::kFramesAsIs;
+ if ([mSelectedFrameRadio state] == NSOnState)
+ return nsIPrintSettings::kSelectedFrame;
+ if ([mSeparateFramesRadio state] == NSOnState)
+ return nsIPrintSettings::kEachFrameSep;
+ return nsIPrintSettings::kNoFrames;
+}
+
+- (const char*)headerFooterStringForList:(NSPopUpButton*)aList
+{
+ NSInteger index = [aList indexOfSelectedItem];
+ NS_ASSERTION(index < NSInteger(ArrayLength(sHeaderFooterTags)), "Index of dropdown is higher than expected!");
+ return sHeaderFooterTags[index];
+}
+
+- (void)exportHeaderFooterSettings
+{
+ const char* headerFooterStr;
+ headerFooterStr = [self headerFooterStringForList:mHeaderLeftList];
+ mSettings->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mHeaderCenterList];
+ mSettings->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mHeaderRightList];
+ mSettings->SetHeaderStrRight(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mFooterLeftList];
+ mSettings->SetFooterStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mFooterCenterList];
+ mSettings->SetFooterStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mFooterRightList];
+ mSettings->SetFooterStrRight(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+}
+
+// Summary
+
+- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox
+{
+ if (![aCheckbox isEnabled])
+ return [self localizedString:"summaryNAValue"];
+
+ return [aCheckbox state] == NSOnState ?
+ [self localizedString:"summaryOnValue"] :
+ [self localizedString:"summaryOffValue"];
+}
+
+- (NSString*)framesSummaryValue
+{
+ switch([self chosenFrameSetting]) {
+ case nsIPrintSettings::kFramesAsIs:
+ return [self localizedString:"asLaidOut"];
+ case nsIPrintSettings::kSelectedFrame:
+ return [self localizedString:"selectedFrame"];
+ case nsIPrintSettings::kEachFrameSep:
+ return [self localizedString:"separateFrames"];
+ }
+ return [self localizedString:"summaryNAValue"];
+}
+
+- (NSString*)headerSummaryValue
+{
+ return [[mHeaderLeftList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [[mHeaderCenterList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [mHeaderRightList titleOfSelectedItem]]]]];
+}
+
+- (NSString*)footerSummaryValue
+{
+ return [[mFooterLeftList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [[mFooterCenterList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [mFooterRightList titleOfSelectedItem]]]]];
+}
+
+- (NSArray*)localizedSummaryItems
+{
+ return [NSArray arrayWithObjects:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryFramesTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self framesSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summarySelectionOnlyTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mPrintSelectionOnlyCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryShrinkToFitTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mShrinkToFitCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryPrintBGColorsTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mPrintBGColorsCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryPrintBGImagesTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mPrintBGImagesCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryHeaderTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self headerSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryFooterTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self footerSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ nil];
+}
+
+@end
+
+// Accessory controller
+
+@implementation PrintPanelAccessoryController
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings
+{
+ [super initWithNibName:nil bundle:nil];
+
+ NSView* accView = [[PrintPanelAccessoryView alloc] initWithSettings:aSettings];
+ [self setView:accView];
+ [accView release];
+ return self;
+}
+
+- (void)exportSettings
+{
+ return [(PrintPanelAccessoryView*)[self view] exportSettings];
+}
+
+- (NSArray *)localizedSummaryItems
+{
+ return [(PrintPanelAccessoryView*)[self view] localizedSummaryItems];
+}
+
+@end
diff --git a/widget/cocoa/nsPrintOptionsX.h b/widget/cocoa/nsPrintOptionsX.h
new file mode 100644
index 000000000..e34e75059
--- /dev/null
+++ b/widget/cocoa/nsPrintOptionsX.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintOptionsX_h_
+#define nsPrintOptionsX_h_
+
+#include "nsPrintOptionsImpl.h"
+
+namespace mozilla
+{
+namespace embedding
+{
+ class PrintData;
+} // namespace embedding
+} // namespace mozilla
+
+class nsPrintOptionsX : public nsPrintOptions
+{
+public:
+ nsPrintOptionsX();
+ virtual ~nsPrintOptionsX();
+
+ /*
+ * These serialize and deserialize methods are not symmetrical in that
+ * printSettingsX != deserialize(serialize(printSettingsX)). This is because
+ * the native print settings stored in the nsPrintSettingsX's NSPrintInfo
+ * object are not fully serialized. Only the values needed for successful
+ * printing are.
+ */
+ NS_IMETHODIMP SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ mozilla::embedding::PrintData* data);
+ NS_IMETHODIMP DeserializeToPrintSettings(const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings);
+
+protected:
+ nsresult _CreatePrintSettings(nsIPrintSettings **_retval);
+ nsresult ReadPrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags);
+ nsresult WritePrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags);
+};
+
+#endif // nsPrintOptionsX_h_
diff --git a/widget/cocoa/nsPrintOptionsX.mm b/widget/cocoa/nsPrintOptionsX.mm
new file mode 100644
index 000000000..d9aa17b42
--- /dev/null
+++ b/widget/cocoa/nsPrintOptionsX.mm
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "nsIServiceManager.h"
+#include "nsPrintOptionsX.h"
+#include "nsPrintSettingsX.h"
+
+// The constants for paper orientation were renamed in 10.9. __MAC_10_9 is
+// defined on OS X 10.9 and later. Although 10.8 and earlier are not supported
+// at this time, this allows for building on those older OS versions. The
+// values are consistent across OS versions so the rename does not affect
+// runtime, just compilation.
+#ifdef __MAC_10_9
+#define NS_PAPER_ORIENTATION_PORTRAIT (NSPaperOrientationPortrait)
+#define NS_PAPER_ORIENTATION_LANDSCAPE (NSPaperOrientationLandscape)
+#else
+#define NS_PAPER_ORIENTATION_PORTRAIT (NSPortraitOrientation)
+#define NS_PAPER_ORIENTATION_LANDSCAPE (NSLandscapeOrientation)
+#endif
+
+using namespace mozilla::embedding;
+
+nsPrintOptionsX::nsPrintOptionsX()
+{
+}
+
+nsPrintOptionsX::~nsPrintOptionsX()
+{
+}
+
+NS_IMETHODIMP
+nsPrintOptionsX::SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ PrintData* data)
+{
+ nsresult rv = nsPrintOptions::SerializeToPrintData(aSettings, aWBP, data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aWBP) {
+ // When serializing an nsIWebBrowserPrint, we need to pass up the first
+ // document name. We could pass up the entire collection of document
+ // names, but the OS X printing prompt code only really cares about
+ // the first one, so we just send the first to save IPC traffic.
+ char16_t** docTitles;
+ uint32_t titleCount;
+ rv = aWBP->EnumerateDocumentNames(&titleCount, &docTitles);
+ if (NS_SUCCEEDED(rv) && titleCount > 0) {
+ data->printJobName().Assign(docTitles[0]);
+ }
+
+ for (int32_t i = titleCount - 1; i >= 0; i--) {
+ free(docTitles[i]);
+ }
+ free(docTitles);
+ docTitles = nullptr;
+ }
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aSettings));
+ if (NS_WARN_IF(!settingsX)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSPrintInfo* printInfo = settingsX->GetCocoaPrintInfo();
+ if (NS_WARN_IF(!printInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ double adjustedWidth, adjustedHeight;
+ settingsX->GetAdjustedPaperSize(&adjustedWidth, &adjustedHeight);
+ data->adjustedPaperWidth() = adjustedWidth;
+ data->adjustedPaperHeight() = adjustedHeight;
+
+ NSDictionary* dict = [printInfo dictionary];
+ if (NS_WARN_IF(!dict)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSString* printerName = [dict objectForKey: NSPrintPrinterName];
+ if (printerName) {
+ nsCocoaUtils::GetStringForNSString(printerName, data->printerName());
+ }
+
+ NSString* faxNumber = [dict objectForKey: NSPrintFaxNumber];
+ if (faxNumber) {
+ nsCocoaUtils::GetStringForNSString(faxNumber, data->faxNumber());
+ }
+
+ NSURL* printToFileURL = [dict objectForKey: NSPrintJobSavingURL];
+ if (printToFileURL) {
+ nsCocoaUtils::GetStringForNSString([printToFileURL absoluteString],
+ data->toFileName());
+ }
+
+ NSDate* printTime = [dict objectForKey: NSPrintTime];
+ if (printTime) {
+ NSTimeInterval timestamp = [printTime timeIntervalSinceReferenceDate];
+ data->printTime() = timestamp;
+ }
+
+ NSString* disposition = [dict objectForKey: NSPrintJobDisposition];
+ if (disposition) {
+ nsCocoaUtils::GetStringForNSString(disposition, data->disposition());
+ }
+
+ NSString* paperName = [dict objectForKey: NSPrintPaperName];
+ if (paperName) {
+ nsCocoaUtils::GetStringForNSString(paperName, data->paperName());
+ }
+
+ float scalingFactor = [[dict objectForKey: NSPrintScalingFactor] floatValue];
+ data->scalingFactor() = scalingFactor;
+
+ int32_t orientation;
+ if ([printInfo orientation] == NS_PAPER_ORIENTATION_PORTRAIT) {
+ orientation = nsIPrintSettings::kPortraitOrientation;
+ } else {
+ orientation = nsIPrintSettings::kLandscapeOrientation;
+ }
+ data->orientation() = orientation;
+
+ NSSize paperSize = [printInfo paperSize];
+ float widthScale, heightScale;
+ settingsX->GetInchesScale(&widthScale, &heightScale);
+ if (orientation == nsIPrintSettings::kLandscapeOrientation) {
+ // switch widths and heights
+ data->widthScale() = heightScale;
+ data->heightScale() = widthScale;
+ data->paperWidth() = paperSize.height / heightScale;
+ data->paperHeight() = paperSize.width / widthScale;
+ } else {
+ data->widthScale() = widthScale;
+ data->heightScale() = heightScale;
+ data->paperWidth() = paperSize.width / widthScale;
+ data->paperHeight() = paperSize.height / heightScale;
+ }
+
+ data->numCopies() = [[dict objectForKey: NSPrintCopies] intValue];
+ data->printAllPages() = [[dict objectForKey: NSPrintAllPages] boolValue];
+ data->startPageRange() = [[dict objectForKey: NSPrintFirstPage] intValue];
+ data->endPageRange() = [[dict objectForKey: NSPrintLastPage] intValue];
+ data->mustCollate() = [[dict objectForKey: NSPrintMustCollate] boolValue];
+ data->printReversed() = [[dict objectForKey: NSPrintReversePageOrder] boolValue];
+ data->pagesAcross() = [[dict objectForKey: NSPrintPagesAcross] unsignedShortValue];
+ data->pagesDown() = [[dict objectForKey: NSPrintPagesDown] unsignedShortValue];
+ data->detailedErrorReporting() = [[dict objectForKey: NSPrintDetailedErrorReporting] boolValue];
+ data->addHeaderAndFooter() = [[dict objectForKey: NSPrintHeaderAndFooter] boolValue];
+ data->fileNameExtensionHidden() =
+ [[dict objectForKey: NSPrintJobSavingFileNameExtensionHidden] boolValue];
+
+ bool printSelectionOnly = [[dict objectForKey: NSPrintSelectionOnly] boolValue];
+ aSettings->SetPrintOptions(nsIPrintSettings::kEnableSelectionRB,
+ printSelectionOnly);
+ aSettings->GetPrintOptionsBits(&data->optionFlags());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintOptionsX::DeserializeToPrintSettings(const PrintData& data,
+ nsIPrintSettings* settings)
+{
+ nsresult rv = nsPrintOptions::DeserializeToPrintSettings(data, settings);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(settings));
+ if (NS_WARN_IF(!settingsX)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSPrintInfo* sharedPrintInfo = [NSPrintInfo sharedPrintInfo];
+ if (NS_WARN_IF(!sharedPrintInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSDictionary* sharedDict = [sharedPrintInfo dictionary];
+ if (NS_WARN_IF(!sharedDict)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // We need to create a new NSMutableDictionary to pass to NSPrintInfo with
+ // the values that we got from the other process.
+ NSMutableDictionary* newPrintInfoDict =
+ [NSMutableDictionary dictionaryWithDictionary:sharedDict];
+ if (NS_WARN_IF(!newPrintInfoDict)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NSString* printerName = nsCocoaUtils::ToNSString(data.printerName());
+ if (printerName) {
+ NSPrinter* printer = [NSPrinter printerWithName: printerName];
+ if (printer) {
+ [newPrintInfoDict setObject: printer forKey: NSPrintPrinter];
+ [newPrintInfoDict setObject: printerName forKey: NSPrintPrinterName];
+ }
+ }
+
+ [newPrintInfoDict setObject: [NSNumber numberWithInt: data.numCopies()]
+ forKey: NSPrintCopies];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.printAllPages()]
+ forKey: NSPrintAllPages];
+ [newPrintInfoDict setObject: [NSNumber numberWithInt: data.startPageRange()]
+ forKey: NSPrintFirstPage];
+ [newPrintInfoDict setObject: [NSNumber numberWithInt: data.endPageRange()]
+ forKey: NSPrintLastPage];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.mustCollate()]
+ forKey: NSPrintMustCollate];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.printReversed()]
+ forKey: NSPrintReversePageOrder];
+
+ [newPrintInfoDict setObject: nsCocoaUtils::ToNSString(data.disposition())
+ forKey: NSPrintJobDisposition];
+
+ [newPrintInfoDict setObject: nsCocoaUtils::ToNSString(data.paperName())
+ forKey: NSPrintPaperName];
+
+ [newPrintInfoDict setObject: [NSNumber numberWithFloat: data.scalingFactor()]
+ forKey: NSPrintScalingFactor];
+
+ CGFloat width = data.paperWidth() * data.widthScale();
+ CGFloat height = data.paperHeight() * data.heightScale();
+ [newPrintInfoDict setObject: [NSValue valueWithSize:NSMakeSize(width,height)]
+ forKey: NSPrintPaperSize];
+
+ int paperOrientation;
+ if (data.orientation() == nsIPrintSettings::kPortraitOrientation) {
+ paperOrientation = NS_PAPER_ORIENTATION_PORTRAIT;
+ settings->SetOrientation(nsIPrintSettings::kPortraitOrientation);
+ } else {
+ paperOrientation = NS_PAPER_ORIENTATION_LANDSCAPE;
+ settings->SetOrientation(nsIPrintSettings::kLandscapeOrientation);
+ }
+ [newPrintInfoDict setObject: [NSNumber numberWithInt:paperOrientation]
+ forKey: NSPrintOrientation];
+
+ [newPrintInfoDict setObject: [NSNumber numberWithShort: data.pagesAcross()]
+ forKey: NSPrintPagesAcross];
+ [newPrintInfoDict setObject: [NSNumber numberWithShort: data.pagesDown()]
+ forKey: NSPrintPagesDown];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.detailedErrorReporting()]
+ forKey: NSPrintDetailedErrorReporting];
+ [newPrintInfoDict setObject: nsCocoaUtils::ToNSString(data.faxNumber())
+ forKey: NSPrintFaxNumber];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.addHeaderAndFooter()]
+ forKey: NSPrintHeaderAndFooter];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.fileNameExtensionHidden()]
+ forKey: NSPrintJobSavingFileNameExtensionHidden];
+
+ // At this point, the base class should have properly deserialized the print
+ // options bitfield for nsIPrintSettings, so that it holds the correct value
+ // for kEnableSelectionRB, which we use to set NSPrintSelectionOnly.
+
+ bool printSelectionOnly = false;
+ rv = settings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &printSelectionOnly);
+ if (NS_SUCCEEDED(rv)) {
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: printSelectionOnly]
+ forKey: NSPrintSelectionOnly];
+ } else {
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: NO]
+ forKey: NSPrintSelectionOnly];
+ }
+
+ NSURL* jobSavingURL =
+ [NSURL URLWithString: nsCocoaUtils::ToNSString(data.toFileName())];
+ if (jobSavingURL) {
+ [newPrintInfoDict setObject: jobSavingURL forKey: NSPrintJobSavingURL];
+ }
+
+ NSTimeInterval timestamp = data.printTime();
+ NSDate* printTime = [NSDate dateWithTimeIntervalSinceReferenceDate: timestamp];
+ if (printTime) {
+ [newPrintInfoDict setObject: printTime forKey: NSPrintTime];
+ }
+
+ // Next, we create a new NSPrintInfo with the values in our dictionary.
+ NSPrintInfo* newPrintInfo =
+ [[NSPrintInfo alloc] initWithDictionary: newPrintInfoDict];
+ if (NS_WARN_IF(!newPrintInfo)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // And now swap in the new NSPrintInfo we've just populated.
+ settingsX->SetCocoaPrintInfo(newPrintInfo);
+ [newPrintInfo release];
+
+ settingsX->SetAdjustedPaperSize(data.adjustedPaperWidth(),
+ data.adjustedPaperHeight());
+
+ return NS_OK;
+}
+
+nsresult
+nsPrintOptionsX::ReadPrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags)
+{
+ nsresult rv;
+
+ rv = nsPrintOptions::ReadPrefs(aPS, aPrinterName, aFlags);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrintOptions::ReadPrefs() failed");
+
+ RefPtr<nsPrintSettingsX> printSettingsX(do_QueryObject(aPS));
+ if (!printSettingsX)
+ return NS_ERROR_NO_INTERFACE;
+ rv = printSettingsX->ReadPageFormatFromPrefs();
+
+ return NS_OK;
+}
+
+nsresult nsPrintOptionsX::_CreatePrintSettings(nsIPrintSettings **_retval)
+{
+ nsresult rv;
+ *_retval = nullptr;
+
+ nsPrintSettingsX* printSettings = new nsPrintSettingsX; // does not initially ref count
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = printSettings);
+
+ rv = printSettings->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(*_retval);
+ return rv;
+ }
+
+ InitPrintSettingsFromPrefs(*_retval, false, nsIPrintSettings::kInitSaveAll);
+ return rv;
+}
+
+nsresult
+nsPrintOptionsX::WritePrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags)
+{
+ nsresult rv;
+
+ rv = nsPrintOptions::WritePrefs(aPS, aPrinterName, aFlags);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrintOptions::WritePrefs() failed");
+
+ RefPtr<nsPrintSettingsX> printSettingsX(do_QueryObject(aPS));
+ if (!printSettingsX)
+ return NS_ERROR_NO_INTERFACE;
+ rv = printSettingsX->WritePageFormatToPrefs();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrintSettingsX::WritePageFormatToPrefs() failed");
+
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsPrintSettingsX.h b/widget/cocoa/nsPrintSettingsX.h
new file mode 100644
index 000000000..1d755b250
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsX.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintSettingsX_h_
+#define nsPrintSettingsX_h_
+
+#include "nsPrintSettingsImpl.h"
+#import <Cocoa/Cocoa.h>
+
+#define NS_PRINTSETTINGSX_IID \
+{ 0x0DF2FDBD, 0x906D, 0x4726, \
+ { 0x9E, 0x4D, 0xCF, 0xE0, 0x87, 0x8D, 0x70, 0x7C } }
+
+class nsPrintSettingsX : public nsPrintSettings
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PRINTSETTINGSX_IID)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsPrintSettingsX();
+ nsresult Init();
+ NSPrintInfo* GetCocoaPrintInfo() { return mPrintInfo; }
+ void SetCocoaPrintInfo(NSPrintInfo* aPrintInfo);
+ virtual nsresult ReadPageFormatFromPrefs();
+ virtual nsresult WritePageFormatToPrefs();
+ virtual nsresult GetEffectivePageSize(double *aWidth,
+ double *aHeight) override;
+
+ // In addition to setting the paper width and height, these
+ // overrides set the adjusted width and height returned from
+ // GetEffectivePageSize. This is needed when a paper size is
+ // set manually without using a print dialog a la reftest-print.
+ virtual nsresult SetPaperWidth(double aPaperWidth) override;
+ virtual nsresult SetPaperHeight(double aPaperWidth) override;
+
+ PMPrintSettings GetPMPrintSettings();
+ PMPrintSession GetPMPrintSession();
+ PMPageFormat GetPMPageFormat();
+ void SetPMPageFormat(PMPageFormat aPageFormat);
+
+ // Re-initialize mUnwriteableMargin with values from mPageFormat.
+ // Should be called whenever mPageFormat is initialized or overwritten.
+ nsresult InitUnwriteableMargin();
+
+ // Re-initialize mAdjustedPaper{Width,Height} with values from mPageFormat.
+ // Should be called whenever mPageFormat is initialized or overwritten.
+ nsresult InitAdjustedPaperSize();
+
+ void SetInchesScale(float aWidthScale, float aHeightScale);
+ void GetInchesScale(float *aWidthScale, float *aHeightScale);
+
+ void SetAdjustedPaperSize(double aWidth, double aHeight);
+ void GetAdjustedPaperSize(double *aWidth, double *aHeight);
+
+protected:
+ virtual ~nsPrintSettingsX();
+
+ nsPrintSettingsX(const nsPrintSettingsX& src);
+ nsPrintSettingsX& operator=(const nsPrintSettingsX& rhs);
+
+ nsresult _Clone(nsIPrintSettings **_retval) override;
+ nsresult _Assign(nsIPrintSettings *aPS) override;
+
+ // The out param has a ref count of 1 on return so caller needs to PMRelase() when done.
+ OSStatus CreateDefaultPageFormat(PMPrintSession aSession, PMPageFormat& outFormat);
+ OSStatus CreateDefaultPrintSettings(PMPrintSession aSession, PMPrintSettings& outSettings);
+
+ NSPrintInfo* mPrintInfo;
+
+ // Scaling factors used to convert the NSPrintInfo
+ // paper size units to inches
+ float mWidthScale;
+ float mHeightScale;
+ double mAdjustedPaperWidth;
+ double mAdjustedPaperHeight;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintSettingsX, NS_PRINTSETTINGSX_IID)
+
+#endif // nsPrintSettingsX_h_
diff --git a/widget/cocoa/nsPrintSettingsX.mm b/widget/cocoa/nsPrintSettingsX.mm
new file mode 100644
index 000000000..73a8e78d2
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsX.mm
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrintSettingsX.h"
+#include "nsObjCExceptions.h"
+
+#include "plbase64.h"
+#include "plstr.h"
+
+#include "nsCocoaUtils.h"
+
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+#define MAC_OS_X_PAGE_SETUP_PREFNAME "print.macosx.pagesetup-2"
+#define COCOA_PAPER_UNITS_PER_INCH 72.0
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsX, nsPrintSettings, nsPrintSettingsX)
+
+nsPrintSettingsX::nsPrintSettingsX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mPrintInfo = [[NSPrintInfo sharedPrintInfo] copy];
+ mWidthScale = COCOA_PAPER_UNITS_PER_INCH;
+ mHeightScale = COCOA_PAPER_UNITS_PER_INCH;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsPrintSettingsX::nsPrintSettingsX(const nsPrintSettingsX& src)
+{
+ *this = src;
+}
+
+nsPrintSettingsX::~nsPrintSettingsX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mPrintInfo release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsPrintSettingsX& nsPrintSettingsX::operator=(const nsPrintSettingsX& rhs)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (this == &rhs) {
+ return *this;
+ }
+
+ nsPrintSettings::operator=(rhs);
+
+ [mPrintInfo release];
+ mPrintInfo = [rhs.mPrintInfo copy];
+
+ return *this;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(*this);
+}
+
+nsresult nsPrintSettingsX::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ InitUnwriteableMargin();
+ InitAdjustedPaperSize();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Should be called whenever the page format changes.
+NS_IMETHODIMP nsPrintSettingsX::InitUnwriteableMargin()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMPaper paper;
+ PMPaperMargins paperMargin;
+ PMPageFormat pageFormat = GetPMPageFormat();
+ ::PMGetPageFormatPaper(pageFormat, &paper);
+ ::PMPaperGetMargins(paper, &paperMargin);
+ mUnwriteableMargin.top = NS_POINTS_TO_INT_TWIPS(paperMargin.top);
+ mUnwriteableMargin.left = NS_POINTS_TO_INT_TWIPS(paperMargin.left);
+ mUnwriteableMargin.bottom = NS_POINTS_TO_INT_TWIPS(paperMargin.bottom);
+ mUnwriteableMargin.right = NS_POINTS_TO_INT_TWIPS(paperMargin.right);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::InitAdjustedPaperSize()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMPageFormat pageFormat = GetPMPageFormat();
+
+ PMRect paperRect;
+ ::PMGetAdjustedPaperRect(pageFormat, &paperRect);
+
+ mAdjustedPaperWidth = paperRect.right - paperRect.left;
+ mAdjustedPaperHeight = paperRect.bottom - paperRect.top;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsPrintSettingsX::SetCocoaPrintInfo(NSPrintInfo* aPrintInfo)
+{
+ if (mPrintInfo != aPrintInfo) {
+ [mPrintInfo release];
+ mPrintInfo = [aPrintInfo retain];
+ }
+}
+
+NS_IMETHODIMP nsPrintSettingsX::ReadPageFormatFromPrefs()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsAutoCString encodedData;
+ nsresult rv =
+ Preferences::GetCString(MAC_OS_X_PAGE_SETUP_PREFNAME, &encodedData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // decode the base64
+ char* decodedData = PL_Base64Decode(encodedData.get(), encodedData.Length(), nullptr);
+ NSData* data = [NSData dataWithBytes:decodedData length:strlen(decodedData)];
+ if (!data)
+ return NS_ERROR_FAILURE;
+
+ PMPageFormat newPageFormat;
+ OSStatus status = ::PMPageFormatCreateWithDataRepresentation((CFDataRef)data, &newPageFormat);
+ if (status == noErr) {
+ SetPMPageFormat(newPageFormat);
+ }
+ InitUnwriteableMargin();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::WritePageFormatToPrefs()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMPageFormat pageFormat = GetPMPageFormat();
+ if (pageFormat == kPMNoPageFormat)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NSData* data = nil;
+ OSStatus err = ::PMPageFormatCreateDataRepresentation(pageFormat, (CFDataRef*)&data, kPMDataFormatXMLDefault);
+ if (err != noErr)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString encodedData;
+ encodedData.Adopt(PL_Base64Encode((char*)[data bytes], [data length], nullptr));
+ if (!encodedData.get())
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return Preferences::SetCString(MAC_OS_X_PAGE_SETUP_PREFNAME, encodedData);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsPrintSettingsX::_Clone(nsIPrintSettings **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsPrintSettingsX *newSettings = new nsPrintSettingsX(*this);
+ if (!newSettings)
+ return NS_ERROR_FAILURE;
+ *_retval = newSettings;
+ NS_ADDREF(*_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::_Assign(nsIPrintSettings *aPS)
+{
+ nsPrintSettingsX *printSettingsX = static_cast<nsPrintSettingsX*>(aPS);
+ if (!printSettingsX)
+ return NS_ERROR_UNEXPECTED;
+ *this = *printSettingsX;
+ return NS_OK;
+}
+
+PMPrintSettings
+nsPrintSettingsX::GetPMPrintSettings()
+{
+ return static_cast<PMPrintSettings>([mPrintInfo PMPrintSettings]);
+}
+
+PMPrintSession
+nsPrintSettingsX::GetPMPrintSession()
+{
+ return static_cast<PMPrintSession>([mPrintInfo PMPrintSession]);
+}
+
+PMPageFormat
+nsPrintSettingsX::GetPMPageFormat()
+{
+ return static_cast<PMPageFormat>([mPrintInfo PMPageFormat]);
+}
+
+void
+nsPrintSettingsX::SetPMPageFormat(PMPageFormat aPageFormat)
+{
+ PMPageFormat oldPageFormat = GetPMPageFormat();
+ ::PMCopyPageFormat(aPageFormat, oldPageFormat);
+ [mPrintInfo updateFromPMPageFormat];
+}
+
+void
+nsPrintSettingsX::SetInchesScale(float aWidthScale, float aHeightScale)
+{
+ if (aWidthScale > 0 && aHeightScale > 0) {
+ mWidthScale = aWidthScale;
+ mHeightScale = aHeightScale;
+ }
+}
+
+void
+nsPrintSettingsX::GetInchesScale(float *aWidthScale, float *aHeightScale)
+{
+ *aWidthScale = mWidthScale;
+ *aHeightScale = mHeightScale;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::SetPaperWidth(double aPaperWidth)
+{
+ mPaperWidth = aPaperWidth;
+ mAdjustedPaperWidth = aPaperWidth * mWidthScale;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::SetPaperHeight(double aPaperHeight)
+{
+ mPaperHeight = aPaperHeight;
+ mAdjustedPaperHeight = aPaperHeight * mHeightScale;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsX::GetEffectivePageSize(double *aWidth, double *aHeight)
+{
+ *aWidth = NS_INCHES_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
+ *aHeight = NS_INCHES_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
+ return NS_OK;
+}
+
+void nsPrintSettingsX::SetAdjustedPaperSize(double aWidth, double aHeight)
+{
+ mAdjustedPaperWidth = aWidth;
+ mAdjustedPaperHeight = aHeight;
+}
+
+void nsPrintSettingsX::GetAdjustedPaperSize(double *aWidth, double *aHeight)
+{
+ *aWidth = mAdjustedPaperWidth;
+ *aHeight = mAdjustedPaperHeight;
+}
diff --git a/widget/cocoa/nsSandboxViolationSink.h b/widget/cocoa/nsSandboxViolationSink.h
new file mode 100644
index 000000000..35b5d89af
--- /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 000000000..057217334
--- /dev/null
+++ b/widget/cocoa/nsSandboxViolationSink.mm
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSandboxViolationSink.h"
+
+#include <unistd.h>
+#include <time.h>
+#include <asl.h>
+#include <dispatch/dispatch.h>
+#include <notify.h>
+#include "nsCocoaDebugUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+
+int nsSandboxViolationSink::mNotifyToken = 0;
+uint64_t nsSandboxViolationSink::mLastMsgReceived = 0;
+
+void
+nsSandboxViolationSink::Start()
+{
+ if (mNotifyToken) {
+ return;
+ }
+ notify_register_dispatch(SANDBOX_VIOLATION_NOTIFICATION_NAME,
+ &mNotifyToken,
+ dispatch_queue_create(SANDBOX_VIOLATION_QUEUE_NAME,
+ DISPATCH_QUEUE_SERIAL),
+ ^(int token) { ViolationHandler(); });
+}
+
+void
+nsSandboxViolationSink::Stop()
+{
+ if (!mNotifyToken) {
+ return;
+ }
+ notify_cancel(mNotifyToken);
+ mNotifyToken = 0;
+}
+
+// We need to query syslogd to find out what violations occurred, and whether
+// they were "ours". We can use the Apple System Log facility to do this.
+// Besides calling notify_post("com.apple.sandbox.violation.*"), Apple's
+// sandboxd also reports all sandbox violations (sent to it by the Sandbox
+// kernel extension) to syslogd, which stores them and makes them viewable
+// in the system console. This is the database we query.
+
+// ViolationHandler() is always called on its own secondary thread. This
+// makes it unlikely it will interfere with other browser activity.
+
+void
+nsSandboxViolationSink::ViolationHandler()
+{
+ aslmsg query = asl_new(ASL_TYPE_QUERY);
+
+ asl_set_query(query, ASL_KEY_FACILITY, "com.apple.sandbox",
+ ASL_QUERY_OP_EQUAL);
+
+ // Only get reports that were generated very recently.
+ char query_time[30] = {0};
+ SprintfLiteral(query_time, "%li", time(NULL) - 2);
+ asl_set_query(query, ASL_KEY_TIME, query_time,
+ ASL_QUERY_OP_NUMERIC | ASL_QUERY_OP_GREATER_EQUAL);
+
+ // This code is easier to test if we don't just track "our" violations,
+ // which are (normally) few and far between. For example (for the time
+ // being at least) four appleeventsd sandbox violations happen every time
+ // we start the browser in e10s mode. But it makes sense to default to
+ // only tracking "our" violations.
+ if (mozilla::Preferences::GetBool(
+ "security.sandbox.mac.track.violations.oursonly", true)) {
+ // This makes each of our processes log its own violations. It might
+ // be better to make the chrome process log all the other processes'
+ // violations.
+ char query_pid[20] = {0};
+ SprintfLiteral(query_pid, "%u", getpid());
+ asl_set_query(query, ASL_KEY_REF_PID, query_pid, ASL_QUERY_OP_EQUAL);
+ }
+
+ aslresponse response = asl_search(nullptr, query);
+
+ // Each time ViolationHandler() is called we grab as many messages as are
+ // available. Otherwise we might not get them all.
+ if (response) {
+ while (true) {
+ aslmsg hit = nullptr;
+ aslmsg found = nullptr;
+ const char* id_str;
+
+ while ((hit = aslresponse_next(response))) {
+ // Record the message id to avoid logging the same violation more
+ // than once.
+ id_str = asl_get(hit, ASL_KEY_MSG_ID);
+ uint64_t id_val = atoll(id_str);
+ if (id_val <= mLastMsgReceived) {
+ continue;
+ }
+ mLastMsgReceived = id_val;
+ found = hit;
+ break;
+ }
+ if (!found) {
+ break;
+ }
+
+ const char* pid_str = asl_get(found, ASL_KEY_REF_PID);
+ const char* message_str = asl_get(found, ASL_KEY_MSG);
+ nsCocoaDebugUtils::DebugLog("nsSandboxViolationSink::ViolationHandler(): id %s, pid %s, message %s",
+ id_str, pid_str, message_str);
+ }
+ aslresponse_free(response);
+ }
+}
diff --git a/widget/cocoa/nsScreenCocoa.h b/widget/cocoa/nsScreenCocoa.h
new file mode 100644
index 000000000..268d5beb0
--- /dev/null
+++ b/widget/cocoa/nsScreenCocoa.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsScreenCocoa_h_
+#define nsScreenCocoa_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsBaseScreen.h"
+
+class nsScreenCocoa : public nsBaseScreen
+{
+public:
+ explicit nsScreenCocoa (NSScreen *screen);
+ ~nsScreenCocoa ();
+
+ NS_IMETHOD GetId(uint32_t* outId);
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor);
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor)
+ {
+ return GetContentsScaleFactor(aScaleFactor);
+ }
+
+ NSScreen *CocoaScreen() { return mScreen; }
+
+private:
+ CGFloat BackingScaleFactor();
+
+ NSScreen *mScreen;
+ uint32_t mId;
+};
+
+#endif // nsScreenCocoa_h_
diff --git a/widget/cocoa/nsScreenCocoa.mm b/widget/cocoa/nsScreenCocoa.mm
new file mode 100644
index 000000000..08905bf0a
--- /dev/null
+++ b/widget/cocoa/nsScreenCocoa.mm
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsScreenCocoa.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+
+static uint32_t sScreenId = 0;
+
+nsScreenCocoa::nsScreenCocoa (NSScreen *screen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mScreen = [screen retain];
+ mId = ++sScreenId;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsScreenCocoa::~nsScreenCocoa ()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mScreen release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetId(uint32_t *outId)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *outId = mId;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen frame];
+
+ mozilla::LayoutDeviceIntRect r =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetAvailRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen visibleFrame];
+
+ mozilla::LayoutDeviceIntRect r =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen frame];
+
+ mozilla::DesktopIntRect r = nsCocoaUtils::CocoaRectToGeckoRect(frame);
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetAvailRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen visibleFrame];
+
+ mozilla::DesktopIntRect r = nsCocoaUtils::CocoaRectToGeckoRect(frame);
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetPixelDepth(int32_t *aPixelDepth)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindowDepth depth = [mScreen depth];
+ int bpp = NSBitsPerPixelFromDepth(depth);
+
+ *aPixelDepth = bpp;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetColorDepth(int32_t *aColorDepth)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindowDepth depth = [mScreen depth];
+ int bpp = NSBitsPerPixelFromDepth (depth);
+
+ *aColorDepth = bpp;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetContentsScaleFactor(double *aContentsScaleFactor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *aContentsScaleFactor = (double) BackingScaleFactor();
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+CGFloat
+nsScreenCocoa::BackingScaleFactor()
+{
+ return nsCocoaUtils::GetBackingScaleFactor(mScreen);
+}
diff --git a/widget/cocoa/nsScreenManagerCocoa.h b/widget/cocoa/nsScreenManagerCocoa.h
new file mode 100644
index 000000000..61a059d97
--- /dev/null
+++ b/widget/cocoa/nsScreenManagerCocoa.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsScreenManagerCocoa_h_
+#define nsScreenManagerCocoa_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+#include "nsIScreenManager.h"
+#include "nsScreenCocoa.h"
+
+class nsScreenManagerCocoa : public nsIScreenManager
+{
+public:
+ nsScreenManagerCocoa();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+protected:
+ virtual ~nsScreenManagerCocoa();
+
+private:
+
+ nsScreenCocoa *ScreenForCocoaScreen(NSScreen *screen);
+ nsTArray< RefPtr<nsScreenCocoa> > mScreenList;
+};
+
+#endif // nsScreenManagerCocoa_h_
diff --git a/widget/cocoa/nsScreenManagerCocoa.mm b/widget/cocoa/nsScreenManagerCocoa.mm
new file mode 100644
index 000000000..9a0cbb9cc
--- /dev/null
+++ b/widget/cocoa/nsScreenManagerCocoa.mm
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsScreenManagerCocoa.h"
+#include "nsObjCExceptions.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsScreenManagerCocoa, nsIScreenManager)
+
+nsScreenManagerCocoa::nsScreenManagerCocoa()
+{
+}
+
+nsScreenManagerCocoa::~nsScreenManagerCocoa()
+{
+}
+
+nsScreenCocoa*
+nsScreenManagerCocoa::ScreenForCocoaScreen(NSScreen *screen)
+{
+ for (uint32_t i = 0; i < mScreenList.Length(); ++i) {
+ nsScreenCocoa* sc = mScreenList[i];
+ if (sc->CocoaScreen() == screen) {
+ // doesn't addref
+ return sc;
+ }
+ }
+
+ // didn't find it; create and insert
+ RefPtr<nsScreenCocoa> sc = new nsScreenCocoa(screen);
+ mScreenList.AppendElement(sc);
+ return sc.get();
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::ScreenForId (uint32_t aId, nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
+
+ *outScreen = nullptr;
+
+ for (uint32_t i = 0; i < mScreenList.Length(); ++i) {
+ nsScreenCocoa* sc = mScreenList[i];
+ uint32_t id;
+ nsresult rv = sc->GetId(&id);
+
+ if (NS_SUCCEEDED(rv) && id == aId) {
+ *outScreen = sc;
+ NS_ADDREF(*outScreen);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::ScreenForRect (int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight,
+ nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+ NSRect inRect =
+ nsCocoaUtils::GeckoRectToCocoaRect(DesktopIntRect(aX, aY,
+ aWidth, aHeight));
+ NSScreen *screenWindowIsOn = [NSScreen mainScreen];
+ float greatestArea = 0;
+
+ while (NSScreen *screen = [screenEnum nextObject]) {
+ NSDictionary *desc = [screen deviceDescription];
+ if ([desc objectForKey:NSDeviceIsScreen] == nil)
+ continue;
+
+ NSRect r = NSIntersectionRect([screen frame], inRect);
+ float area = r.size.width * r.size.height;
+ if (area > greatestArea) {
+ greatestArea = area;
+ screenWindowIsOn = screen;
+ }
+ }
+
+ *outScreen = ScreenForCocoaScreen(screenWindowIsOn);
+ NS_ADDREF(*outScreen);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::GetPrimaryScreen (nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // the mainScreen is the screen with the "key window" (focus, I assume?)
+ NSScreen *sc = [[NSScreen screens] objectAtIndex:0];
+
+ *outScreen = ScreenForCocoaScreen(sc);
+ NS_ADDREF(*outScreen);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::GetNumberOfScreens (uint32_t *aNumberOfScreens)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSArray *ss = [NSScreen screens];
+
+ *aNumberOfScreens = [ss count];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::GetSystemDefaultScale(float *aDefaultScale)
+{
+ *aDefaultScale = 1.0f;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::ScreenForNativeWidget (void *nativeWidget, nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindow *window = static_cast<NSWindow*>(nativeWidget);
+ if (window) {
+ nsIScreen *screen = ScreenForCocoaScreen([window screen]);
+ *outScreen = screen;
+ NS_ADDREF(*outScreen);
+ return NS_OK;
+ }
+
+ *outScreen = nullptr;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsSound.h b/widget/cocoa/nsSound.h
new file mode 100644
index 000000000..0e0293ae2
--- /dev/null
+++ b/widget/cocoa/nsSound.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSound_h_
+#define nsSound_h_
+
+#include "nsISound.h"
+#include "nsIStreamLoader.h"
+
+class nsSound : public nsISound,
+ public nsIStreamLoaderObserver
+{
+public:
+ nsSound();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+protected:
+ virtual ~nsSound();
+};
+
+#endif // nsSound_h_
diff --git a/widget/cocoa/nsSound.mm b/widget/cocoa/nsSound.mm
new file mode 100644
index 000000000..04c6b4d76
--- /dev/null
+++ b/widget/cocoa/nsSound.mm
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSound.h"
+#include "nsContentUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsIURL.h"
+#include "nsString.h"
+
+#import <Cocoa/Cocoa.h>
+
+NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
+
+nsSound::nsSound()
+{
+}
+
+nsSound::~nsSound()
+{
+}
+
+NS_IMETHODIMP
+nsSound::Beep()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSBeep();
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
+ nsISupports *context,
+ nsresult aStatus,
+ uint32_t dataLen,
+ const uint8_t *data)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSData *value = [NSData dataWithBytes:data length:dataLen];
+
+ NSSound *sound = [[NSSound alloc] initWithData:value];
+
+ [sound play];
+
+ [sound autorelease];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::Play(nsIURL *aURL)
+{
+ nsCOMPtr<nsIURI> uri(do_QueryInterface(aURL));
+ nsCOMPtr<nsIStreamLoader> loader;
+ return NS_NewStreamLoader(getter_AddRefs(loader),
+ uri,
+ this, // aObserver
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+}
+
+NS_IMETHODIMP
+nsSound::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSound::PlaySystemSound(const nsAString &aSoundAlias)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_IsMozAliasSound(aSoundAlias)) {
+ NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead");
+ // Mac doesn't have system sound settings for each user actions.
+ return NS_OK;
+ }
+
+ NSString *name = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aSoundAlias.BeginReading())
+ length:aSoundAlias.Length()];
+ NSSound *sound = [NSSound soundNamed:name];
+ if (sound) {
+ [sound stop];
+ [sound play];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::PlayEventSound(uint32_t aEventId)
+{
+ // Mac doesn't have system sound settings for each user actions.
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsStandaloneNativeMenu.h b/widget/cocoa/nsStandaloneNativeMenu.h
new file mode 100644
index 000000000..e03742b1e
--- /dev/null
+++ b/widget/cocoa/nsStandaloneNativeMenu.h
@@ -0,0 +1,40 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStandaloneNativeMenu_h_
+#define nsStandaloneNativeMenu_h_
+
+#include "nsMenuGroupOwnerX.h"
+#include "nsMenuX.h"
+#include "nsIStandaloneNativeMenu.h"
+
+class nsStandaloneNativeMenu : public nsMenuGroupOwnerX, public nsIStandaloneNativeMenu
+{
+public:
+ nsStandaloneNativeMenu();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISTANDALONENATIVEMENU
+
+ // nsMenuObjectX
+ nsMenuObjectTypeX MenuObjectType() override { return eStandaloneNativeMenuObjectType; }
+ void * NativeData() override { return mMenu != nullptr ? mMenu->NativeData() : nullptr; }
+ virtual void IconUpdated() override;
+
+ nsMenuX * GetMenuXObject() { return mMenu; }
+
+ // If this menu is the menu of a system status bar item (NSStatusItem),
+ // let the menu know about the status item so that it can propagate
+ // any icon changes to the status item.
+ void SetContainerStatusBarItem(NSStatusItem* aItem);
+
+protected:
+ virtual ~nsStandaloneNativeMenu();
+
+ nsMenuX * mMenu;
+ NSStatusItem* mContainerStatusBarItem;
+};
+
+#endif
diff --git a/widget/cocoa/nsStandaloneNativeMenu.mm b/widget/cocoa/nsStandaloneNativeMenu.mm
new file mode 100644
index 000000000..98a5fd8f6
--- /dev/null
+++ b/widget/cocoa/nsStandaloneNativeMenu.mm
@@ -0,0 +1,213 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsStandaloneNativeMenu.h"
+#include "nsMenuUtilsX.h"
+#include "nsIDOMElement.h"
+#include "nsIMutationObserver.h"
+#include "nsGkAtoms.h"
+#include "nsObjCExceptions.h"
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsStandaloneNativeMenu, nsMenuGroupOwnerX,
+ nsIMutationObserver, nsIStandaloneNativeMenu)
+
+nsStandaloneNativeMenu::nsStandaloneNativeMenu()
+: mMenu(nullptr)
+, mContainerStatusBarItem(nil)
+{
+}
+
+nsStandaloneNativeMenu::~nsStandaloneNativeMenu()
+{
+ if (mMenu) delete mMenu;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::Init(nsIDOMElement * aDOMElement)
+{
+ NS_ASSERTION(mMenu == nullptr, "nsNativeMenu::Init - mMenu not null!");
+
+ nsresult rv;
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!content->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menupopup))
+ return NS_ERROR_FAILURE;
+
+ rv = nsMenuGroupOwnerX::Create(content);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mMenu = new nsMenuX();
+ rv = mMenu->Create(this, this, content);
+ if (NS_FAILED(rv)) {
+ delete mMenu;
+ mMenu = nullptr;
+ return rv;
+ }
+
+ mMenu->SetupIcon();
+
+ return NS_OK;
+}
+
+static void
+UpdateMenu(nsMenuX * aMenu)
+{
+ aMenu->MenuOpened();
+ aMenu->MenuClosed();
+
+ uint32_t itemCount = aMenu->GetItemCount();
+ for (uint32_t i = 0; i < itemCount; i++) {
+ nsMenuObjectX * menuObject = aMenu->GetItemAt(i);
+ if (menuObject->MenuObjectType() == eSubmenuObjectType) {
+ UpdateMenu(static_cast<nsMenuX*>(menuObject));
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::MenuWillOpen(bool * aResult)
+{
+ NS_ASSERTION(mMenu != nullptr, "nsStandaloneNativeMenu::OnOpen - mMenu is null!");
+
+ // Force an update on the mMenu by faking an open/close on all of
+ // its submenus.
+ UpdateMenu(mMenu);
+
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::GetNativeMenu(void ** aVoidPointer)
+{
+ if (mMenu) {
+ *aVoidPointer = mMenu->NativeData();
+ [[(NSObject *)(*aVoidPointer) retain] autorelease];
+ return NS_OK;
+ } else {
+ *aVoidPointer = nullptr;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+}
+
+static NSMenuItem *
+NativeMenuItemWithLocation(NSMenu * currentSubmenu, NSString * locationString)
+{
+ NSArray * indexes = [locationString componentsSeparatedByString:@"|"];
+ NSUInteger indexCount = [indexes count];
+ if (indexCount == 0)
+ return nil;
+
+ for (NSUInteger i = 0; i < indexCount; i++) {
+ NSInteger targetIndex = [[indexes objectAtIndex:i] integerValue];
+ NSInteger itemCount = [currentSubmenu numberOfItems];
+ if (targetIndex < itemCount) {
+ NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
+
+ // If this is the last index, just return the menu item.
+ if (i == (indexCount - 1))
+ return menuItem;
+
+ // If this is not the last index, find the submenu and keep going.
+ if ([menuItem hasSubmenu])
+ currentSubmenu = [menuItem submenu];
+ else
+ return nil;
+ }
+ }
+
+ return nil;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::ActivateNativeMenuItemAt(const nsAString& indexString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mMenu)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NSString * locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSMenu * menu = static_cast<NSMenu *> (mMenu->NativeData());
+ NSMenuItem * item = NativeMenuItemWithLocation(menu, locationString);
+
+ // We can't perform an action on an item with a submenu, that will raise
+ // an obj-c exception.
+ if (item && ![item hasSubmenu]) {
+ NSMenu * parent = [item menu];
+ if (parent) {
+ // NSLog(@"Performing action for native menu item titled: %@\n",
+ // [[currentSubmenu itemAtIndex:targetIndex] title]);
+ [parent performActionForItemAtIndex:[parent indexOfItem:item]];
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+ if (!mMenu)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0)
+ return NS_OK;
+
+ nsMenuX* currentMenu = mMenu;
+
+ // now find the correct submenu
+ for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
+ int targetIndex = [[indexes objectAtIndex:i] intValue];
+ int visible = 0;
+ uint32_t length = currentMenu->GetItemCount();
+ for (unsigned int j = 0; j < length; j++) {
+ nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
+ if (!targetMenu)
+ return NS_OK;
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
+ visible++;
+ if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
+ currentMenu = static_cast<nsMenuX*>(targetMenu);
+ // fake open/close to cause lazy update to happen
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+ break;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsStandaloneNativeMenu::IconUpdated()
+{
+ if (mContainerStatusBarItem) {
+ [mContainerStatusBarItem setImage:[mMenu->NativeMenuItem() image]];
+ }
+}
+
+void
+nsStandaloneNativeMenu::SetContainerStatusBarItem(NSStatusItem* aItem)
+{
+ mContainerStatusBarItem = aItem;
+ IconUpdated();
+}
diff --git a/widget/cocoa/nsSystemStatusBarCocoa.h b/widget/cocoa/nsSystemStatusBarCocoa.h
new file mode 100644
index 000000000..51aa4df00
--- /dev/null
+++ b/widget/cocoa/nsSystemStatusBarCocoa.h
@@ -0,0 +1,40 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSystemStatusBarCocoa_h_
+#define nsSystemStatusBarCocoa_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsISystemStatusBar.h"
+#include "nsClassHashtable.h"
+
+class nsStandaloneNativeMenu;
+@class NSStatusItem;
+
+class nsSystemStatusBarCocoa : public nsISystemStatusBar
+{
+public:
+ nsSystemStatusBarCocoa() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISYSTEMSTATUSBAR
+
+protected:
+ virtual ~nsSystemStatusBarCocoa() {}
+
+ struct StatusItem
+ {
+ explicit StatusItem(nsStandaloneNativeMenu* aMenu);
+ ~StatusItem();
+
+ private:
+ RefPtr<nsStandaloneNativeMenu> mMenu;
+ NSStatusItem* mStatusItem;
+ };
+
+ nsClassHashtable<nsISupportsHashKey, StatusItem> mItems;
+};
+
+#endif // nsSystemStatusBarCocoa_h_
diff --git a/widget/cocoa/nsSystemStatusBarCocoa.mm b/widget/cocoa/nsSystemStatusBarCocoa.mm
new file mode 100644
index 000000000..522da7145
--- /dev/null
+++ b/widget/cocoa/nsSystemStatusBarCocoa.mm
@@ -0,0 +1,74 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsSystemStatusBarCocoa.h"
+#include "nsStandaloneNativeMenu.h"
+#include "nsObjCExceptions.h"
+#include "nsIDOMElement.h"
+
+NS_IMPL_ISUPPORTS(nsSystemStatusBarCocoa, nsISystemStatusBar)
+
+NS_IMETHODIMP
+nsSystemStatusBarCocoa::AddItem(nsIDOMElement* aDOMElement)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ RefPtr<nsStandaloneNativeMenu> menu = new nsStandaloneNativeMenu();
+ nsresult rv = menu->Init(aDOMElement);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISupports> keyPtr = aDOMElement;
+ mItems.Put(keyPtr, new StatusItem(menu));
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSystemStatusBarCocoa::RemoveItem(nsIDOMElement* aDOMElement)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mItems.Remove(aDOMElement);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsSystemStatusBarCocoa::StatusItem::StatusItem(nsStandaloneNativeMenu* aMenu)
+ : mMenu(aMenu)
+{
+ MOZ_COUNT_CTOR(nsSystemStatusBarCocoa::StatusItem);
+
+ NSMenu* nativeMenu = nil;
+ mMenu->GetNativeMenu(reinterpret_cast<void**>(&nativeMenu));
+
+ mStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
+ [mStatusItem setMenu:nativeMenu];
+ [mStatusItem setHighlightMode:YES];
+
+ // We want the status item to get its image from menu item that mMenu was
+ // initialized with. Icon loads are asynchronous, so we need to let the menu
+ // know about the item so that it can update its icon as soon as it has
+ // loaded.
+ mMenu->SetContainerStatusBarItem(mStatusItem);
+}
+
+nsSystemStatusBarCocoa::StatusItem::~StatusItem()
+{
+ mMenu->SetContainerStatusBarItem(nil);
+ [[NSStatusBar systemStatusBar] removeStatusItem:mStatusItem];
+ [mStatusItem release];
+ mStatusItem = nil;
+
+ MOZ_COUNT_DTOR(nsSystemStatusBarCocoa::StatusItem);
+}
diff --git a/widget/cocoa/nsToolkit.h b/widget/cocoa/nsToolkit.h
new file mode 100644
index 000000000..1631a8ac2
--- /dev/null
+++ b/widget/cocoa/nsToolkit.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsToolkit_h_
+#define nsToolkit_h_
+
+#include "nscore.h"
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#import <objc/Object.h>
+#import <IOKit/IOKitLib.h>
+
+class nsToolkit
+{
+public:
+ nsToolkit();
+ virtual ~nsToolkit();
+
+ static nsToolkit* GetToolkit();
+
+ static void Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+ }
+
+ static void PostSleepWakeNotification(const char* aNotification);
+
+ static nsresult SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
+ bool classMethods = false);
+
+ void RegisterForAllProcessMouseEvents();
+ void UnregisterAllProcessMouseEventHandlers();
+
+protected:
+
+ nsresult RegisterForSleepWakeNotifications();
+ void RemoveSleepWakeNotifications();
+
+protected:
+
+ static nsToolkit* gToolkit;
+
+ CFRunLoopSourceRef mSleepWakeNotificationRLS;
+ io_object_t mPowerNotifier;
+
+ CFMachPortRef mEventTapPort;
+ CFRunLoopSourceRef mEventTapRLS;
+};
+
+#endif // nsToolkit_h_
diff --git a/widget/cocoa/nsToolkit.mm b/widget/cocoa/nsToolkit.mm
new file mode 100644
index 000000000..4d0222d5d
--- /dev/null
+++ b/widget/cocoa/nsToolkit.mm
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsToolkit.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <mach/mach_port.h>
+#include <mach/mach_interface.h>
+#include <mach/mach_init.h>
+
+extern "C" {
+#include <mach-o/getsect.h>
+}
+#include <unistd.h>
+#include <dlfcn.h>
+
+#import <Cocoa/Cocoa.h>
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <IOKit/IOMessage.h>
+
+#include "nsCocoaUtils.h"
+#include "nsObjCExceptions.h"
+
+#include "nsGkAtoms.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsBaseWidget.h"
+
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+static io_connect_t gRootPort = MACH_PORT_NULL;
+
+nsToolkit* nsToolkit::gToolkit = nullptr;
+
+nsToolkit::nsToolkit()
+: mSleepWakeNotificationRLS(nullptr)
+, mEventTapPort(nullptr)
+, mEventTapRLS(nullptr)
+{
+ MOZ_COUNT_CTOR(nsToolkit);
+ RegisterForSleepWakeNotifications();
+}
+
+nsToolkit::~nsToolkit()
+{
+ MOZ_COUNT_DTOR(nsToolkit);
+ RemoveSleepWakeNotifications();
+ UnregisterAllProcessMouseEventHandlers();
+}
+
+void
+nsToolkit::PostSleepWakeNotification(const char* aNotification)
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(nullptr, aNotification, nullptr);
+}
+
+// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html
+static void ToolkitSleepWakeCallback(void *refCon, io_service_t service, natural_t messageType, void * messageArgument)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ switch (messageType)
+ {
+ case kIOMessageSystemWillSleep:
+ // System is going to sleep now.
+ nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC);
+ ::IOAllowPowerChange(gRootPort, (long)messageArgument);
+ break;
+
+ case kIOMessageCanSystemSleep:
+ // In this case, the computer has been idle for several minutes
+ // and will sleep soon so you must either allow or cancel
+ // this notification. Important: if you don’t respond, there will
+ // be a 30-second timeout before the computer sleeps.
+ // In Mozilla's case, we always allow sleep.
+ ::IOAllowPowerChange(gRootPort,(long)messageArgument);
+ break;
+
+ case kIOMessageSystemHasPoweredOn:
+ // Handle wakeup.
+ nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ break;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult
+nsToolkit::RegisterForSleepWakeNotifications()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ IONotificationPortRef notifyPortRef;
+
+ NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
+
+ gRootPort = ::IORegisterForSystemPower(0, &notifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
+ if (gRootPort == MACH_PORT_NULL) {
+ NS_ERROR("IORegisterForSystemPower failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef);
+ ::CFRunLoopAddSource(::CFRunLoopGetCurrent(),
+ mSleepWakeNotificationRLS,
+ kCFRunLoopDefaultMode);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsToolkit::RemoveSleepWakeNotifications()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mSleepWakeNotificationRLS) {
+ ::IODeregisterForSystemPower(&mPowerNotifier);
+ ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(),
+ mSleepWakeNotificationRLS,
+ kCFRunLoopDefaultMode);
+
+ mSleepWakeNotificationRLS = nullptr;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Converts aPoint from the CoreGraphics "global display coordinate" system
+// (which includes all displays/screens and has a top-left origin) to its
+// (presumed) Cocoa counterpart (assumed to be the same as the "screen
+// coordinates" system), which has a bottom-left origin.
+static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint)
+{
+ NSPoint cocoaPoint;
+ cocoaPoint.x = aPoint.x;
+ cocoaPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
+ return cocoaPoint;
+}
+
+// Since our event tap is "listen only", events arrive here a little after
+// they've already been processed.
+static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ((type == kCGEventTapDisabledByUserInput) ||
+ (type == kCGEventTapDisabledByTimeout))
+ return event;
+ if ([NSApp isActive])
+ return event;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE(rollupListener, event);
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (!rollupWidget)
+ return event;
+
+ // Don't bother with rightMouseDown events here -- because of the delay,
+ // we'll end up closing browser context menus that we just opened. Since
+ // these events usually raise a context menu, we'll handle them by hooking
+ // the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed
+ // notification (in nsAppShell.mm's AppShellDelegate).
+ if (type == kCGEventRightMouseDown)
+ return event;
+ NSWindow *ctxMenuWindow = (NSWindow*) rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
+ if (!ctxMenuWindow)
+ return event;
+ NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event));
+ // Don't roll up the rollup widget if our mouseDown happens over it (doing
+ // so would break the corresponding context menu).
+ if (NSPointInRect(screenLocation, [ctxMenuWindow frame]))
+ return event;
+ rollupListener->Rollup(0, false, nullptr, nullptr);
+ return event;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL);
+}
+
+// Cocoa Firefox's use of custom context menus requires that we explicitly
+// handle mouse events from other processes that the OS handles
+// "automatically" for native context menus -- mouseMoved events so that
+// right-click context menus work properly when our browser doesn't have the
+// focus (bmo bug 368077), and mouseDown events so that our browser can
+// dismiss a context menu when a mouseDown happens in another process (bmo
+// bug 339945).
+void
+nsToolkit::RegisterForAllProcessMouseEvents()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (getenv("MOZ_DEBUG"))
+ return;
+
+ // Don't do this for apps that use native context menus.
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+
+ if (!mEventTapRLS) {
+ // Using an event tap for mouseDown events (instead of installing a
+ // handler for them on the EventMonitor target) works around an Apple
+ // bug that causes OS menus (like the Clock menu) not to work properly
+ // on OS X 10.4.X and below (bmo bug 381448).
+ // We install our event tap "listen only" to get around yet another Apple
+ // bug -- when we install it as an event filter on any kind of mouseDown
+ // event, that kind of event stops working in the main menu, and usually
+ // mouse event processing stops working in all apps in the current login
+ // session (so the entire OS appears to be hung)! The downside of
+ // installing listen-only is that events arrive at our handler slightly
+ // after they've already been processed.
+ mEventTapPort = CGEventTapCreate(kCGSessionEventTap,
+ kCGHeadInsertEventTap,
+ kCGEventTapOptionListenOnly,
+ CGEventMaskBit(kCGEventLeftMouseDown)
+ | CGEventMaskBit(kCGEventRightMouseDown)
+ | CGEventMaskBit(kCGEventOtherMouseDown),
+ EventTapCallback,
+ nullptr);
+ if (!mEventTapPort)
+ return;
+ mEventTapRLS = CFMachPortCreateRunLoopSource(nullptr, mEventTapPort, 0);
+ if (!mEventTapRLS) {
+ CFRelease(mEventTapPort);
+ mEventTapPort = nullptr;
+ return;
+ }
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsToolkit::UnregisterAllProcessMouseEventHandlers()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mEventTapRLS) {
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS,
+ kCFRunLoopDefaultMode);
+ CFRelease(mEventTapRLS);
+ mEventTapRLS = nullptr;
+ }
+ if (mEventTapPort) {
+ // mEventTapPort must be invalidated as well as released. Otherwise the
+ // event tap doesn't get destroyed until the browser process ends (it
+ // keeps showing up in the list returned by CGGetEventTapList()).
+ CFMachPortInvalidate(mEventTapPort);
+ CFRelease(mEventTapPort);
+ mEventTapPort = nullptr;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Return the nsToolkit instance. If a toolkit does not yet exist, then one
+// will be created.
+// static
+nsToolkit* nsToolkit::GetToolkit()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!gToolkit) {
+ gToolkit = new nsToolkit();
+ }
+
+ return gToolkit;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
+}
+
+// An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X
+// Leopard and is available to 64-bit binaries on Leopard and above. Based on
+// ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling.
+// Since the Method type becomes an opaque type as of Objective-C 2.0, we'll
+// have to switch to using accessor methods like method_exchangeImplementations()
+// when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard
+// and above).
+//
+// Be aware that, if aClass doesn't have an orgMethod selector but one of its
+// superclasses does, the method substitution will (in effect) take place in
+// that superclass (rather than in aClass itself). The substitution has
+// effect on the class where it takes place and all of that class's
+// subclasses. In order for method swizzling to work properly, posedMethod
+// needs to be unique in the class where the substitution takes place and all
+// of its subclasses.
+nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
+ bool classMethods)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ Method original = nil;
+ Method posed = nil;
+
+ if (classMethods) {
+ original = class_getClassMethod(aClass, orgMethod);
+ posed = class_getClassMethod(aClass, posedMethod);
+ } else {
+ original = class_getInstanceMethod(aClass, orgMethod);
+ posed = class_getInstanceMethod(aClass, posedMethod);
+ }
+
+ if (!original || !posed)
+ return NS_ERROR_FAILURE;
+
+ method_exchangeImplementations(original, posed);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsWidgetFactory.mm b/widget/cocoa/nsWidgetFactory.mm
new file mode 100644
index 000000000..3bddaf95c
--- /dev/null
+++ b/widget/cocoa/nsWidgetFactory.mm
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFactory.h"
+#include "nsISupports.h"
+#include "nsIComponentManager.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+
+#include "nsWidgetsCID.h"
+
+#include "nsChildView.h"
+#include "nsCocoaWindow.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsFilePicker.h"
+#include "nsColorPicker.h"
+
+#include "nsClipboard.h"
+#include "nsClipboardHelper.h"
+#include "nsTransferable.h"
+#include "nsHTMLFormatConverter.h"
+#include "nsDragService.h"
+#include "nsToolkit.h"
+
+#include "nsLookAndFeel.h"
+
+#include "nsSound.h"
+#include "nsIdleServiceX.h"
+#include "NativeKeyBindings.h"
+#include "OSXNotificationCenter.h"
+
+#include "nsScreenManagerCocoa.h"
+#include "nsDeviceContextSpecX.h"
+#include "nsPrintOptionsX.h"
+#include "nsPrintDialogX.h"
+#include "nsPrintSession.h"
+#include "nsToolkitCompsCID.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCocoaWindow)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsChildView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFilePicker)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsColorPicker)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerCocoa)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecX)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsX, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintDialogServiceX, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceX, nsIdleServiceX::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(OSXNotificationCenter, Init)
+
+#include "nsMenuBarX.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeMenuServiceX)
+
+#include "nsBidiKeyboard.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard)
+
+#include "nsNativeThemeCocoa.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeThemeCocoa)
+
+#include "nsMacDockSupport.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacDockSupport)
+
+#include "nsMacWebAppUtils.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacWebAppUtils)
+
+#include "nsStandaloneNativeMenu.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsStandaloneNativeMenu)
+
+#include "nsSystemStatusBarCocoa.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSystemStatusBarCocoa)
+
+#include "GfxInfo.h"
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+} // namespace widget
+} // namespace mozilla
+
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_POPUP_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_SOUND_CID);
+NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
+NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTDIALOGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MACDOCKSUPPORT_CID);
+NS_DEFINE_NAMED_CID(NS_MACWEBAPPUTILS_CID);
+NS_DEFINE_NAMED_CID(NS_STANDALONENATIVEMENU_CID);
+NS_DEFINE_NAMED_CID(NS_MACSYSTEMSTATUSBAR_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, NULL, nsCocoaWindowConstructor },
+ { &kNS_POPUP_CID, false, NULL, nsCocoaWindowConstructor },
+ { &kNS_CHILD_CID, false, NULL, nsChildViewConstructor },
+ { &kNS_FILEPICKER_CID, false, NULL, nsFilePickerConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_COLORPICKER_CID, false, NULL, nsColorPickerConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_APPSHELL_CID, false, NULL, nsAppShellConstructor, mozilla::Module::ALLOW_IN_GPU_PROCESS },
+ { &kNS_SOUND_CID, false, NULL, nsSoundConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_TRANSFERABLE_CID, false, NULL, nsTransferableConstructor },
+ { &kNS_HTMLFORMATCONVERTER_CID, false, NULL, nsHTMLFormatConverterConstructor },
+ { &kNS_CLIPBOARD_CID, false, NULL, nsClipboardConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_CLIPBOARDHELPER_CID, false, NULL, nsClipboardHelperConstructor },
+ { &kNS_DRAGSERVICE_CID, false, NULL, nsDragServiceConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_BIDIKEYBOARD_CID, false, NULL, nsBidiKeyboardConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_THEMERENDERER_CID, false, NULL, nsNativeThemeCocoaConstructor },
+ { &kNS_SCREENMANAGER_CID, false, NULL, nsScreenManagerCocoaConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_DEVICE_CONTEXT_SPEC_CID, false, NULL, nsDeviceContextSpecXConstructor },
+ { &kNS_PRINTSESSION_CID, false, NULL, nsPrintSessionConstructor },
+ { &kNS_PRINTSETTINGSSERVICE_CID, false, NULL, nsPrintOptionsXConstructor },
+ { &kNS_PRINTDIALOGSERVICE_CID, false, NULL, nsPrintDialogServiceXConstructor },
+ { &kNS_IDLE_SERVICE_CID, false, NULL, nsIdleServiceXConstructor },
+ { &kNS_SYSTEMALERTSSERVICE_CID, false, NULL, OSXNotificationCenterConstructor },
+ { &kNS_NATIVEMENUSERVICE_CID, false, NULL, nsNativeMenuServiceXConstructor },
+ { &kNS_MACDOCKSUPPORT_CID, false, NULL, nsMacDockSupportConstructor },
+ { &kNS_MACWEBAPPUTILS_CID, false, NULL, nsMacWebAppUtilsConstructor },
+ { &kNS_STANDALONENATIVEMENU_CID, false, NULL, nsStandaloneNativeMenuConstructor },
+ { &kNS_MACSYSTEMSTATUSBAR_CID, false, NULL, nsSystemStatusBarCocoaConstructor },
+ { &kNS_GFXINFO_CID, false, NULL, mozilla::widget::GfxInfoConstructor },
+ { NULL }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/mac;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/popup/mac;1", &kNS_POPUP_CID },
+ { "@mozilla.org/widgets/childwindow/mac;1", &kNS_CHILD_CID },
+ { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/appshell/mac;1", &kNS_APPSHELL_CID, mozilla::Module::ALLOW_IN_GPU_PROCESS },
+ { "@mozilla.org/sound;1", &kNS_SOUND_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
+ { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
+ { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
+ { NS_PRINTDIALOGSERVICE_CONTRACTID, &kNS_PRINTDIALOGSERVICE_CID },
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/system-alerts-service;1", &kNS_SYSTEMALERTSSERVICE_CID },
+ { "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID },
+ { "@mozilla.org/widget/macdocksupport;1", &kNS_MACDOCKSUPPORT_CID },
+ { "@mozilla.org/widget/mac-web-app-utils;1", &kNS_MACWEBAPPUTILS_CID },
+ { "@mozilla.org/widget/standalonenativemenu;1", &kNS_STANDALONENATIVEMENU_CID },
+ { "@mozilla.org/widget/macsystemstatusbar;1", &kNS_MACSYSTEMSTATUSBAR_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { NULL }
+};
+
+static void
+nsWidgetCocoaModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ NativeKeyBindings::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsToolkit::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ NULL,
+ NULL,
+ nsAppShellInit,
+ nsWidgetCocoaModuleDtor,
+ mozilla::Module::ALLOW_IN_GPU_PROCESS
+};
+
+NSMODULE_DEFN(nsWidgetMacModule) = &kWidgetModule;
diff --git a/widget/cocoa/nsWindowMap.h b/widget/cocoa/nsWindowMap.h
new file mode 100644
index 000000000..c6ad72c01
--- /dev/null
+++ b/widget/cocoa/nsWindowMap.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWindowMap_h_
+#define nsWindowMap_h_
+
+#import <Cocoa/Cocoa.h>
+
+// WindowDataMap
+//
+// In both mozilla and embedding apps, we need to have a place to put
+// per-top-level-window logic and data, to handle such things as IME
+// commit when the window gains/loses focus. We can't use a window
+// delegate, because an embeddor probably already has one. Nor can we
+// subclass NSWindow, again because we can't impose that burden on the
+// embeddor.
+//
+// So we have a global map of NSWindow -> TopLevelWindowData, and set
+// up TopLevelWindowData as a notification observer etc.
+
+@interface WindowDataMap : NSObject
+{
+@private
+ NSMutableDictionary* mWindowMap; // dict of TopLevelWindowData keyed by address of NSWindow
+}
+
++ (WindowDataMap*)sharedWindowDataMap;
+
+- (void)ensureDataForWindow:(NSWindow*)inWindow;
+- (id)dataForWindow:(NSWindow*)inWindow;
+
+// set data for a given window. inData is retained (and any previously set data
+// is released).
+- (void)setData:(id)inData forWindow:(NSWindow*)inWindow;
+
+// remove the data for the given window. the data is released.
+- (void)removeDataForWindow:(NSWindow*)inWindow;
+
+@end
+
+@class ChildView;
+
+// TopLevelWindowData
+//
+// Class to hold per-window data, and handle window state changes.
+
+@interface TopLevelWindowData : NSObject
+{
+@private
+}
+
+- (id)initWithWindow:(NSWindow*)inWindow;
++ (void)activateInWindow:(NSWindow*)aWindow;
++ (void)deactivateInWindow:(NSWindow*)aWindow;
++ (void)activateInWindowViews:(NSWindow*)aWindow;
++ (void)deactivateInWindowViews:(NSWindow*)aWindow;
+
+@end
+
+#endif // nsWindowMap_h_
diff --git a/widget/cocoa/nsWindowMap.mm b/widget/cocoa/nsWindowMap.mm
new file mode 100644
index 000000000..c43b02408
--- /dev/null
+++ b/widget/cocoa/nsWindowMap.mm
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindowMap.h"
+#include "nsObjCExceptions.h"
+#include "nsChildView.h"
+#include "nsCocoaWindow.h"
+
+@interface WindowDataMap(Private)
+
+- (NSString*)keyForWindow:(NSWindow*)inWindow;
+
+@end
+
+@interface TopLevelWindowData(Private)
+
+- (void)windowResignedKey:(NSNotification*)inNotification;
+- (void)windowBecameKey:(NSNotification*)inNotification;
+- (void)windowWillClose:(NSNotification*)inNotification;
+
+@end
+
+#pragma mark -
+
+@implementation WindowDataMap
+
++ (WindowDataMap*)sharedWindowDataMap
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ static WindowDataMap* sWindowMap = nil;
+ if (!sWindowMap)
+ sWindowMap = [[WindowDataMap alloc] init];
+
+ return sWindowMap;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)init
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mWindowMap = [[NSMutableDictionary alloc] initWithCapacity:10];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindowMap release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)ensureDataForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!inWindow || [self dataForWindow:inWindow])
+ return;
+
+ TopLevelWindowData* windowData = [[TopLevelWindowData alloc] initWithWindow:inWindow];
+ [self setData:windowData forWindow:inWindow]; // takes ownership
+ [windowData release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (id)dataForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [mWindowMap objectForKey:[self keyForWindow:inWindow]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)setData:(id)inData forWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindowMap setObject:inData forKey:[self keyForWindow:inWindow]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)removeDataForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindowMap removeObjectForKey:[self keyForWindow:inWindow]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSString*)keyForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSString stringWithFormat:@"%p", inWindow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+@end
+
+// TopLevelWindowData
+//
+// This class holds data about top-level windows. We can't use a window
+// delegate, because an embedder may already have one.
+
+@implementation TopLevelWindowData
+
+- (id)initWithWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowBecameKey:)
+ name:NSWindowDidBecomeKeyNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowResignedKey:)
+ name:NSWindowDidResignKeyNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowBecameMain:)
+ name:NSWindowDidBecomeMainNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowResignedMain:)
+ name:NSWindowDidResignMainNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:inWindow];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// As best I can tell, if the notification's object has a corresponding
+// top-level widget (an nsCocoaWindow object), it has a delegate (set in
+// nsCocoaWindow::StandardCreate()) of class WindowDelegate, and otherwise
+// not (Camino didn't use top-level widgets (nsCocoaWindow objects) --
+// only child widgets (nsChildView objects)). (The notification is sent
+// to windowBecameKey: or windowBecameMain: below.)
+//
+// For use with clients that (like Firefox) do use top-level widgets (and
+// have NSWindow delegates of class WindowDelegate).
++ (void)activateInWindow:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
+ return;
+
+ if ([delegate toplevelActiveState])
+ return;
+ [delegate sendToplevelActivateEvents];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// See comments above activateInWindow:
+//
+// If we're using top-level widgets (nsCocoaWindow objects), we send them
+// NS_DEACTIVATE events (which propagate to child widgets (nsChildView
+// objects) via nsWebShellWindow::HandleEvent()).
+//
+// For use with clients that (like Firefox) do use top-level widgets (and
+// have NSWindow delegates of class WindowDelegate).
++ (void)deactivateInWindow:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
+ return;
+
+ if (![delegate toplevelActiveState])
+ return;
+ [delegate sendToplevelDeactivateEvents];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// For use with clients that (like Camino) don't use top-level widgets (and
+// don't have NSWindow delegates of class WindowDelegate).
++ (void)activateInWindowViews:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ id firstResponder = [aWindow firstResponder];
+ if ([firstResponder isKindOfClass:[ChildView class]])
+ [firstResponder viewsWindowDidBecomeKey];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// For use with clients that (like Camino) don't use top-level widgets (and
+// don't have NSWindow delegates of class WindowDelegate).
++ (void)deactivateInWindowViews:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ id firstResponder = [aWindow firstResponder];
+ if ([firstResponder isKindOfClass:[ChildView class]])
+ [firstResponder viewsWindowDidResignKey];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// We make certain exceptions for top-level windows in non-embedders (see
+// comment above windowBecameMain below). And we need (elsewhere) to guard
+// against sending duplicate events. But in general the NS_ACTIVATE event
+// should be sent when a native window becomes key, and the NS_DEACTIVATE
+// event should be sent when it resignes key.
+- (void)windowBecameKey:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
+ [TopLevelWindowData activateInWindowViews:window];
+ } else if ([window isSheet]) {
+ [TopLevelWindowData activateInWindow:window];
+ }
+
+ [[window contentView] setNeedsDisplay:YES];
+}
+
+- (void)windowResignedKey:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
+ [TopLevelWindowData deactivateInWindowViews:window];
+ } else if ([window isSheet]) {
+ [TopLevelWindowData deactivateInWindow:window];
+ }
+
+ [[window contentView] setNeedsDisplay:YES];
+}
+
+// The appearance of a top-level window depends on its main state (not its key
+// state). So (for non-embedders) we need to ensure that a top-level window
+// is main when an NS_ACTIVATE event is sent to Gecko for it.
+- (void)windowBecameMain:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ // Don't send events to a top-level window that has a sheet open above it --
+ // as far as Gecko is concerned, it's inactive, and stays so until the sheet
+ // closes.
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
+ [TopLevelWindowData activateInWindow:window];
+}
+
+- (void)windowResignedMain:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
+ [TopLevelWindowData deactivateInWindow:window];
+}
+
+- (void)windowWillClose:(NSNotification*)inNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // postpone our destruction
+ [[self retain] autorelease];
+
+ // remove ourselves from the window map (which owns us)
+ [[WindowDataMap sharedWindowDataMap] removeDataForWindow:[inNotification object]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/resources/MainMenu.nib/classes.nib b/widget/cocoa/resources/MainMenu.nib/classes.nib
new file mode 100644
index 000000000..b9b4b09f6
--- /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 000000000..bcf3ace84
--- /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 000000000..16b3f7e52
--- /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 000000000..749871c9e
--- /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 000000000..51511ba18
--- /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 000000000..00a70945d
--- /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 000000000..3a06376c0
--- /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="position: fixed; -moz-appearance: checkbox;" />
+
+</body>
+</html>
diff --git a/widget/crashtests/crashtests.list b/widget/crashtests/crashtests.list
new file mode 100644
index 000000000..957f3f005
--- /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/gonk/GeckoTouchDispatcher.cpp b/widget/gonk/GeckoTouchDispatcher.cpp
new file mode 100644
index 000000000..0b18c91a1
--- /dev/null
+++ b/widget/gonk/GeckoTouchDispatcher.cpp
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* Copyright 2014 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FrameMetrics.h"
+#include "GeckoProfiler.h"
+#include "GeckoTouchDispatcher.h"
+#include "InputData.h"
+#include "ProfilerMarkers.h"
+#include "base/basictypes.h"
+#include "gfxPrefs.h"
+#include "libui/Input.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "nsAppShell.h"
+#include "nsDebug.h"
+#include "nsThreadUtils.h"
+#include "nsWindow.h"
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/Timers.h>
+
+#undef LOG
+#define LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+
+// uncomment to print log resample data
+// #define LOG_RESAMPLE_DATA 1
+
+namespace mozilla {
+
+// Amount of time in MS before an input is considered expired.
+static const uint64_t kInputExpirationThresholdMs = 1000;
+
+static StaticRefPtr<GeckoTouchDispatcher> sTouchDispatcher;
+
+/* static */ GeckoTouchDispatcher*
+GeckoTouchDispatcher::GetInstance()
+{
+ if (!sTouchDispatcher) {
+ sTouchDispatcher = new GeckoTouchDispatcher();
+ ClearOnShutdown(&sTouchDispatcher);
+ }
+ return sTouchDispatcher;
+}
+
+GeckoTouchDispatcher::GeckoTouchDispatcher()
+ : mTouchQueueLock("GeckoTouchDispatcher::mTouchQueueLock")
+ , mHavePendingTouchMoves(false)
+ , mInflightNonMoveEvents(0)
+ , mTouchEventsFiltered(false)
+{
+ // Since GeckoTouchDispatcher is initialized when input is initialized
+ // and reads gfxPrefs, it is the first thing to touch gfxPrefs.
+ // The first thing to touch gfxPrefs MUST occur on the main thread and init
+ // the singleton
+ MOZ_ASSERT(sTouchDispatcher == nullptr);
+ MOZ_ASSERT(NS_IsMainThread());
+ gfxPrefs::GetSingleton();
+
+ mEnabledUniformityInfo = gfxPrefs::UniformityInfo();
+ mVsyncAdjust = TimeDuration::FromMilliseconds(gfxPrefs::TouchVsyncSampleAdjust());
+ mMaxPredict = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMaxPredict());
+ mMinDelta = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMinDelta());
+ mOldTouchThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleOldTouchThreshold());
+ mDelayedVsyncThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleVsyncDelayThreshold());
+}
+
+void
+GeckoTouchDispatcher::SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler *aObserver)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // We assume on b2g that there is only 1 CompositorBridgeParent
+ MOZ_ASSERT(mCompositorVsyncScheduler == nullptr);
+ mCompositorVsyncScheduler = aObserver;
+}
+
+void
+GeckoTouchDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
+{
+ layers::APZThreadUtils::AssertOnControllerThread();
+ DispatchTouchMoveEvents(aVsyncTimestamp);
+}
+
+// Touch data timestamps are in milliseconds, aEventTime is in nanoseconds
+void
+GeckoTouchDispatcher::NotifyTouch(MultiTouchInput& aTouch, TimeStamp aEventTime)
+{
+ if (mCompositorVsyncScheduler) {
+ mCompositorVsyncScheduler->SetNeedsComposite();
+ }
+
+ if (aTouch.mType == MultiTouchInput::MULTITOUCH_MOVE) {
+ MutexAutoLock lock(mTouchQueueLock);
+ if (mInflightNonMoveEvents > 0) {
+ // If we have any pending non-move events, we shouldn't resample the
+ // move events because we might end up dispatching events out of order.
+ // Instead, fall back to a non-resampling in-order dispatch until we're
+ // done processing the non-move events.
+ layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod<MultiTouchInput>(
+ this, &GeckoTouchDispatcher::DispatchTouchEvent, aTouch));
+ return;
+ }
+
+ mTouchMoveEvents.push_back(aTouch);
+ mHavePendingTouchMoves = true;
+ } else {
+ { // scope lock
+ MutexAutoLock lock(mTouchQueueLock);
+ mInflightNonMoveEvents++;
+ }
+ layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod<MultiTouchInput>(
+ this, &GeckoTouchDispatcher::DispatchTouchNonMoveEvent, aTouch));
+ }
+}
+
+void
+GeckoTouchDispatcher::DispatchTouchNonMoveEvent(MultiTouchInput aInput)
+{
+ layers::APZThreadUtils::AssertOnControllerThread();
+
+ // Flush pending touch move events, if there are any
+ // (DispatchTouchMoveEvents will check the mHavePendingTouchMoves flag and
+ // bail out if there's nothing to be done).
+ NotifyVsync(TimeStamp::Now());
+ DispatchTouchEvent(aInput);
+
+ { // scope lock
+ MutexAutoLock lock(mTouchQueueLock);
+ mInflightNonMoveEvents--;
+ MOZ_ASSERT(mInflightNonMoveEvents >= 0);
+ }
+}
+
+void
+GeckoTouchDispatcher::DispatchTouchMoveEvents(TimeStamp aVsyncTime)
+{
+ MultiTouchInput touchMove;
+
+ {
+ MutexAutoLock lock(mTouchQueueLock);
+ if (!mHavePendingTouchMoves) {
+ return;
+ }
+ mHavePendingTouchMoves = false;
+
+ int touchCount = mTouchMoveEvents.size();
+ TimeDuration vsyncTouchDiff = aVsyncTime - mTouchMoveEvents.back().mTimeStamp;
+ // The delay threshold is a positive pref, but we're testing to see if the
+ // vsync time is delayed from the touch, so add a negative sign.
+ bool isDelayedVsyncEvent = vsyncTouchDiff < -mDelayedVsyncThreshold;
+ bool isOldTouch = vsyncTouchDiff > mOldTouchThreshold;
+ bool resample = (touchCount > 1) && !isDelayedVsyncEvent && !isOldTouch;
+
+ if (!resample) {
+ touchMove = mTouchMoveEvents.back();
+ mTouchMoveEvents.clear();
+ if (!isDelayedVsyncEvent && !isOldTouch) {
+ mTouchMoveEvents.push_back(touchMove);
+ }
+ } else {
+ ResampleTouchMoves(touchMove, aVsyncTime);
+ }
+ }
+
+ DispatchTouchEvent(touchMove);
+}
+
+static int
+Interpolate(int start, int end, TimeDuration aFrameDiff, TimeDuration aTouchDiff)
+{
+ return start + (((end - start) * aFrameDiff.ToMicroseconds()) / aTouchDiff.ToMicroseconds());
+}
+
+static const SingleTouchData&
+GetTouchByID(const SingleTouchData& aCurrentTouch, MultiTouchInput& aOtherTouch)
+{
+ int32_t index = aOtherTouch.IndexOfTouch(aCurrentTouch.mIdentifier);
+ if (index < 0) {
+ // We can have situations where a previous touch event had 2 fingers
+ // and we lift 1 finger off. In those cases, we won't find the touch event
+ // with given id, so just return the current touch, which will be resampled
+ // without modification and dispatched.
+ return aCurrentTouch;
+ }
+ return aOtherTouch.mTouches[index];
+}
+
+
+// aTouchDiff is the duration between the base and current touch times
+// aFrameDiff is the duration between the base and the time we're resampling to
+static void
+ResampleTouch(MultiTouchInput& aOutTouch,
+ MultiTouchInput& aBase, MultiTouchInput& aCurrent,
+ TimeDuration aFrameDiff, TimeDuration aTouchDiff)
+{
+ aOutTouch = aCurrent;
+
+ // Make sure we only resample the correct finger.
+ for (size_t i = 0; i < aOutTouch.mTouches.Length(); i++) {
+ const SingleTouchData& current = aCurrent.mTouches[i];
+ const SingleTouchData& base = GetTouchByID(current, aBase);
+
+ const ScreenIntPoint& baseTouchPoint = base.mScreenPoint;
+ const ScreenIntPoint& currentTouchPoint = current.mScreenPoint;
+
+ ScreenIntPoint newSamplePoint;
+ newSamplePoint.x = Interpolate(baseTouchPoint.x, currentTouchPoint.x, aFrameDiff, aTouchDiff);
+ newSamplePoint.y = Interpolate(baseTouchPoint.y, currentTouchPoint.y, aFrameDiff, aTouchDiff);
+
+ aOutTouch.mTouches[i].mScreenPoint = newSamplePoint;
+
+#ifdef LOG_RESAMPLE_DATA
+ const char* type = "extrapolate";
+ if (aFrameDiff < aTouchDiff) {
+ type = "interpolate";
+ }
+
+ float alpha = aFrameDiff / aTouchDiff;
+ LOG("%s base (%d, %d), current (%d, %d) to (%d, %d) alpha %f, touch diff %d, frame diff %d\n",
+ type,
+ baseTouchPoint.x, baseTouchPoint.y,
+ currentTouchPoint.x, currentTouchPoint.y,
+ newSamplePoint.x, newSamplePoint.y,
+ alpha, (int)aTouchDiff.ToMilliseconds(), (int)aFrameDiff.ToMilliseconds());
+#endif
+ }
+}
+
+/*
+ * +> Base touch (The touch before current touch)
+ * |
+ * | +> Current touch (Latest touch)
+ * | |
+ * | | +> Maximum resample time
+ * | | |
+ * +-----+------+--------------------> Time
+ * ^ ^
+ * | |
+ * +------+--> Potential vsync events which the touches are resampled to
+ * | |
+ * | +> Extrapolation
+ * |
+ * +> Interpolation
+ */
+
+void
+GeckoTouchDispatcher::ResampleTouchMoves(MultiTouchInput& aOutTouch, TimeStamp aVsyncTime)
+{
+ MOZ_RELEASE_ASSERT(mTouchMoveEvents.size() >= 2);
+ mTouchQueueLock.AssertCurrentThreadOwns();
+
+ MultiTouchInput currentTouch = mTouchMoveEvents.back();
+ mTouchMoveEvents.pop_back();
+ MultiTouchInput baseTouch = mTouchMoveEvents.back();
+ mTouchMoveEvents.clear();
+ mTouchMoveEvents.push_back(currentTouch);
+
+ TimeStamp sampleTime = aVsyncTime - mVsyncAdjust;
+ TimeDuration touchDiff = currentTouch.mTimeStamp - baseTouch.mTimeStamp;
+
+ if (touchDiff < mMinDelta) {
+ aOutTouch = currentTouch;
+ #ifdef LOG_RESAMPLE_DATA
+ LOG("The touches are too close, skip resampling\n");
+ #endif
+ return;
+ }
+
+ if (currentTouch.mTimeStamp < sampleTime) {
+ TimeDuration maxResampleTime = std::min(touchDiff / int64_t(2), mMaxPredict);
+ TimeStamp maxTimestamp = currentTouch.mTimeStamp + maxResampleTime;
+ if (sampleTime > maxTimestamp) {
+ sampleTime = maxTimestamp;
+ #ifdef LOG_RESAMPLE_DATA
+ LOG("Overshot extrapolation time, adjusting sample time\n");
+ #endif
+ }
+ }
+
+ ResampleTouch(aOutTouch, baseTouch, currentTouch, sampleTime - baseTouch.mTimeStamp, touchDiff);
+
+ // Both mTimeStamp and mTime are being updated to sampleTime here.
+ // mTime needs to be updated using a delta since TimeStamp doesn't
+ // provide a way to obtain a raw value.
+ aOutTouch.mTime += (sampleTime - aOutTouch.mTimeStamp).ToMilliseconds();
+ aOutTouch.mTimeStamp = sampleTime;
+}
+
+static bool
+IsExpired(const MultiTouchInput& aTouch)
+{
+ // No pending events, the filter state can be updated.
+ uint64_t timeNowMs = systemTime(SYSTEM_TIME_MONOTONIC) / 1000000;
+ return (timeNowMs - aTouch.mTime) > kInputExpirationThresholdMs;
+}
+void
+GeckoTouchDispatcher::DispatchTouchEvent(MultiTouchInput aMultiTouch)
+{
+ if ((aMultiTouch.mType == MultiTouchInput::MULTITOUCH_END ||
+ aMultiTouch.mType == MultiTouchInput::MULTITOUCH_CANCEL) &&
+ aMultiTouch.mTouches.Length() == 1) {
+ MutexAutoLock lock(mTouchQueueLock);
+ mTouchMoveEvents.clear();
+ } else if (aMultiTouch.mType == MultiTouchInput::MULTITOUCH_START &&
+ aMultiTouch.mTouches.Length() == 1) {
+ mTouchEventsFiltered = IsExpired(aMultiTouch);
+ }
+
+ if (mTouchEventsFiltered) {
+ return;
+ }
+
+ nsWindow::DispatchTouchInput(aMultiTouch);
+
+ if (mEnabledUniformityInfo && profiler_is_active()) {
+ const char* touchAction = "Invalid";
+ switch (aMultiTouch.mType) {
+ case MultiTouchInput::MULTITOUCH_START:
+ touchAction = "Touch_Event_Down";
+ break;
+ case MultiTouchInput::MULTITOUCH_MOVE:
+ touchAction = "Touch_Event_Move";
+ break;
+ case MultiTouchInput::MULTITOUCH_END:
+ case MultiTouchInput::MULTITOUCH_CANCEL:
+ touchAction = "Touch_Event_Up";
+ break;
+ case MultiTouchInput::MULTITOUCH_SENTINEL:
+ MOZ_ASSERT_UNREACHABLE("Invalid MultTouchInput.");
+ break;
+ }
+
+ const ScreenIntPoint& touchPoint = aMultiTouch.mTouches[0].mScreenPoint;
+ TouchDataPayload* payload = new TouchDataPayload(touchPoint);
+ PROFILER_MARKER_PAYLOAD(touchAction, payload);
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/GeckoTouchDispatcher.h b/widget/gonk/GeckoTouchDispatcher.h
new file mode 100644
index 000000000..3c7acd0e3
--- /dev/null
+++ b/widget/gonk/GeckoTouchDispatcher.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* Copyright 2014 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GECKO_TOUCH_INPUT_DISPATCHER_h
+#define GECKO_TOUCH_INPUT_DISPATCHER_h
+
+#include "InputData.h"
+#include "Units.h"
+#include "mozilla/Mutex.h"
+#include <vector>
+#include "mozilla/RefPtr.h"
+
+class nsIWidget;
+
+namespace mozilla {
+namespace layers {
+class CompositorVsyncScheduler;
+}
+
+// Used to resample touch events whenever a vsync event occurs. It batches
+// touch moves and on every vsync, resamples the touch position to create smooth
+// scrolls. We use the Android touch resample algorithm. It uses a combination of
+// extrapolation and interpolation. The algorithm takes the vsync time and
+// subtracts mVsyncAdjust time in ms and creates a sample time. All touch events are
+// relative to this sample time. If the last touch event occurs AFTER this
+// sample time, interpolate the last two touch events. If the last touch event occurs BEFORE
+// this sample time, we extrapolate the last two touch events to the sample
+// time. The magic numbers defined as constants are taken from android
+// InputTransport.cpp.
+class GeckoTouchDispatcher final
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoTouchDispatcher)
+
+public:
+ static GeckoTouchDispatcher* GetInstance();
+ void NotifyTouch(MultiTouchInput& aTouch, TimeStamp aEventTime);
+ void DispatchTouchEvent(MultiTouchInput aMultiTouch);
+ void DispatchTouchNonMoveEvent(MultiTouchInput aInput);
+ void DispatchTouchMoveEvents(TimeStamp aVsyncTime);
+ void NotifyVsync(TimeStamp aVsyncTimestamp);
+ void SetCompositorVsyncScheduler(layers::CompositorVsyncScheduler* aObserver);
+
+protected:
+ ~GeckoTouchDispatcher() {}
+
+private:
+ GeckoTouchDispatcher();
+ void ResampleTouchMoves(MultiTouchInput& aOutTouch, TimeStamp vsyncTime);
+ void SendTouchEvent(MultiTouchInput& aData);
+ void DispatchMouseEvent(MultiTouchInput& aMultiTouch,
+ bool aForwardToChildren);
+
+ // mTouchQueueLock is used to protect the vector and state below
+ // as it is accessed on multiple threads.
+ Mutex mTouchQueueLock;
+ std::vector<MultiTouchInput> mTouchMoveEvents;
+ bool mHavePendingTouchMoves;
+ int mInflightNonMoveEvents;
+ // end stuff protected by mTouchQueueLock
+
+ bool mResamplingEnabled;
+ bool mTouchEventsFiltered;
+ bool mEnabledUniformityInfo;
+
+ // All times below are in nanoseconds
+ TimeDuration mVsyncAdjust; // Time from vsync we create sample times from
+ TimeDuration mMaxPredict; // How far into the future we're allowed to extrapolate
+ TimeDuration mMinDelta; // Minimal time difference between touches for resampling
+
+ // Amount of time between vsync and the last event that is required before we
+ // resample
+ TimeDuration mMinResampleTime;
+
+ // Threshold if a vsync event runs too far behind touch events
+ TimeDuration mDelayedVsyncThreshold;
+
+ // How far ahead can vsync events get ahead of touch events.
+ TimeDuration mOldTouchThreshold;
+
+ RefPtr<layers::CompositorVsyncScheduler> mCompositorVsyncScheduler;
+};
+
+} // namespace mozilla
+
+#endif // GECKO_TOUCH_INPUT_DISPATCHER_h
diff --git a/widget/gonk/GfxInfo.cpp b/widget/gonk/GfxInfo.cpp
new file mode 100644
index 000000000..7ddd39038
--- /dev/null
+++ b/widget/gonk/GfxInfo.cpp
@@ -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/. */
+
+#include "GfxInfo.h"
+
+using namespace mozilla::widget;
+
+/* 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;
+}
+
+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)
+{
+ aAdapterDescription.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString & aAdapterDescription)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(nsAString & aAdapterRAM)
+{
+ aAdapterRAM.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & 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::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion)
+{
+ aAdapterDriverVersion.Truncate();
+ 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)
+{
+ aAdapterVendorID.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID)
+{
+ aAdapterDeviceID.Truncate();
+ 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()
+{
+ return *mDriverInfo;
+}
+
+uint32_t GfxInfo::OperatingSystemVersion()
+{
+ return 0;
+}
+
+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_OK;
+
+ return NS_OK;
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString &)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString &)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString &)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t)
+{
+ return NS_OK;
+}
+
+#endif
diff --git a/widget/gonk/GfxInfo.h b/widget/gonk/GfxInfo.h
new file mode 100644
index 000000000..61494713f
--- /dev/null
+++ b/widget/gonk/GfxInfo.h
@@ -0,0 +1,69 @@
+/* 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"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo : 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);
+ NS_IMETHOD GetDWriteEnabled(bool *aDWriteEnabled);
+ NS_IMETHOD GetDWriteVersion(nsAString & aDwriteVersion);
+ NS_IMETHOD GetCleartypeParameters(nsAString & aCleartypeParams);
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription);
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver);
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID);
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID);
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID);
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM);
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion);
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate);
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription);
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver);
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID);
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID);
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID);
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM);
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion);
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate);
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active);
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+ virtual uint32_t OperatingSystemVersion() override;
+
+#ifdef DEBUG
+ 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);
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/gonk/GonkClipboardData.cpp b/widget/gonk/GonkClipboardData.cpp
new file mode 100644
index 000000000..ced6422a5
--- /dev/null
+++ b/widget/gonk/GonkClipboardData.cpp
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GonkClipboardData.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+
+namespace mozilla {
+
+void
+GonkClipboardData::SetText(const nsAString &aText)
+{
+ mPlain = aText;
+}
+
+bool
+GonkClipboardData::HasText() const
+{
+ return !mPlain.IsEmpty();
+}
+
+const nsAString&
+GonkClipboardData::GetText() const
+{
+ return mPlain;
+}
+
+void
+GonkClipboardData::SetHTML(const nsAString &aHTML)
+{
+ mHTML = aHTML;
+}
+
+bool
+GonkClipboardData::HasHTML() const
+{
+ return !mHTML.IsEmpty();
+}
+
+const nsAString&
+GonkClipboardData::GetHTML() const
+{
+ return mHTML;
+}
+
+void
+GonkClipboardData::SetImage(gfx::DataSourceSurface* aDataSource)
+{
+ // Clone a new DataSourceSurface and store it.
+ mImage = gfx::CreateDataSourceSurfaceByCloning(aDataSource);
+}
+
+bool
+GonkClipboardData::HasImage() const
+{
+ return static_cast<bool>(mImage);
+}
+
+already_AddRefed<gfx::DataSourceSurface>
+GonkClipboardData::GetImage() const
+{
+ // Return cloned DataSourceSurface.
+ RefPtr<gfx::DataSourceSurface> cloned = gfx::CreateDataSourceSurfaceByCloning(mImage);
+ return cloned.forget();
+}
+
+void
+GonkClipboardData::Clear()
+{
+ mPlain.Truncate(0);
+ mHTML.Truncate(0);
+ mImage = nullptr;
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/GonkClipboardData.h b/widget/gonk/GonkClipboardData.h
new file mode 100644
index 000000000..8bc1f1c9c
--- /dev/null
+++ b/widget/gonk/GonkClipboardData.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_GonkClipboardData
+#define mozilla_GonkClipboardData
+
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+namespace gfx {
+class DataSourceSurface;
+}
+
+class GonkClipboardData final
+{
+public:
+ explicit GonkClipboardData() = default;
+ ~GonkClipboardData() = default;
+
+ // For text/plain
+ void SetText(const nsAString &aText);
+ bool HasText() const;
+ const nsAString& GetText() const;
+
+ // For text/html
+ void SetHTML(const nsAString &aHTML);
+ bool HasHTML() const;
+ const nsAString& GetHTML() const;
+
+ // For images
+ void SetImage(gfx::DataSourceSurface* aDataSource);
+ bool HasImage() const;
+ already_AddRefed<gfx::DataSourceSurface> GetImage() const;
+
+ // For other APIs
+ void Clear();
+
+private:
+ nsAutoString mPlain;
+ nsAutoString mHTML;
+ RefPtr<gfx::DataSourceSurface> mImage;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_GonkClipboardData
diff --git a/widget/gonk/GonkKeyMapping.h b/widget/gonk/GonkKeyMapping.h
new file mode 100644
index 000000000..d5d4e7a0b
--- /dev/null
+++ b/widget/gonk/GonkKeyMapping.h
@@ -0,0 +1,301 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GONKKEYMAPPING_H
+#define GONKKEYMAPPING_H
+
+#include "libui/android_keycodes.h"
+#include "mozilla/EventForwards.h"
+
+namespace mozilla {
+namespace widget {
+
+/* See libui/KeycodeLabels.h for the mapping */
+static const unsigned long kKeyMapping[] = {
+ 0,
+ 0, // SOFT_LEFT
+ 0, // SOFT_RIGHT
+ NS_VK_HOME, // HOME
+ NS_VK_ESCAPE, // BACK
+ 0, // CALL
+ NS_VK_SLEEP, // ENDCALL
+ NS_VK_0,
+ NS_VK_1,
+ NS_VK_2,
+ NS_VK_3,
+ NS_VK_4,
+ NS_VK_5,
+ NS_VK_6,
+ NS_VK_7,
+ NS_VK_8,
+ NS_VK_9,
+ NS_VK_ASTERISK,
+ NS_VK_HASH,
+ NS_VK_UP,
+ NS_VK_DOWN,
+ NS_VK_LEFT,
+ NS_VK_RIGHT,
+ NS_VK_RETURN,
+ NS_VK_VOLUME_UP,
+ NS_VK_VOLUME_DOWN,
+ NS_VK_SLEEP, // POWER
+ NS_VK_PRINTSCREEN, // CAMERA
+ NS_VK_CLEAR,
+ NS_VK_A,
+ NS_VK_B,
+ NS_VK_C,
+ NS_VK_D,
+ NS_VK_E,
+ NS_VK_F,
+ NS_VK_G,
+ NS_VK_H,
+ NS_VK_I,
+ NS_VK_J,
+ NS_VK_K,
+ NS_VK_L,
+ NS_VK_M,
+ NS_VK_N,
+ NS_VK_O,
+ NS_VK_P,
+ NS_VK_Q,
+ NS_VK_R,
+ NS_VK_S,
+ NS_VK_T,
+ NS_VK_U,
+ NS_VK_V,
+ NS_VK_W,
+ NS_VK_X,
+ NS_VK_Y,
+ NS_VK_Z,
+ NS_VK_COMMA,
+ NS_VK_PERIOD,
+ 0,
+ 0,
+ 0,
+ 0,
+ NS_VK_TAB,
+ NS_VK_SPACE,
+ NS_VK_META, // SYM
+ 0, // EXPLORER
+ 0, // ENVELOPE
+ NS_VK_RETURN, // ENTER
+ NS_VK_BACK,
+ NS_VK_BACK_QUOTE, // GRAVE
+ NS_VK_HYPHEN_MINUS,
+ NS_VK_EQUALS,
+ NS_VK_OPEN_BRACKET,
+ NS_VK_CLOSE_BRACKET,
+ NS_VK_BACK_SLASH,
+ NS_VK_SEMICOLON,
+ NS_VK_QUOTE,
+ NS_VK_SLASH,
+ NS_VK_AT,
+ 0, // NUM
+ NS_VK_F1, // HEADSETHOOK
+ 0, // FOCUS
+ NS_VK_PLUS,
+ NS_VK_CONTEXT_MENU,
+ 0, // NOTIFICATION
+ NS_VK_F5, // SEARCH
+ 0, // MEDIA_PLAY_PAUSE
+ 0, // MEDIA_STOP
+ 0, // MEDIA_NEXT
+ 0, // MEDIA_PREVIOUS
+ 0, // MEDIA_REWIND
+ 0, // MEDIA_FAST_FORWARD
+ 0, // MUTE
+ NS_VK_PAGE_UP,
+ NS_VK_PAGE_DOWN,
+ 0, // PICTSYMBOLS
+ 0, // SWITCH_CHARSET
+ 0, // BUTTON_A
+ 0, // BUTTON_B
+ 0, // BUTTON_C
+ 0, // BUTTON_X
+ 0, // BUTTON_Y
+ 0, // BUTTON_Z
+ 0, // BUTTON_L1
+ 0, // BUTTON_R1
+ 0, // BUTTON_L2
+ 0, // BUTTON_R2
+ 0, // BUTTON_THUMBL
+ 0, // BUTTON_THUMBR
+ 0, // BUTTON_START
+ 0, // BUTTON_SELECT
+ 0, // BUTTON_MODE
+ NS_VK_ESCAPE,
+ NS_VK_DELETE,
+ 0, // CTRL_LEFT
+ 0, // CTRL_RIGHT
+ NS_VK_CAPS_LOCK,
+ NS_VK_SCROLL_LOCK,
+ 0, // META_LEFT
+ 0, // META_RIGHT
+ 0, // FUNCTION
+ 0, // SYSRQ
+ 0, // BREAK
+ NS_VK_HOME, // MOVE_HOME
+ NS_VK_END,
+ NS_VK_INSERT,
+ 0, // FORWARD
+ 0, // MEDIA_PLAY
+ 0, // MEDIA_PAUSE
+ 0, // MEDIA_CLOSE
+ 0, // MEDIA_EJECT
+ 0, // MEDIA_RECORD
+ NS_VK_F1,
+ NS_VK_F2,
+ NS_VK_F3,
+ NS_VK_F4,
+ NS_VK_F5,
+ NS_VK_F6,
+ NS_VK_F7,
+ NS_VK_F8,
+ NS_VK_F9,
+ NS_VK_F10,
+ NS_VK_F11,
+ NS_VK_F12,
+ NS_VK_NUM_LOCK,
+ NS_VK_NUMPAD0,
+ NS_VK_NUMPAD1,
+ NS_VK_NUMPAD2,
+ NS_VK_NUMPAD3,
+ NS_VK_NUMPAD4,
+ NS_VK_NUMPAD5,
+ NS_VK_NUMPAD6,
+ NS_VK_NUMPAD7,
+ NS_VK_NUMPAD8,
+ NS_VK_NUMPAD9,
+ NS_VK_DIVIDE,
+ NS_VK_MULTIPLY,
+ NS_VK_SUBTRACT,
+ NS_VK_ADD,
+ NS_VK_PERIOD,
+ NS_VK_COMMA,
+ NS_VK_RETURN,
+ NS_VK_EQUALS,
+ 0, // NUMPAD_LEFT_PAREN
+ 0, // NUMPAD_RIGHT_PAREN
+ NS_VK_VOLUME_MUTE,
+ // There are more but we don't map them
+};
+
+static KeyNameIndex GetKeyNameIndex(int aKeyCode)
+{
+ switch (aKeyCode) {
+#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
+
+ case AKEYCODE_0:
+ case AKEYCODE_1:
+ case AKEYCODE_2:
+ case AKEYCODE_3:
+ case AKEYCODE_4:
+ case AKEYCODE_5:
+ case AKEYCODE_6:
+ case AKEYCODE_7:
+ case AKEYCODE_8:
+ case AKEYCODE_9:
+ case AKEYCODE_STAR:
+ case AKEYCODE_POUND:
+ case AKEYCODE_A:
+ case AKEYCODE_B:
+ case AKEYCODE_C:
+ case AKEYCODE_D:
+ case AKEYCODE_E:
+ case AKEYCODE_F:
+ case AKEYCODE_G:
+ case AKEYCODE_H:
+ case AKEYCODE_I:
+ case AKEYCODE_J:
+ case AKEYCODE_K:
+ case AKEYCODE_L:
+ case AKEYCODE_M:
+ case AKEYCODE_N:
+ case AKEYCODE_O:
+ case AKEYCODE_P:
+ case AKEYCODE_Q:
+ case AKEYCODE_R:
+ case AKEYCODE_S:
+ case AKEYCODE_T:
+ case AKEYCODE_U:
+ case AKEYCODE_V:
+ case AKEYCODE_W:
+ case AKEYCODE_X:
+ case AKEYCODE_Y:
+ case AKEYCODE_Z:
+ case AKEYCODE_COMMA:
+ case AKEYCODE_PERIOD:
+ case AKEYCODE_SPACE:
+ case AKEYCODE_GRAVE:
+ case AKEYCODE_MINUS:
+ case AKEYCODE_EQUALS:
+ case AKEYCODE_LEFT_BRACKET:
+ case AKEYCODE_RIGHT_BRACKET:
+ case AKEYCODE_BACKSLASH:
+ case AKEYCODE_SEMICOLON:
+ case AKEYCODE_APOSTROPHE:
+ case AKEYCODE_SLASH:
+ case AKEYCODE_AT:
+ case AKEYCODE_PLUS:
+ 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:
+ return KEY_NAME_INDEX_USE_STRING;
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+static CodeNameIndex GetCodeNameIndex(int 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;
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* GONKKEYMAPPING_H */
diff --git a/widget/gonk/GonkMemoryPressureMonitoring.cpp b/widget/gonk/GonkMemoryPressureMonitoring.cpp
new file mode 100644
index 000000000..0fafb37cf
--- /dev/null
+++ b/widget/gonk/GonkMemoryPressureMonitoring.cpp
@@ -0,0 +1,359 @@
+/* -*- 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 <android/log.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/sysinfo.h>
+
+#include "GonkMemoryPressureMonitoring.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProcessPriorityManager.h"
+#include "mozilla/Services.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsMemoryPressure.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+
+#define LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args)
+
+using namespace mozilla;
+
+namespace {
+
+/**
+ * MemoryPressureWatcher watches sysfs from its own thread to notice when the
+ * system is under memory pressure. When we observe memory pressure, we use
+ * MemoryPressureRunnable to notify observers that they should release memory.
+ *
+ * When the system is under memory pressure, we don't want to constantly fire
+ * memory-pressure events. So instead, we try to detect when sysfs indicates
+ * that we're no longer under memory pressure, and only then start firing events
+ * again.
+ *
+ * (This is a bit problematic because we can't poll() to detect when we're no
+ * longer under memory pressure; instead we have to periodically read the sysfs
+ * node. If we remain under memory pressure for a long time, this means we'll
+ * continue waking up to read from the node for a long time, potentially wasting
+ * battery life. Hopefully we don't hit this case in practice! We write to
+ * logcat each time we go around this loop so it's at least noticable.)
+ *
+ * Shutting down safely is a bit of a chore. XPCOM won't shut down until all
+ * threads exit, so we need to exit the Run() method below on shutdown. But our
+ * thread might be blocked in one of two situations: We might be poll()'ing the
+ * sysfs node waiting for memory pressure to occur, or we might be asleep
+ * waiting to read() the sysfs node to see if we're no longer under memory
+ * pressure.
+ *
+ * To let us wake up from the poll(), we poll() not just the sysfs node but also
+ * a pipe, which we write to on shutdown. To let us wake up from sleeping
+ * between read()s, we sleep by Wait()'ing on a monitor, which we notify on
+ * shutdown.
+ */
+class MemoryPressureWatcher final
+ : public nsIRunnable
+ , public nsIObserver
+{
+public:
+ MemoryPressureWatcher()
+ : mMonitor("MemoryPressureWatcher")
+ , mLowMemTriggerKB(0)
+ , mPageSize(0)
+ , mShuttingDown(false)
+ , mTriggerFd(-1)
+ , mShutdownPipeRead(-1)
+ , mShutdownPipeWrite(-1)
+ {
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsresult Init()
+ {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ // The observer service holds us alive.
+ os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* ownsWeak */ false);
+
+ // Initialize the internal state
+ mPageSize = sysconf(_SC_PAGESIZE);
+ ReadPrefs();
+ nsresult rv = OpenFiles();
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetLowMemTrigger(mSoftLowMemTriggerKB);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+ {
+ MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
+ LOG("Observed XPCOM shutdown.");
+
+ MonitorAutoLock lock(mMonitor);
+ mShuttingDown = true;
+ mMonitor.Notify();
+
+ int rv;
+ do {
+ // Write something to the pipe; doesn't matter what.
+ uint32_t dummy = 0;
+ rv = write(mShutdownPipeWrite, &dummy, sizeof(dummy));
+ } while(rv == -1 && errno == EINTR);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ int triggerResetTimeout = -1;
+ bool memoryPressure;
+ nsresult rv = CheckForMemoryPressure(&memoryPressure);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (true) {
+ // Wait for a notification on mTriggerFd or for data to be written to
+ // mShutdownPipeWrite. (poll(mTriggerFd, POLLPRI) blocks until we're
+ // under memory pressure or until we time out, the time out is used
+ // to adjust the trigger level after a memory pressure event.)
+ struct pollfd pollfds[2];
+ pollfds[0].fd = mTriggerFd;
+ pollfds[0].events = POLLPRI;
+ pollfds[1].fd = mShutdownPipeRead;
+ pollfds[1].events = POLLIN;
+
+ int pollRv = MOZ_TEMP_FAILURE_RETRY(
+ poll(pollfds, ArrayLength(pollfds), triggerResetTimeout)
+ );
+
+ if (pollRv == 0) {
+ // Timed out, adjust the trigger and update the timeout.
+ triggerResetTimeout = AdjustTrigger(triggerResetTimeout);
+ continue;
+ }
+
+ if (pollfds[1].revents) {
+ // Something was written to our shutdown pipe; we're outta here.
+ LOG("shutting down (1)");
+ return NS_OK;
+ }
+
+ // If pollfds[1] isn't happening, pollfds[0] ought to be!
+ if (!(pollfds[0].revents & POLLPRI)) {
+ LOG("Unexpected revents value after poll(): %d. "
+ "Shutting down GonkMemoryPressureMonitoring.", pollfds[0].revents);
+ return NS_ERROR_FAILURE;
+ }
+
+ // POLLPRI on mTriggerFd indicates that we're in a low-memory situation.
+ // We could read lowMemFd to double-check, but we've observed that the
+ // read sometimes completes after the memory-pressure event is over, so
+ // let's just believe the result of poll().
+ rv = DispatchMemoryPressure(MemPressure_New);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Move to the hard level if we're on the soft one.
+ if (mLowMemTriggerKB > mHardLowMemTriggerKB) {
+ SetLowMemTrigger(mHardLowMemTriggerKB);
+ }
+
+ // Manually check mTriggerFd until we observe that memory pressure is
+ // over. We won't fire any more low-memory events until we observe that
+ // we're no longer under pressure. Instead, we fire low-memory-ongoing
+ // events, which cause processes to keep flushing caches but will not
+ // trigger expensive GCs and other attempts to save memory that are
+ // likely futile at this point.
+ do {
+ {
+ MonitorAutoLock lock(mMonitor);
+
+ // We need to check mShuttingDown before we wait here, in order to
+ // catch a shutdown signal sent after we poll()'ed mShutdownPipeRead
+ // above but before we started waiting on the monitor. But we don't
+ // need to check after we wait, because we'll either do another
+ // iteration of this inner loop, in which case we'll check
+ // mShuttingDown, or we'll exit this loop and do another iteration
+ // of the outer loop, in which case we'll check the shutdown pipe.
+ if (mShuttingDown) {
+ LOG("shutting down (2)");
+ return NS_OK;
+ }
+ mMonitor.Wait(PR_MillisecondsToInterval(mPollMS));
+ }
+
+ LOG("Checking to see if memory pressure is over.");
+ rv = CheckForMemoryPressure(&memoryPressure);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (memoryPressure) {
+ rv = DispatchMemoryPressure(MemPressure_Ongoing);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+ } while (false);
+
+ if (XRE_IsParentProcess()) {
+ // The main process will try to adjust the trigger.
+ triggerResetTimeout = mPollMS * 2;
+ }
+
+ LOG("Memory pressure is over.");
+ }
+
+ return NS_OK;
+ }
+
+protected:
+ ~MemoryPressureWatcher() {}
+
+private:
+ void ReadPrefs() {
+ // While we're under memory pressure, we periodically read()
+ // notify_trigger_active to try and see when we're no longer under memory
+ // pressure. mPollMS indicates how many milliseconds we wait between those
+ // read()s.
+ Preferences::AddUintVarCache(&mPollMS,
+ "gonk.systemMemoryPressureRecoveryPollMS", /* default */ 5000);
+
+ // We have two values for the notify trigger, a soft one which is triggered
+ // before we start killing background applications and an hard one which is
+ // after we've killed background applications but before we start killing
+ // foreground ones.
+ Preferences::AddUintVarCache(&mSoftLowMemTriggerKB,
+ "gonk.notifySoftLowMemUnderKB", /* default */ 43008);
+ Preferences::AddUintVarCache(&mHardLowMemTriggerKB,
+ "gonk.notifyHardLowMemUnderKB", /* default */ 14336);
+ }
+
+ nsresult OpenFiles() {
+ mTriggerFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
+ O_RDONLY | O_CLOEXEC);
+ NS_ENSURE_STATE(mTriggerFd != -1);
+
+ int pipes[2];
+ NS_ENSURE_STATE(!pipe(pipes));
+ mShutdownPipeRead = pipes[0];
+ mShutdownPipeWrite = pipes[1];
+ return NS_OK;
+ }
+
+ /**
+ * Set the low memory trigger to the specified value, this can be done by
+ * the main process alone.
+ */
+ void SetLowMemTrigger(uint32_t aValue) {
+ if (XRE_IsParentProcess()) {
+ nsPrintfCString str("%ld", (aValue * 1024) / mPageSize);
+ if (WriteSysFile("/sys/module/lowmemorykiller/parameters/notify_trigger",
+ str.get())) {
+ mLowMemTriggerKB = aValue;
+ }
+ }
+ }
+
+ /**
+ * Read from the trigger file descriptor and determine whether we're
+ * currently under memory pressure.
+ *
+ * We don't expect this method to block.
+ */
+ nsresult CheckForMemoryPressure(bool* aOut)
+ {
+ *aOut = false;
+
+ lseek(mTriggerFd, 0, SEEK_SET);
+
+ char buf[2];
+ int nread = MOZ_TEMP_FAILURE_RETRY(read(mTriggerFd, buf, sizeof(buf)));
+ NS_ENSURE_STATE(nread == 2);
+
+ // The notify_trigger_active sysfs node should contain either "0\n" or
+ // "1\n". The latter indicates memory pressure.
+ *aOut = (buf[0] == '1');
+ return NS_OK;
+ }
+
+ int AdjustTrigger(int timeout)
+ {
+ if (!XRE_IsParentProcess()) {
+ return -1; // Only the main process can adjust the trigger.
+ }
+
+ struct sysinfo info;
+ int rv = sysinfo(&info);
+ if (rv < 0) {
+ return -1; // Without system information we're blind, bail out.
+ }
+
+ size_t freeMemory = (info.freeram * info.mem_unit) / 1024;
+
+ if (freeMemory > mSoftLowMemTriggerKB) {
+ SetLowMemTrigger(mSoftLowMemTriggerKB);
+ return -1; // Trigger adjusted, wait indefinitely.
+ }
+
+ // Wait again but double the duration, max once per day.
+ return std::min(86400000, timeout * 2);
+ }
+
+ /**
+ * Dispatch the specified memory pressure event unless a high-priority
+ * process is present. If a high-priority process is present then it's likely
+ * responding to an urgent event (an incoming call or message for example) so
+ * avoid wasting CPU time responding to low-memory events.
+ */
+ nsresult DispatchMemoryPressure(MemoryPressureState state)
+ {
+ if (ProcessPriorityManager::AnyProcessHasHighPriority()) {
+ return NS_OK;
+ }
+
+ return NS_DispatchMemoryPressure(state);
+ }
+
+ Monitor mMonitor;
+ uint32_t mPollMS; // Ongoing pressure poll delay
+ uint32_t mSoftLowMemTriggerKB; // Soft memory pressure level
+ uint32_t mHardLowMemTriggerKB; // Hard memory pressure level
+ uint32_t mLowMemTriggerKB; // Current value of the trigger
+ size_t mPageSize;
+ bool mShuttingDown;
+
+ ScopedClose mTriggerFd;
+ ScopedClose mShutdownPipeRead;
+ ScopedClose mShutdownPipeWrite;
+};
+
+NS_IMPL_ISUPPORTS(MemoryPressureWatcher, nsIRunnable, nsIObserver);
+
+} // namespace
+
+namespace mozilla {
+
+void
+InitGonkMemoryPressureMonitoring()
+{
+ // memoryPressureWatcher is held alive by the observer service.
+ RefPtr<MemoryPressureWatcher> memoryPressureWatcher =
+ new MemoryPressureWatcher();
+ NS_ENSURE_SUCCESS_VOID(memoryPressureWatcher->Init());
+
+ nsCOMPtr<nsIThread> thread;
+ NS_NewNamedThread("MemoryPressure", getter_AddRefs(thread),
+ memoryPressureWatcher);
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/GonkMemoryPressureMonitoring.h b/widget/gonk/GonkMemoryPressureMonitoring.h
new file mode 100644
index 000000000..4d5149cd6
--- /dev/null
+++ b/widget/gonk/GonkMemoryPressureMonitoring.h
@@ -0,0 +1,14 @@
+/* -*- 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_GonkMemoryPressureMonitoring_h_
+#define mozilla_GonkMemoryPressureMonitoring_h_
+
+namespace mozilla {
+void InitGonkMemoryPressureMonitoring();
+}
+
+#endif /* mozilla_GonkMemoryPressureMonitoring_h_ */
diff --git a/widget/gonk/GonkPermission.cpp b/widget/gonk/GonkPermission.cpp
new file mode 100644
index 000000000..8ebc43de8
--- /dev/null
+++ b/widget/gonk/GonkPermission.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GonkPermission.h"
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <binder/IPermissionController.h>
+
+#ifndef HAVE_ANDROID_OS
+#define HAVE_ANDROID_OS 1
+#endif
+#include <private/android_filesystem_config.h>
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
+#include "nsThreadUtils.h"
+
+#undef LOG
+#include <android/log.h>
+#undef ALOGE
+#define ALOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "gonkperm" , ## args)
+
+using namespace android;
+using namespace mozilla;
+
+// Checking permissions needs to happen on the main thread, but the
+// binder callback is called on a special binder thread, so we use
+// this runnable for that.
+class GonkPermissionChecker : public Runnable {
+ int32_t mPid;
+ bool mCanUseCamera;
+
+ explicit GonkPermissionChecker(int32_t pid)
+ : mPid(pid)
+ , mCanUseCamera(false)
+ {
+ }
+
+public:
+ static already_AddRefed<GonkPermissionChecker> Inspect(int32_t pid)
+ {
+ RefPtr<GonkPermissionChecker> that = new GonkPermissionChecker(pid);
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ MOZ_ASSERT(mainThread);
+ SyncRunnable::DispatchToThread(mainThread, that);
+ return that.forget();
+ }
+
+ bool CanUseCamera()
+ {
+ return mCanUseCamera;
+ }
+
+ NS_IMETHOD Run();
+};
+
+NS_IMETHODIMP
+GonkPermissionChecker::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Find our ContentParent.
+ dom::ContentParent *contentParent = nullptr;
+ {
+ nsTArray<dom::ContentParent*> parents;
+ dom::ContentParent::GetAll(parents);
+ for (uint32_t i = 0; i < parents.Length(); ++i) {
+ if (parents[i]->Pid() == mPid) {
+ contentParent = parents[i];
+ break;
+ }
+ }
+ }
+ if (!contentParent) {
+ ALOGE("pid=%d denied: can't find ContentParent", mPid);
+ return NS_OK;
+ }
+
+ // Now iterate its apps...
+ const ManagedContainer<dom::PBrowserParent>& browsers =
+ contentParent->ManagedPBrowserParent();
+ for (auto iter = browsers.ConstIter(); !iter.Done(); iter.Next()) {
+ dom::TabParent *tabParent =
+ static_cast<dom::TabParent*>(iter.Get()->GetKey());
+ nsCOMPtr<mozIApplication> mozApp = tabParent->GetOwnOrContainingApp();
+ if (!mozApp) {
+ continue;
+ }
+
+ // ...and check if any of them has camera access.
+ bool appCanUseCamera;
+ nsresult rv = mozApp->HasPermission("camera", &appCanUseCamera);
+ if (NS_SUCCEEDED(rv) && appCanUseCamera) {
+ mCanUseCamera = true;
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+bool
+GonkPermissionService::checkPermission(const String16& permission, int32_t pid,
+ int32_t uid)
+{
+ // root can do anything.
+ if (0 == uid) {
+ return true;
+ }
+
+ String8 perm8(permission);
+
+ // Some ril implementations need android.permission.MODIFY_AUDIO_SETTINGS
+ if ((uid == AID_SYSTEM || uid == AID_RADIO || uid == AID_BLUETOOTH) &&
+ perm8 == "android.permission.MODIFY_AUDIO_SETTINGS") {
+ return true;
+ }
+
+ // No other permissions apply to non-app processes.
+ if (uid < AID_APP) {
+ ALOGE("%s for pid=%d,uid=%d denied: not an app",
+ String8(permission).string(), pid, uid);
+ return false;
+ }
+
+ // Only these permissions can be granted to apps through this service.
+ if (perm8 != "android.permission.CAMERA" &&
+ perm8 != "android.permission.RECORD_AUDIO") {
+ ALOGE("%s for pid=%d,uid=%d denied: unsupported permission",
+ String8(permission).string(), pid, uid);
+ return false;
+ }
+
+ // Users granted the permission through a prompt dialog.
+ // Before permission managment of gUM is done, app cannot remember the
+ // permission.
+ PermissionGrant permGrant(perm8.string(), pid);
+ if (nsTArray<PermissionGrant>::NoIndex != mGrantArray.IndexOf(permGrant)) {
+ mGrantArray.RemoveElement(permGrant);
+ return true;
+ }
+
+ // Camera/audio record permissions are allowed for apps with the
+ // "camera" permission.
+ RefPtr<GonkPermissionChecker> checker =
+ GonkPermissionChecker::Inspect(pid);
+ bool canUseCamera = checker->CanUseCamera();
+ if (!canUseCamera) {
+ ALOGE("%s for pid=%d,uid=%d denied: not granted by user or app manifest",
+ String8(permission).string(), pid, uid);
+ }
+ return canUseCamera;
+}
+
+static GonkPermissionService* gGonkPermissionService = NULL;
+
+/* static */
+void
+GonkPermissionService::instantiate()
+{
+ defaultServiceManager()->addService(String16(getServiceName()),
+ GetInstance());
+}
+
+/* static */
+GonkPermissionService*
+GonkPermissionService::GetInstance()
+{
+ if (!gGonkPermissionService) {
+ gGonkPermissionService = new GonkPermissionService();
+ }
+ return gGonkPermissionService;
+}
+
+void
+GonkPermissionService::addGrantInfo(const char* permission, int32_t pid)
+{
+ mGrantArray.AppendElement(PermissionGrant(permission, pid));
+}
diff --git a/widget/gonk/GonkPermission.h b/widget/gonk/GonkPermission.h
new file mode 100644
index 000000000..d34fcd8ac
--- /dev/null
+++ b/widget/gonk/GonkPermission.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef GONKPERMISSION_H
+#define GONKPERMISSION_H
+
+#include <binder/BinderService.h>
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+class PermissionGrant
+{
+public:
+ PermissionGrant(const char* perm, int32_t p) : mPid(p)
+ {
+ mPermission.Assign(perm);
+ }
+
+ PermissionGrant(const nsACString& permission, int32_t pid) : mPid(pid),
+ mPermission(permission)
+ {
+ }
+
+ bool operator==(const PermissionGrant& other) const
+ {
+ return (mPid == other.pid() && mPermission.Equals(other.permission()));
+ }
+
+ int32_t pid() const
+ {
+ return mPid;
+ }
+
+ const nsACString& permission() const
+ {
+ return mPermission;
+ }
+
+private:
+ int32_t mPid;
+ nsCString mPermission;
+};
+
+class PermissionGrant;
+
+class GonkPermissionService :
+ public android::BinderService<GonkPermissionService>,
+ public android::BnPermissionController
+{
+public:
+ virtual ~GonkPermissionService() {}
+ static GonkPermissionService* GetInstance();
+ static const char *getServiceName() {
+ return "permission";
+ }
+
+ static void instantiate();
+
+ virtual android::status_t dump(int fd, const android::Vector<android::String16>& args) {
+ return android::NO_ERROR;
+ }
+ virtual bool checkPermission(const android::String16& permission, int32_t pid,
+ int32_t uid);
+
+ void addGrantInfo(const char* permission, int32_t pid);
+private:
+ GonkPermissionService(): android::BnPermissionController() {}
+ nsTArray<PermissionGrant> mGrantArray;
+};
+
+} // namespace mozilla
+
+#endif // GONKPERMISSION_H
diff --git a/widget/gonk/HwcComposer2D.cpp b/widget/gonk/HwcComposer2D.cpp
new file mode 100644
index 000000000..6b4c7a1cc
--- /dev/null
+++ b/widget/gonk/HwcComposer2D.cpp
@@ -0,0 +1,971 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2012, 2013 The Linux Foundation. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+#include <string.h>
+
+#include "gfxPrefs.h"
+#include "ImageLayers.h"
+#include "libdisplay/GonkDisplay.h"
+#include "HwcComposer2D.h"
+#include "LayerScope.h"
+#include "Units.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/layers/PLayerTransaction.h"
+#include "mozilla/layers/ShadowLayerUtilsGralloc.h"
+#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL
+#include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+#include "cutils/properties.h"
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "VsyncSource.h"
+#include "nsScreenManagerGonk.h"
+#include "nsWindow.h"
+
+#if ANDROID_VERSION >= 17
+#include "libdisplay/DisplaySurface.h"
+#endif
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "HWComposer"
+
+/*
+ * By default the debug message of hwcomposer (LOG_DEBUG level) are undefined,
+ * but can be enabled by uncommenting HWC_DEBUG below.
+ */
+//#define HWC_DEBUG
+
+#ifdef HWC_DEBUG
+#define LOGD(args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, ## args)
+#else
+#define LOGD(args...) ((void)0)
+#endif
+
+#define LOGI(args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, ## args)
+
+#define LAYER_COUNT_INCREMENTS 5
+
+using namespace android;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace mozilla {
+
+static void
+HookInvalidate(const struct hwc_procs* aProcs)
+{
+ HwcComposer2D::GetInstance()->Invalidate();
+}
+
+static void
+HookVsync(const struct hwc_procs* aProcs, int aDisplay,
+ int64_t aTimestamp)
+{
+ HwcComposer2D::GetInstance()->Vsync(aDisplay, aTimestamp);
+}
+
+static void
+HookHotplug(const struct hwc_procs* aProcs, int aDisplay,
+ int aConnected)
+{
+ HwcComposer2D::GetInstance()->Hotplug(aDisplay, aConnected);
+}
+
+static StaticRefPtr<HwcComposer2D> sInstance;
+
+HwcComposer2D::HwcComposer2D()
+ : mList(nullptr)
+ , mMaxLayerCount(0)
+ , mColorFill(false)
+ , mRBSwapSupport(false)
+ , mPrepared(false)
+ , mHasHWVsync(false)
+ , mLock("mozilla.HwcComposer2D.mLock")
+{
+ mHal = HwcHALBase::CreateHwcHAL();
+ if (!mHal->HasHwc()) {
+ LOGD("no hwc support");
+ return;
+ }
+
+ RegisterHwcEventCallback();
+
+ nsIntSize screenSize;
+
+ GonkDisplay::NativeData data = GetGonkDisplay()->GetNativeData(GonkDisplay::DISPLAY_PRIMARY);
+ ANativeWindow *win = data.mNativeWindow.get();
+ win->query(win, NATIVE_WINDOW_WIDTH, &screenSize.width);
+ win->query(win, NATIVE_WINDOW_HEIGHT, &screenSize.height);
+ mScreenRect = gfx::IntRect(gfx::IntPoint(0, 0), screenSize);
+
+ mColorFill = mHal->Query(HwcHALBase::QueryType::COLOR_FILL);
+ mRBSwapSupport = mHal->Query(HwcHALBase::QueryType::RB_SWAP);
+}
+
+HwcComposer2D::~HwcComposer2D()
+{
+ free(mList);
+}
+
+HwcComposer2D*
+HwcComposer2D::GetInstance()
+{
+ if (!sInstance) {
+#ifdef HWC_DEBUG
+ // Make sure only create once
+ static int timesCreated = 0;
+ ++timesCreated;
+ MOZ_ASSERT(timesCreated == 1);
+#endif
+ LOGI("Creating new instance");
+ sInstance = new HwcComposer2D();
+
+ // If anyone uses the compositor thread to create HwcComposer2D,
+ // we just skip this function.
+ // If ClearOnShutdown() can handle objects in other threads
+ // in the future, we can remove this check.
+ if (NS_IsMainThread()) {
+ // If we create HwcComposer2D by the main thread, we can use
+ // ClearOnShutdown() to make sure it will be nullified properly.
+ ClearOnShutdown(&sInstance);
+ }
+ }
+ return sInstance;
+}
+
+bool
+HwcComposer2D::EnableVsync(bool aEnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mHasHWVsync) {
+ return false;
+ }
+ return mHal->EnableVsync(aEnable) && aEnable;
+}
+
+bool
+HwcComposer2D::RegisterHwcEventCallback()
+{
+ const HwcHALProcs_t cHWCProcs = {
+ &HookInvalidate, // 1st: void (*invalidate)(...)
+ &HookVsync, // 2nd: void (*vsync)(...)
+ &HookHotplug // 3rd: void (*hotplug)(...)
+ };
+ mHasHWVsync = mHal->RegisterHwcEventCallback(cHWCProcs);
+ return mHasHWVsync;
+}
+
+void
+HwcComposer2D::Vsync(int aDisplay, nsecs_t aVsyncTimestamp)
+{
+ // Only support hardware vsync on kitkat, L and up due to inaccurate timings
+ // with JellyBean.
+#if (ANDROID_VERSION == 19 || ANDROID_VERSION >= 21)
+ TimeStamp vsyncTime = mozilla::TimeStamp::FromSystemTime(aVsyncTimestamp);
+ gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay().NotifyVsync(vsyncTime);
+#else
+ // If this device doesn't support vsync, this function should not be used.
+ MOZ_ASSERT(false);
+#endif
+}
+
+// Called on the "invalidator" thread (run from HAL).
+void
+HwcComposer2D::Invalidate()
+{
+ if (!mHal->HasHwc()) {
+ LOGE("HwcComposer2D::Invalidate failed!");
+ return;
+ }
+
+ MutexAutoLock lock(mLock);
+ if (mCompositorBridgeParent) {
+ mCompositorBridgeParent->ScheduleRenderOnCompositorThread();
+ }
+}
+
+namespace {
+class HotplugEvent : public Runnable {
+public:
+ HotplugEvent(GonkDisplay::DisplayType aType, bool aConnected)
+ : mType(aType)
+ , mConnected(aConnected)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ RefPtr<nsScreenManagerGonk> screenManager =
+ nsScreenManagerGonk::GetInstance();
+ if (mConnected) {
+ screenManager->AddScreen(mType);
+ } else {
+ screenManager->RemoveScreen(mType);
+ }
+ return NS_OK;
+ }
+private:
+ GonkDisplay::DisplayType mType;
+ bool mConnected;
+};
+} // namespace
+
+void
+HwcComposer2D::Hotplug(int aDisplay, int aConnected)
+{
+ NS_DispatchToMainThread(new HotplugEvent(GonkDisplay::DISPLAY_EXTERNAL,
+ aConnected));
+}
+
+void
+HwcComposer2D::SetCompositorBridgeParent(CompositorBridgeParent* aCompositorBridgeParent)
+{
+ MutexAutoLock lock(mLock);
+ mCompositorBridgeParent = aCompositorBridgeParent;
+}
+
+bool
+HwcComposer2D::ReallocLayerList()
+{
+ int size = sizeof(HwcList) +
+ ((mMaxLayerCount + LAYER_COUNT_INCREMENTS) * sizeof(HwcLayer));
+
+ HwcList* listrealloc = (HwcList*)realloc(mList, size);
+
+ if (!listrealloc) {
+ return false;
+ }
+
+ if (!mList) {
+ //first alloc, initialize
+ listrealloc->numHwLayers = 0;
+ listrealloc->flags = 0;
+ }
+
+ mList = listrealloc;
+ mMaxLayerCount += LAYER_COUNT_INCREMENTS;
+ return true;
+}
+
+bool
+HwcComposer2D::PrepareLayerList(Layer* aLayer,
+ const nsIntRect& aClip,
+ const Matrix& aParentTransform,
+ bool aFindSidebandStreams)
+{
+ // NB: we fall off this path whenever there are container layers
+ // that require intermediate surfaces. That means all the
+ // GetEffective*() coordinates are relative to the framebuffer.
+
+ bool fillColor = false;
+
+ const nsIntRegion visibleRegion = aLayer->GetLocalVisibleRegion().ToUnknownRegion();
+ if (visibleRegion.IsEmpty()) {
+ return true;
+ }
+
+ uint8_t opacity = std::min(0xFF, (int)(aLayer->GetEffectiveOpacity() * 256.0));
+ if (opacity == 0) {
+ LOGD("%s Layer has zero opacity; skipping", aLayer->Name());
+ return true;
+ }
+
+ if (!mHal->SupportTransparency() && opacity < 0xFF && !aFindSidebandStreams) {
+ LOGD("%s Layer has planar semitransparency which is unsupported by hwcomposer", aLayer->Name());
+ return false;
+ }
+
+ if (aLayer->GetMaskLayer() && !aFindSidebandStreams) {
+ LOGD("%s Layer has MaskLayer which is unsupported by hwcomposer", aLayer->Name());
+ return false;
+ }
+
+ nsIntRect clip;
+ nsIntRect layerClip = aLayer->GetLocalClipRect().valueOr(ParentLayerIntRect()).ToUnknownRect();
+ nsIntRect* layerClipPtr = aLayer->GetLocalClipRect() ? &layerClip : nullptr;
+ if (!HwcUtils::CalculateClipRect(aParentTransform,
+ layerClipPtr,
+ aClip,
+ &clip))
+ {
+ LOGD("%s Clip rect is empty. Skip layer", aLayer->Name());
+ return true;
+ }
+
+ // HWC supports only the following 2D transformations:
+ //
+ // Scaling via the sourceCrop and displayFrame in HwcLayer
+ // Translation via the sourceCrop and displayFrame in HwcLayer
+ // Rotation (in square angles only) via the HWC_TRANSFORM_ROT_* flags
+ // Reflection (horizontal and vertical) via the HWC_TRANSFORM_FLIP_* flags
+ //
+ // A 2D transform with PreservesAxisAlignedRectangles() has all the attributes
+ // above
+ Matrix layerTransform;
+ if (!aLayer->GetEffectiveTransform().Is2D(&layerTransform) ||
+ !layerTransform.PreservesAxisAlignedRectangles()) {
+ LOGD("Layer EffectiveTransform has a 3D transform or a non-square angle rotation");
+ return false;
+ }
+
+ Matrix layerBufferTransform;
+ if (!aLayer->GetEffectiveTransformForBuffer().Is2D(&layerBufferTransform) ||
+ !layerBufferTransform.PreservesAxisAlignedRectangles()) {
+ LOGD("Layer EffectiveTransformForBuffer has a 3D transform or a non-square angle rotation");
+ return false;
+ }
+
+ if (ContainerLayer* container = aLayer->AsContainerLayer()) {
+ if (container->UseIntermediateSurface() && !aFindSidebandStreams) {
+ LOGD("Container layer needs intermediate surface");
+ return false;
+ }
+ AutoTArray<Layer*, 12> children;
+ container->SortChildrenBy3DZOrder(children);
+
+ for (uint32_t i = 0; i < children.Length(); i++) {
+ if (!PrepareLayerList(children[i], clip, layerTransform, aFindSidebandStreams) &&
+ !aFindSidebandStreams) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ LayerRenderState state = aLayer->GetRenderState();
+
+#if ANDROID_VERSION >= 21
+ if (!state.GetGrallocBuffer() && !state.GetSidebandStream().IsValid()) {
+#else
+ if (!state.GetGrallocBuffer()) {
+#endif
+ if (aLayer->AsColorLayer() && mColorFill) {
+ fillColor = true;
+ } else {
+ LOGD("%s Layer doesn't have a gralloc buffer", aLayer->Name());
+ return false;
+ }
+ }
+
+ nsIntRect visibleRect = visibleRegion.GetBounds();
+
+ nsIntRect bufferRect;
+ if (fillColor) {
+ bufferRect = nsIntRect(visibleRect);
+ } else {
+ nsIntRect layerRect;
+ if (state.mHasOwnOffset) {
+ bufferRect = nsIntRect(state.mOffset.x, state.mOffset.y,
+ state.mSize.width, state.mSize.height);
+ layerRect = bufferRect;
+ } else {
+ //Since the buffer doesn't have its own offset, assign the whole
+ //surface size as its buffer bounds
+ bufferRect = nsIntRect(0, 0, state.mSize.width, state.mSize.height);
+ layerRect = bufferRect;
+ if (aLayer->GetType() == Layer::TYPE_IMAGE) {
+ ImageLayer* imageLayer = static_cast<ImageLayer*>(aLayer);
+ if(imageLayer->GetScaleMode() != ScaleMode::SCALE_NONE) {
+ layerRect = nsIntRect(0, 0, imageLayer->GetScaleToSize().width, imageLayer->GetScaleToSize().height);
+ }
+ }
+ }
+ // In some cases the visible rect assigned to the layer can be larger
+ // than the layer's surface, e.g., an ImageLayer with a small Image
+ // in it.
+ visibleRect.IntersectRect(visibleRect, layerRect);
+ }
+
+ // Buffer rotation is not to be confused with the angled rotation done by a transform matrix
+ // It's a fancy PaintedLayer feature used for scrolling
+ if (state.BufferRotated()) {
+ LOGD("%s Layer has a rotated buffer", aLayer->Name());
+ return false;
+ }
+
+ const bool needsYFlip = state.OriginBottomLeft() ? true
+ : false;
+
+ hwc_rect_t sourceCrop, displayFrame;
+ if(!HwcUtils::PrepareLayerRects(visibleRect,
+ layerTransform,
+ layerBufferTransform,
+ clip,
+ bufferRect,
+ needsYFlip,
+ &(sourceCrop),
+ &(displayFrame)))
+ {
+ return true;
+ }
+
+ // OK! We can compose this layer with hwc.
+ int current = mList ? mList->numHwLayers : 0;
+
+ // Do not compose any layer below full-screen Opaque layer
+ // Note: It can be generalized to non-fullscreen Opaque layers.
+ bool isOpaque = opacity == 0xFF &&
+ (state.mFlags & LayerRenderStateFlags::OPAQUE);
+ // Currently we perform opacity calculation using the *bounds* of the layer.
+ // We can only make this assumption if we're not dealing with a complex visible region.
+ bool isSimpleVisibleRegion = visibleRegion.Contains(visibleRect);
+ if (current && isOpaque && isSimpleVisibleRegion) {
+ nsIntRect displayRect = nsIntRect(displayFrame.left, displayFrame.top,
+ displayFrame.right - displayFrame.left, displayFrame.bottom - displayFrame.top);
+ if (displayRect.Contains(mScreenRect)) {
+ // In z-order, all previous layers are below
+ // the current layer. We can ignore them now.
+ mList->numHwLayers = current = 0;
+ mHwcLayerMap.Clear();
+ }
+ }
+
+ if (!mList || current >= mMaxLayerCount) {
+ if (!ReallocLayerList() || current >= mMaxLayerCount) {
+ LOGE("PrepareLayerList failed! Could not increase the maximum layer count");
+ return false;
+ }
+ }
+
+ HwcLayer& hwcLayer = mList->hwLayers[current];
+ hwcLayer.displayFrame = displayFrame;
+ mHal->SetCrop(hwcLayer, sourceCrop);
+ buffer_handle_t handle = nullptr;
+#if ANDROID_VERSION >= 21
+ if (state.GetSidebandStream().IsValid()) {
+ handle = state.GetSidebandStream().GetRawNativeHandle();
+ } else if (state.GetGrallocBuffer()) {
+ handle = state.GetGrallocBuffer()->getNativeBuffer()->handle;
+ }
+#else
+ if (state.GetGrallocBuffer()) {
+ handle = state.GetGrallocBuffer()->getNativeBuffer()->handle;
+ }
+#endif
+ hwcLayer.handle = handle;
+
+ hwcLayer.flags = 0;
+ hwcLayer.hints = 0;
+ hwcLayer.blending = isOpaque ? HWC_BLENDING_NONE : HWC_BLENDING_PREMULT;
+#if ANDROID_VERSION >= 17
+ hwcLayer.compositionType = HWC_FRAMEBUFFER;
+#if ANDROID_VERSION >= 21
+ if (state.GetSidebandStream().IsValid()) {
+ hwcLayer.compositionType = HWC_SIDEBAND;
+ }
+#endif
+ hwcLayer.acquireFenceFd = -1;
+ hwcLayer.releaseFenceFd = -1;
+#if ANDROID_VERSION >= 18
+ hwcLayer.planeAlpha = opacity;
+#endif
+#else
+ hwcLayer.compositionType = HwcUtils::HWC_USE_COPYBIT;
+#endif
+
+ if (!fillColor) {
+ if (state.FormatRBSwapped()) {
+ if (!mRBSwapSupport) {
+ LOGD("No R/B swap support in H/W Composer");
+ return false;
+ }
+ hwcLayer.flags |= HwcUtils::HWC_FORMAT_RB_SWAP;
+ }
+
+ // Translation and scaling have been addressed in PrepareLayerRects().
+ // Given the above and that we checked for PreservesAxisAlignedRectangles()
+ // the only possible transformations left to address are
+ // square angle rotation and horizontal/vertical reflection.
+ //
+ // The rotation and reflection permutations total 16 but can be
+ // reduced to 8 transformations after eliminating redundancies.
+ //
+ // All matrices represented here are in the form
+ //
+ // | xx xy |
+ // | yx yy |
+ //
+ // And ignore scaling.
+ //
+ // Reflection is applied before rotation
+ gfx::Matrix rotation = layerTransform;
+ // Compute fuzzy zero like PreservesAxisAlignedRectangles()
+ if (fabs(rotation._11) < 1e-6) {
+ if (rotation._21 < 0) {
+ if (rotation._12 > 0) {
+ // 90 degree rotation
+ //
+ // | 0 -1 |
+ // | 1 0 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_90;
+ LOGD("Layer rotated 90 degrees");
+ }
+ else {
+ // Horizontal reflection then 90 degree rotation
+ //
+ // | 0 -1 | | -1 0 | = | 0 -1 |
+ // | 1 0 | | 0 1 | | -1 0 |
+ //
+ // same as vertical reflection then 270 degree rotation
+ //
+ // | 0 1 | | 1 0 | = | 0 -1 |
+ // | -1 0 | | 0 -1 | | -1 0 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_90 | HWC_TRANSFORM_FLIP_H;
+ LOGD("Layer vertically reflected then rotated 270 degrees");
+ }
+ } else {
+ if (rotation._12 < 0) {
+ // 270 degree rotation
+ //
+ // | 0 1 |
+ // | -1 0 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_270;
+ LOGD("Layer rotated 270 degrees");
+ }
+ else {
+ // Vertical reflection then 90 degree rotation
+ //
+ // | 0 1 | | -1 0 | = | 0 1 |
+ // | -1 0 | | 0 1 | | 1 0 |
+ //
+ // Same as horizontal reflection then 270 degree rotation
+ //
+ // | 0 -1 | | 1 0 | = | 0 1 |
+ // | 1 0 | | 0 -1 | | 1 0 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_90 | HWC_TRANSFORM_FLIP_V;
+ LOGD("Layer horizontally reflected then rotated 270 degrees");
+ }
+ }
+ } else if (rotation._11 < 0) {
+ if (rotation._22 > 0) {
+ // Horizontal reflection
+ //
+ // | -1 0 |
+ // | 0 1 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_FLIP_H;
+ LOGD("Layer rotated 180 degrees");
+ }
+ else {
+ // 180 degree rotation
+ //
+ // | -1 0 |
+ // | 0 -1 |
+ //
+ // Same as horizontal and vertical reflection
+ //
+ // | -1 0 | | 1 0 | = | -1 0 |
+ // | 0 1 | | 0 -1 | | 0 -1 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_180;
+ LOGD("Layer rotated 180 degrees");
+ }
+ } else {
+ if (rotation._22 < 0) {
+ // Vertical reflection
+ //
+ // | 1 0 |
+ // | 0 -1 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_FLIP_V;
+ LOGD("Layer rotated 180 degrees");
+ }
+ else {
+ // No rotation or reflection
+ //
+ // | 1 0 |
+ // | 0 1 |
+ //
+ hwcLayer.transform = 0;
+ }
+ }
+
+ const bool needsYFlip = state.OriginBottomLeft() ? true
+ : false;
+
+ if (needsYFlip) {
+ // Invert vertical reflection flag if it was already set
+ hwcLayer.transform ^= HWC_TRANSFORM_FLIP_V;
+ }
+ hwc_region_t region;
+ if (visibleRegion.GetNumRects() > 1) {
+ mVisibleRegions.push_back(HwcUtils::RectVector());
+ HwcUtils::RectVector* visibleRects = &(mVisibleRegions.back());
+ bool isVisible = false;
+ if(!HwcUtils::PrepareVisibleRegion(visibleRegion,
+ layerTransform,
+ layerBufferTransform,
+ clip,
+ bufferRect,
+ visibleRects,
+ isVisible)) {
+ LOGD("A region of layer is too small to be rendered by HWC");
+ return false;
+ }
+ if (!isVisible) {
+ // Layer is not visible, no need to render it
+ return true;
+ }
+ region.numRects = visibleRects->size();
+ region.rects = &((*visibleRects)[0]);
+ } else {
+ region.numRects = 1;
+ region.rects = &(hwcLayer.displayFrame);
+ }
+ hwcLayer.visibleRegionScreen = region;
+ } else {
+ hwcLayer.flags |= HwcUtils::HWC_COLOR_FILL;
+ ColorLayer* colorLayer = aLayer->AsColorLayer();
+ if (colorLayer->GetColor().a < 1.0) {
+ LOGD("Color layer has semitransparency which is unsupported");
+ return false;
+ }
+ hwcLayer.transform = colorLayer->GetColor().ToABGR();
+ }
+
+#if ANDROID_VERSION >= 21
+ if (aFindSidebandStreams && hwcLayer.compositionType == HWC_SIDEBAND) {
+ mCachedSidebandLayers.AppendElement(hwcLayer);
+ }
+#endif
+
+ mHwcLayerMap.AppendElement(static_cast<LayerComposite*>(aLayer->ImplData()));
+ mList->numHwLayers++;
+ return true;
+}
+
+
+#if ANDROID_VERSION >= 17
+bool
+HwcComposer2D::TryHwComposition(nsScreenGonk* aScreen)
+{
+ DisplaySurface* dispSurface = aScreen->GetDisplaySurface();
+
+ if (!(dispSurface && dispSurface->lastHandle)) {
+ LOGD("H/W Composition failed. DispSurface not initialized.");
+ return false;
+ }
+
+ // Add FB layer
+ int idx = mList->numHwLayers++;
+ if (idx >= mMaxLayerCount) {
+ if (!ReallocLayerList() || idx >= mMaxLayerCount) {
+ LOGE("TryHwComposition failed! Could not add FB layer");
+ return false;
+ }
+ }
+
+ Prepare(dispSurface->lastHandle, -1, aScreen);
+
+ /* Possible composition paths, after hwc prepare:
+ 1. GPU Composition
+ 2. BLIT Composition
+ 3. Full OVERLAY Composition
+ 4. Partial OVERLAY Composition (GPU + OVERLAY) */
+
+ bool gpuComposite = false;
+ bool blitComposite = false;
+ bool overlayComposite = true;
+
+ for (int j=0; j < idx; j++) {
+ if (mList->hwLayers[j].compositionType == HWC_FRAMEBUFFER ||
+ mList->hwLayers[j].compositionType == HWC_BLIT) {
+ // Full OVERLAY composition is not possible on this frame
+ // It is either GPU / BLIT / partial OVERLAY composition.
+ overlayComposite = false;
+ break;
+ }
+ }
+
+ if (!overlayComposite) {
+ for (int k=0; k < idx; k++) {
+ switch (mList->hwLayers[k].compositionType) {
+ case HWC_FRAMEBUFFER:
+ gpuComposite = true;
+ break;
+ case HWC_BLIT:
+ blitComposite = true;
+ break;
+#if ANDROID_VERSION >= 21
+ case HWC_SIDEBAND:
+#endif
+ case HWC_OVERLAY: {
+ // HWC will compose HWC_OVERLAY layers in partial
+ // Overlay Composition, set layer composition flag
+ // on mapped LayerComposite to skip GPU composition
+ mHwcLayerMap[k]->SetLayerComposited(true);
+
+ uint8_t opacity = std::min(0xFF, (int)(mHwcLayerMap[k]->GetLayer()->GetEffectiveOpacity() * 256.0));
+ if ((mList->hwLayers[k].hints & HWC_HINT_CLEAR_FB) &&
+ (opacity == 0xFF)) {
+ // Clear visible rect on FB with transparent pixels.
+ hwc_rect_t r = mList->hwLayers[k].displayFrame;
+ mHwcLayerMap[k]->SetClearRect(nsIntRect(r.left, r.top,
+ r.right - r.left,
+ r.bottom - r.top));
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (gpuComposite) {
+ // GPU or partial OVERLAY Composition
+ return false;
+ } else if (blitComposite) {
+ // BLIT Composition, flip DispSurface target
+ GetGonkDisplay()->UpdateDispSurface(aScreen->GetEGLDisplay(), aScreen->GetEGLSurface());
+ DisplaySurface* dispSurface = aScreen->GetDisplaySurface();
+ if (!dispSurface) {
+ LOGE("H/W Composition failed. NULL DispSurface.");
+ return false;
+ }
+ mList->hwLayers[idx].handle = dispSurface->lastHandle;
+ mList->hwLayers[idx].acquireFenceFd = dispSurface->GetPrevDispAcquireFd();
+ }
+ }
+
+ // BLIT or full OVERLAY Composition
+ return Commit(aScreen);
+}
+
+bool
+HwcComposer2D::Render(nsIWidget* aWidget)
+{
+ nsScreenGonk* screen = static_cast<nsWindow*>(aWidget)->GetScreen();
+
+ // HWC module does not exist or mList is not created yet.
+ if (!mHal->HasHwc() || !mList) {
+ return GetGonkDisplay()->SwapBuffers(screen->GetEGLDisplay(), screen->GetEGLSurface());
+ } else if (!mList && !ReallocLayerList()) {
+ LOGE("Cannot realloc layer list");
+ return false;
+ }
+
+ DisplaySurface* dispSurface = screen->GetDisplaySurface();
+ if (!dispSurface) {
+ LOGE("H/W Composition failed. DispSurface not initialized.");
+ return false;
+ }
+
+ if (mPrepared) {
+ // No mHwc prepare, if already prepared in current draw cycle
+ mList->hwLayers[mList->numHwLayers - 1].handle = dispSurface->lastHandle;
+ mList->hwLayers[mList->numHwLayers - 1].acquireFenceFd = dispSurface->GetPrevDispAcquireFd();
+ } else {
+ // Update screen rect to handle a case that TryRenderWithHwc() is not called.
+ mScreenRect = screen->GetNaturalBounds().ToUnknownRect();
+
+ mList->flags = HWC_GEOMETRY_CHANGED;
+ mList->numHwLayers = 2;
+ mList->hwLayers[0].hints = 0;
+ mList->hwLayers[0].compositionType = HWC_FRAMEBUFFER;
+ mList->hwLayers[0].flags = HWC_SKIP_LAYER;
+ mList->hwLayers[0].backgroundColor = {0};
+ mList->hwLayers[0].acquireFenceFd = -1;
+ mList->hwLayers[0].releaseFenceFd = -1;
+ mList->hwLayers[0].displayFrame = {0, 0, mScreenRect.width, mScreenRect.height};
+
+#if ANDROID_VERSION >= 21
+ // Prepare layers for sideband streams
+ const uint32_t len = mCachedSidebandLayers.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ ++mList->numHwLayers;
+ mList->hwLayers[i+1] = mCachedSidebandLayers[i];
+ }
+#endif
+ Prepare(dispSurface->lastHandle, dispSurface->GetPrevDispAcquireFd(), screen);
+ }
+
+ // GPU or partial HWC Composition
+ return Commit(screen);
+}
+
+void
+HwcComposer2D::Prepare(buffer_handle_t dispHandle, int fence, nsScreenGonk* screen)
+{
+ if (mPrepared) {
+ LOGE("Multiple hwc prepare calls!");
+ }
+ hwc_rect_t dispRect = {0, 0, mScreenRect.width, mScreenRect.height};
+ mHal->Prepare(mList, screen->GetDisplayType(), dispRect, dispHandle, fence);
+ mPrepared = true;
+}
+
+bool
+HwcComposer2D::Commit(nsScreenGonk* aScreen)
+{
+ for (uint32_t j=0; j < (mList->numHwLayers - 1); j++) {
+ mList->hwLayers[j].acquireFenceFd = -1;
+ if (mHwcLayerMap.IsEmpty() ||
+ (mList->hwLayers[j].compositionType == HWC_FRAMEBUFFER)) {
+ continue;
+ }
+ LayerRenderState state = mHwcLayerMap[j]->GetLayer()->GetRenderState();
+ if (!state.mTexture) {
+ continue;
+ }
+ FenceHandle fence = state.mTexture->GetAndResetAcquireFenceHandle();
+ if (fence.IsValid()) {
+ RefPtr<FenceHandle::FdObj> fdObj = fence.GetAndResetFdObj();
+ mList->hwLayers[j].acquireFenceFd = fdObj->GetAndResetFd();
+ }
+ }
+
+ int err = mHal->Set(mList, aScreen->GetDisplayType());
+
+ mPrevRetireFence.TransferToAnotherFenceHandle(mPrevDisplayFence);
+
+ for (uint32_t j=0; j < (mList->numHwLayers - 1); j++) {
+ if (mList->hwLayers[j].releaseFenceFd >= 0) {
+ int fd = mList->hwLayers[j].releaseFenceFd;
+ mList->hwLayers[j].releaseFenceFd = -1;
+ RefPtr<FenceHandle::FdObj> fdObj = new FenceHandle::FdObj(fd);
+ FenceHandle fence(fdObj);
+
+ LayerRenderState state = mHwcLayerMap[j]->GetLayer()->GetRenderState();
+ if (!state.mTexture) {
+ continue;
+ }
+ state.mTexture->SetReleaseFenceHandle(fence);
+ }
+ }
+
+ if (mList->retireFenceFd >= 0) {
+ mPrevRetireFence = FenceHandle(new FenceHandle::FdObj(mList->retireFenceFd));
+ }
+
+ // Set DisplaySurface layer fence
+ DisplaySurface* displaySurface = aScreen->GetDisplaySurface();
+ displaySurface->setReleaseFenceFd(mList->hwLayers[mList->numHwLayers - 1].releaseFenceFd);
+ mList->hwLayers[mList->numHwLayers - 1].releaseFenceFd = -1;
+
+ mPrepared = false;
+ return !err;
+}
+#else
+bool
+HwcComposer2D::TryHwComposition(nsScreenGonk* aScreen)
+{
+ mHal->SetEGLInfo(aScreen->GetEGLDisplay(), aScreen->GetEGLSurface());
+ return !mHal->Set(mList, aScreen->GetDisplayType());
+}
+
+bool
+HwcComposer2D::Render(nsIWidget* aWidget)
+{
+ nsScreenGonk* screen = static_cast<nsWindow*>(aWidget)->GetScreen();
+ return GetGonkDisplay()->SwapBuffers(screen->GetEGLDisplay(), screen->GetEGLSurface());
+}
+#endif
+
+bool
+HwcComposer2D::TryRenderWithHwc(Layer* aRoot,
+ nsIWidget* aWidget,
+ bool aGeometryChanged,
+ bool aHasImageHostOverlays)
+{
+ if (!mHal->HasHwc()) {
+ return false;
+ }
+
+ nsScreenGonk* screen = static_cast<nsWindow*>(aWidget)->GetScreen();
+
+ if (mList) {
+ mList->flags = mHal->GetGeometryChangedFlag(aGeometryChanged);
+ mList->numHwLayers = 0;
+ mHwcLayerMap.Clear();
+ }
+
+ if (mPrepared) {
+ mHal->ResetHwc();
+ mPrepared = false;
+ }
+
+ // XXX: The clear() below means all rect vectors will be have to be
+ // reallocated. We may want to avoid this if possible
+ mVisibleRegions.clear();
+
+ mScreenRect = screen->GetNaturalBounds().ToUnknownRect();
+ MOZ_ASSERT(mHwcLayerMap.IsEmpty());
+ mCachedSidebandLayers.Clear();
+ if (!PrepareLayerList(aRoot,
+ mScreenRect,
+ gfx::Matrix(),
+ /* aFindSidebandStreams */ false))
+ {
+ mHwcLayerMap.Clear();
+ LOGD("Render aborted. Fallback to GPU Composition");
+ if (aHasImageHostOverlays) {
+ LOGD("Prepare layers of SidebandStreams");
+ // Failed to create a layer list for hwc. But we need the list
+ // only for handling sideband streams. Traverse layer tree without
+ // some early returns to make sure we can find all the layers.
+ // It is the best wrong thing that we can do.
+ PrepareLayerList(aRoot,
+ mScreenRect,
+ gfx::Matrix(),
+ /* aFindSidebandStreams */ true);
+ // Reset mPrepared to false, since we already fell back to
+ // gpu composition.
+ mPrepared = false;
+ }
+ return false;
+ }
+
+ // Send data to LayerScope for debugging
+ SendtoLayerScope();
+
+ if (!TryHwComposition(screen)) {
+ LOGD("Full HWC Composition failed. Fallback to GPU Composition or partial OVERLAY Composition");
+ LayerScope::CleanLayer();
+ return false;
+ }
+
+ LOGD("Frame rendered");
+ return true;
+}
+
+void
+HwcComposer2D::SendtoLayerScope()
+{
+ if (!LayerScope::CheckSendable()) {
+ return;
+ }
+
+ const int len = mList->numHwLayers;
+ for (int i = 0; i < len; ++i) {
+ LayerComposite* layer = mHwcLayerMap[i];
+ const hwc_rect_t r = mList->hwLayers[i].displayFrame;
+ LayerScope::SendLayer(layer, r.right - r.left, r.bottom - r.top);
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/HwcComposer2D.h b/widget/gonk/HwcComposer2D.h
new file mode 100644
index 000000000..56c1d1ec1
--- /dev/null
+++ b/widget/gonk/HwcComposer2D.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_HwcComposer2D
+#define mozilla_HwcComposer2D
+
+#include "Composer2D.h"
+#include "hwchal/HwcHALBase.h" // for HwcHAL
+#include "HwcUtils.h" // for RectVector
+#include "Layers.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/layers/FenceUtils.h" // for FenceHandle
+#include "mozilla/UniquePtr.h" // for HwcHAL
+
+#include <vector>
+#include <list>
+
+#include <utils/Timers.h>
+
+class nsScreenGonk;
+
+namespace mozilla {
+
+namespace gl {
+ class GLContext;
+}
+
+namespace layers {
+class CompositorBridgeParent;
+class Layer;
+}
+
+/*
+ * HwcComposer2D provides a way for gecko to render frames
+ * using hwcomposer.h in the AOSP HAL.
+ *
+ * hwcomposer.h defines an interface for display composition
+ * using dedicated hardware. This hardware is usually faster
+ * or more power efficient than the GPU. However, in exchange
+ * for better performance, generality has to be sacrificed:
+ * no 3d transforms, no intermediate surfaces, no special shader effects,
+ * and loss of other goodies depending on the platform.
+ *
+ * In general, when hwc is enabled gecko tries to compose
+ * its frames using HwcComposer2D first. Then if HwcComposer2D is
+ * unable to compose a frame then it falls back to compose it
+ * using the GPU with OpenGL.
+ *
+ */
+class HwcComposer2D : public mozilla::layers::Composer2D {
+public:
+ HwcComposer2D();
+ virtual ~HwcComposer2D();
+
+ static HwcComposer2D* GetInstance();
+
+ // Returns TRUE if the container has been succesfully rendered
+ // Returns FALSE if the container cannot be fully rendered
+ // by this composer so nothing was rendered at all
+ virtual bool TryRenderWithHwc(layers::Layer* aRoot,
+ nsIWidget* aWidget,
+ bool aGeometryChanged,
+ bool aHasImageHostOverlays) override;
+
+ virtual bool Render(nsIWidget* aWidget) override;
+
+ virtual bool HasHwc() override { return mHal->HasHwc(); }
+
+ bool EnableVsync(bool aEnable);
+ bool RegisterHwcEventCallback();
+ void Vsync(int aDisplay, int64_t aTimestamp);
+ void Invalidate();
+ void Hotplug(int aDisplay, int aConnected);
+ void SetCompositorBridgeParent(layers::CompositorBridgeParent* aCompositorBridgeParent);
+
+private:
+ void Reset();
+ void Prepare(buffer_handle_t dispHandle, int fence, nsScreenGonk* screen);
+ bool Commit(nsScreenGonk* aScreen);
+ bool TryHwComposition(nsScreenGonk* aScreen);
+ bool ReallocLayerList();
+ bool PrepareLayerList(layers::Layer* aContainer, const nsIntRect& aClip,
+ const gfx::Matrix& aParentTransform,
+ bool aFindSidebandStreams);
+ void SendtoLayerScope();
+
+ UniquePtr<HwcHALBase> mHal;
+ HwcList* mList;
+ nsIntRect mScreenRect;
+ int mMaxLayerCount;
+ bool mColorFill;
+ bool mRBSwapSupport;
+ //Holds all the dynamically allocated RectVectors needed
+ //to render the current frame
+ std::list<HwcUtils::RectVector> mVisibleRegions;
+ layers::FenceHandle mPrevRetireFence;
+ layers::FenceHandle mPrevDisplayFence;
+ nsTArray<HwcLayer> mCachedSidebandLayers;
+ nsTArray<layers::LayerComposite*> mHwcLayerMap;
+ bool mPrepared;
+ bool mHasHWVsync;
+ layers::CompositorBridgeParent* mCompositorBridgeParent;
+ Mutex mLock;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_HwcComposer2D
diff --git a/widget/gonk/HwcUtils.cpp b/widget/gonk/HwcUtils.cpp
new file mode 100644
index 000000000..a8f030f3c
--- /dev/null
+++ b/widget/gonk/HwcUtils.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2013 The Linux Foundation. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+#include "HwcUtils.h"
+#include "gfxUtils.h"
+#include "gfx2DGlue.h"
+
+#define LOG_TAG "HwcUtils"
+
+#if (LOG_NDEBUG == 0)
+#define LOGD(args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, ## args)
+#else
+#define LOGD(args...) ((void)0)
+#endif
+
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, ## args)
+
+
+namespace mozilla {
+
+/* Utility functions for HwcComposer */
+
+
+
+/* static */ bool
+HwcUtils::PrepareLayerRects(nsIntRect aVisible,
+ const gfx::Matrix& aLayerTransform,
+ const gfx::Matrix& aLayerBufferTransform,
+ nsIntRect aClip, nsIntRect aBufferRect,
+ bool aYFlipped,
+ hwc_rect_t* aSourceCrop, hwc_rect_t* aVisibleRegionScreen) {
+
+ gfxMatrix aTransform = gfx::ThebesMatrix(aLayerTransform);
+ gfxRect visibleRect(ThebesRect(aVisible));
+ gfxRect clip(ThebesRect(aClip));
+ gfxRect visibleRectScreen = aTransform.TransformBounds(visibleRect);
+ // |clip| is guaranteed to be integer
+ visibleRectScreen.IntersectRect(visibleRectScreen, clip);
+
+ if (visibleRectScreen.IsEmpty()) {
+ return false;
+ }
+
+ gfxMatrix inverse = gfx::ThebesMatrix(aLayerBufferTransform);
+ inverse.Invert();
+ gfxRect crop = inverse.TransformBounds(visibleRectScreen);
+
+ //clip to buffer size
+ crop.IntersectRect(crop, ThebesRect(aBufferRect));
+ crop.Round();
+
+ if (crop.IsEmpty()) {
+ return false;
+ }
+
+ //propagate buffer clipping back to visible rect
+ gfxMatrix layerBufferTransform = gfx::ThebesMatrix(aLayerBufferTransform);
+ visibleRectScreen = layerBufferTransform.TransformBounds(crop);
+ visibleRectScreen.Round();
+
+ // Map from layer space to buffer space
+ crop -= aBufferRect.TopLeft();
+ if (aYFlipped) {
+ crop.y = aBufferRect.height - (crop.y + crop.height);
+ }
+
+ aSourceCrop->left = crop.x;
+ aSourceCrop->top = crop.y;
+ aSourceCrop->right = crop.x + crop.width;
+ aSourceCrop->bottom = crop.y + crop.height;
+
+ aVisibleRegionScreen->left = visibleRectScreen.x;
+ aVisibleRegionScreen->top = visibleRectScreen.y;
+ aVisibleRegionScreen->right = visibleRectScreen.x + visibleRectScreen.width;
+ aVisibleRegionScreen->bottom = visibleRectScreen.y + visibleRectScreen.height;
+
+ return true;
+}
+
+/* static */ bool
+HwcUtils::PrepareVisibleRegion(const nsIntRegion& aVisible,
+ const gfx::Matrix& aLayerTransform,
+ const gfx::Matrix& aLayerBufferTransform,
+ nsIntRect aClip, nsIntRect aBufferRect,
+ RectVector* aVisibleRegionScreen,
+ bool& aIsVisible) {
+ const float MIN_SRC_WIDTH = 2.f;
+ const float MIN_SRC_HEIGHT = 2.f;
+
+ gfxMatrix layerTransform = gfx::ThebesMatrix(aLayerTransform);
+ gfxMatrix layerBufferTransform = gfx::ThebesMatrix(aLayerBufferTransform);
+ gfxRect bufferRect =
+ layerBufferTransform.TransformBounds(ThebesRect(aBufferRect));
+ gfxMatrix inverse = gfx::ThebesMatrix(aLayerBufferTransform);
+ inverse.Invert();
+ aIsVisible = false;
+
+ for (auto iter = aVisible.RectIter(); !iter.Done(); iter.Next()) {
+ gfxRect screenRect =
+ layerTransform.TransformBounds(ThebesRect(iter.Get()));
+ screenRect.IntersectRect(screenRect, bufferRect);
+ screenRect.IntersectRect(screenRect, ThebesRect(aClip));
+ screenRect.Round();
+ if (screenRect.IsEmpty()) {
+ continue;
+ }
+
+ hwc_rect_t visibleRectScreen;
+ visibleRectScreen.left = screenRect.x;
+ visibleRectScreen.top = screenRect.y;
+ visibleRectScreen.right = screenRect.XMost();
+ visibleRectScreen.bottom = screenRect.YMost();
+
+ gfxRect srcCrop = inverse.TransformBounds(screenRect);
+ // When src crop is very small, HWC could not render correctly in some cases.
+ // See Bug 1169093
+ if(srcCrop.Width() < MIN_SRC_WIDTH || srcCrop.Height() < MIN_SRC_HEIGHT) {
+ return false;
+ }
+
+ aVisibleRegionScreen->push_back(visibleRectScreen);
+ aIsVisible = true;
+ }
+
+ return true;
+}
+
+/* static */ bool
+HwcUtils::CalculateClipRect(const gfx::Matrix& transform,
+ const nsIntRect* aLayerClip,
+ nsIntRect aParentClip, nsIntRect* aRenderClip) {
+
+ gfxMatrix aTransform = gfx::ThebesMatrix(transform);
+ *aRenderClip = aParentClip;
+
+ if (!aLayerClip) {
+ return true;
+ }
+
+ if (aLayerClip->IsEmpty()) {
+ return false;
+ }
+
+ nsIntRect clip = *aLayerClip;
+
+ gfxRect r = ThebesRect(clip);
+ gfxRect trClip = aTransform.TransformBounds(r);
+ trClip.Round();
+ gfxUtils::GfxRectToIntRect(trClip, &clip);
+
+ aRenderClip->IntersectRect(*aRenderClip, clip);
+ return true;
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/HwcUtils.h b/widget/gonk/HwcUtils.h
new file mode 100644
index 000000000..876ff8e99
--- /dev/null
+++ b/widget/gonk/HwcUtils.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_HwcUtils
+#define mozilla_HwcUtils
+
+#include "Layers.h"
+#include <vector>
+#include "hardware/hwcomposer.h"
+
+namespace mozilla {
+
+namespace gfx {
+class Matrix;
+}
+
+class HwcUtils {
+public:
+
+enum {
+ HWC_USE_GPU = HWC_FRAMEBUFFER,
+ HWC_USE_OVERLAY = HWC_OVERLAY,
+ HWC_USE_COPYBIT
+};
+
+// HWC layer flags
+enum {
+ // Draw a solid color rectangle
+ // The color should be set on the transform member of the hwc_layer_t struct
+ // The expected format is a 32 bit ABGR with 8 bits per component
+ HWC_COLOR_FILL = 0x8,
+ // Swap the RB pixels of gralloc buffer, like RGBA<->BGRA or RGBX<->BGRX
+ // The flag will be set inside LayerRenderState
+ HWC_FORMAT_RB_SWAP = 0x40
+};
+
+typedef std::vector<hwc_rect_t> RectVector;
+
+/* Utility functions - implemented in HwcUtils.cpp */
+
+/**
+ * Calculates the layer's clipping rectangle
+ *
+ * @param aTransform Input. A transformation matrix
+ * It transforms the clip rect to screen space
+ * @param aLayerClip Input. The layer's internal clipping rectangle.
+ * This may be NULL which means the layer has no internal clipping
+ * The origin is the top-left corner of the layer
+ * @param aParentClip Input. The parent layer's rendering clipping rectangle
+ * The origin is the top-left corner of the screen
+ * @param aRenderClip Output. The layer's rendering clipping rectangle
+ * The origin is the top-left corner of the screen
+ * @return true if the layer should be rendered.
+ * false if the layer can be skipped
+ */
+static bool CalculateClipRect(const gfx::Matrix& aTransform,
+ const nsIntRect* aLayerClip,
+ nsIntRect aParentClip, nsIntRect* aRenderClip);
+
+
+/**
+ * Prepares hwc layer visible region required for hwc composition
+ *
+ * @param aVisible Input. Layer's unclipped visible region
+ * The origin is the top-left corner of the layer
+ * @param aLayerTransform Input. Layer's transformation matrix
+ * It transforms from layer space to screen space
+ * @param aLayerBufferTransform Input. Layer buffer's transformation matrix
+ * It transforms from layer buffer's space to screen space
+ * @param aClip Input. A clipping rectangle.
+ * The origin is the top-left corner of the screen
+ * @param aBufferRect Input. The layer's buffer bounds
+ * The origin is the top-left corner of the layer
+ * @param aVisibleRegionScreen Output. Visible region in screen space.
+ * The origin is the top-left corner of the screen
+ * @param aIsVisible Output. true if region is visible
+ * false if region is not visible
+ * @return true if region can be rendered by HWC.
+ * false if region should not be rendered by HWC
+ */
+static bool PrepareVisibleRegion(const nsIntRegion& aVisible,
+ const gfx::Matrix& aLayerTransform,
+ const gfx::Matrix& aLayerBufferTransform,
+ nsIntRect aClip, nsIntRect aBufferRect,
+ RectVector* aVisibleRegionScreen,
+ bool& aIsVisible);
+
+
+/**
+ * Sets hwc layer rectangles required for hwc composition
+ *
+ * @param aVisible Input. Layer's unclipped visible rectangle
+ * The origin is the top-left corner of the layer
+ * @param aLayerTransform Input. Layer's transformation matrix
+ * It transforms from layer space to screen space
+ * @param aLayerBufferTransform Input. Layer buffer's transformation matrix
+ * It transforms from layer buffer's space to screen space
+ * @param aClip Input. A clipping rectangle.
+ * The origin is the top-left corner of the screen
+ * @param aBufferRect Input. The layer's buffer bounds
+ * The origin is the top-left corner of the layer
+ * @param aYFlipped Input. true if the buffer is rendered as Y flipped
+ * @param aSurceCrop Output. Area of the source to consider,
+ * the origin is the top-left corner of the buffer
+ * @param aVisibleRegionScreen Output. Visible region in screen space.
+ * The origin is the top-left corner of the screen
+ * @return true if the layer should be rendered.
+ * false if the layer can be skipped
+ */
+static bool PrepareLayerRects(nsIntRect aVisible,
+ const gfx::Matrix& aLayerTransform,
+ const gfx::Matrix& aLayerBufferTransform,
+ nsIntRect aClip, nsIntRect aBufferRect,
+ bool aYFlipped,
+ hwc_rect_t* aSourceCrop,
+ hwc_rect_t* aVisibleRegionScreen);
+
+};
+
+} // namespace mozilla
+
+#endif // mozilla_HwcUtils
diff --git a/widget/gonk/OrientationObserver.cpp b/widget/gonk/OrientationObserver.cpp
new file mode 100644
index 000000000..9096404cf
--- /dev/null
+++ b/widget/gonk/OrientationObserver.cpp
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/basictypes.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Hal.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "OrientationObserver.h"
+#include "mozilla/HalSensor.h"
+#include "ProcessOrientation.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+using namespace dom;
+
+namespace {
+
+struct OrientationMapping {
+ uint32_t mScreenRotation;
+ ScreenOrientationInternal mDomOrientation;
+};
+
+static OrientationMapping sOrientationMappings[] = {
+ {nsIScreen::ROTATION_0_DEG, eScreenOrientation_PortraitPrimary},
+ {nsIScreen::ROTATION_180_DEG, eScreenOrientation_PortraitSecondary},
+ {nsIScreen::ROTATION_90_DEG, eScreenOrientation_LandscapePrimary},
+ {nsIScreen::ROTATION_270_DEG, eScreenOrientation_LandscapeSecondary},
+};
+
+const static uint32_t sDefaultLandscape = 2;
+const static uint32_t sDefaultPortrait = 0;
+
+static uint32_t sOrientationOffset = 0;
+
+static already_AddRefed<nsIScreen>
+GetPrimaryScreen()
+{
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ NS_ENSURE_TRUE(screenMgr, nullptr);
+
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->GetPrimaryScreen(getter_AddRefs(screen));
+ return screen.forget();
+}
+
+static void
+DetectDefaultOrientation()
+{
+ nsCOMPtr<nsIScreen> screen = GetPrimaryScreen();
+ if (!screen) {
+ return;
+ }
+
+ int32_t left, top, width, height;
+ if (NS_FAILED(screen->GetRect(&left, &top, &width, &height))) {
+ return;
+ }
+
+ uint32_t rotation;
+ if (NS_FAILED(screen->GetRotation(&rotation))) {
+ return;
+ }
+
+ if (width < height) {
+ if (rotation == nsIScreen::ROTATION_0_DEG ||
+ rotation == nsIScreen::ROTATION_180_DEG) {
+ sOrientationOffset = sDefaultPortrait;
+ } else {
+ sOrientationOffset = sDefaultLandscape;
+ }
+ } else {
+ if (rotation == nsIScreen::ROTATION_0_DEG ||
+ rotation == nsIScreen::ROTATION_180_DEG) {
+ sOrientationOffset = sDefaultLandscape;
+ } else {
+ sOrientationOffset = sDefaultPortrait;
+ }
+ }
+}
+
+/**
+ * Converts DOM orientation to nsIScreen rotation. Portrait and Landscape are
+ * treated as PortraitPrimary and LandscapePrimary, respectively, during
+ * conversion.
+ *
+ * @param aOrientation DOM orientation e.g.
+ * dom::eScreenOrientation_PortraitPrimary.
+ * @param aResult output nsIScreen rotation e.g. nsIScreen::ROTATION_0_DEG.
+ * @return NS_OK on success. NS_ILLEGAL_VALUE on failure.
+ */
+static nsresult
+ConvertToScreenRotation(ScreenOrientationInternal aOrientation, uint32_t *aResult)
+{
+ for (uint32_t i = 0; i < ArrayLength(sOrientationMappings); i++) {
+ if (aOrientation & sOrientationMappings[i].mDomOrientation) {
+ // Shift the mappings in sOrientationMappings so devices with default
+ // landscape orientation map landscape-primary to 0 degree and so forth.
+ int adjusted = (i + sOrientationOffset) %
+ ArrayLength(sOrientationMappings);
+ *aResult = sOrientationMappings[adjusted].mScreenRotation;
+ return NS_OK;
+ }
+ }
+
+ *aResult = nsIScreen::ROTATION_0_DEG;
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+/**
+ * Converts nsIScreen rotation to DOM orientation.
+ *
+ * @param aRotation nsIScreen rotation e.g. nsIScreen::ROTATION_0_DEG.
+ * @param aResult output DOM orientation e.g.
+ * dom::eScreenOrientation_PortraitPrimary.
+ * @return NS_OK on success. NS_ILLEGAL_VALUE on failure.
+ */
+nsresult
+ConvertToDomOrientation(uint32_t aRotation, ScreenOrientationInternal *aResult)
+{
+ for (uint32_t i = 0; i < ArrayLength(sOrientationMappings); i++) {
+ if (aRotation == sOrientationMappings[i].mScreenRotation) {
+ // Shift the mappings in sOrientationMappings so devices with default
+ // landscape orientation map 0 degree to landscape-primary and so forth.
+ int adjusted = (i + sOrientationOffset) %
+ ArrayLength(sOrientationMappings);
+ *aResult = sOrientationMappings[adjusted].mDomOrientation;
+ return NS_OK;
+ }
+ }
+
+ *aResult = eScreenOrientation_None;
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+// Note that all operations with sOrientationSensorObserver
+// should be on the main thread.
+static StaticAutoPtr<OrientationObserver> sOrientationSensorObserver;
+
+} // namespace
+
+OrientationObserver*
+OrientationObserver::GetInstance()
+{
+ if (!sOrientationSensorObserver) {
+ sOrientationSensorObserver = new OrientationObserver();
+ ClearOnShutdown(&sOrientationSensorObserver);
+ }
+
+ return sOrientationSensorObserver;
+}
+
+OrientationObserver::OrientationObserver()
+ : mAutoOrientationEnabled(false)
+ , mAllowedOrientations(sDefaultOrientations)
+ , mOrientation(new mozilla::ProcessOrientation())
+{
+ DetectDefaultOrientation();
+
+ EnableAutoOrientation();
+}
+
+OrientationObserver::~OrientationObserver()
+{
+ if (mAutoOrientationEnabled) {
+ DisableAutoOrientation();
+ }
+}
+
+/* static */ void
+OrientationObserver::ShutDown()
+{
+ if (!sOrientationSensorObserver) {
+ return;
+ }
+
+ if (sOrientationSensorObserver->mAutoOrientationEnabled) {
+ sOrientationSensorObserver->DisableAutoOrientation();
+ }
+}
+
+void
+OrientationObserver::Notify(const hal::SensorData& aSensorData)
+{
+ // Sensor will call us on the main thread.
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aSensorData.sensor() == hal::SensorType::SENSOR_ACCELERATION);
+
+ nsCOMPtr<nsIScreen> screen = GetPrimaryScreen();
+ if (!screen) {
+ return;
+ }
+
+ uint32_t currRotation;
+ if(NS_FAILED(screen->GetRotation(&currRotation))) {
+ return;
+ }
+
+ int rotation = mOrientation->OnSensorChanged(aSensorData, static_cast<int>(currRotation));
+ if (rotation < 0 || uint32_t(rotation) == currRotation) {
+ return;
+ }
+
+ ScreenOrientationInternal orientation;
+ if (NS_FAILED(ConvertToDomOrientation(rotation, &orientation))) {
+ return;
+ }
+
+ if ((mAllowedOrientations & orientation) == eScreenOrientation_None) {
+ // The orientation from sensor is not allowed.
+ return;
+ }
+
+ if (NS_FAILED(screen->SetRotation(static_cast<uint32_t>(rotation)))) {
+ // Don't notify dom on rotation failure.
+ return;
+ }
+}
+
+/**
+ * Register the observer. Note that the observer shouldn't be registered.
+ */
+void
+OrientationObserver::EnableAutoOrientation()
+{
+ MOZ_ASSERT(NS_IsMainThread() && !mAutoOrientationEnabled);
+
+ mOrientation->Reset();
+ hal::RegisterSensorObserver(hal::SENSOR_ACCELERATION, this);
+ mAutoOrientationEnabled = true;
+}
+
+/**
+ * Unregister the observer. Note that the observer should already be registered.
+ */
+void
+OrientationObserver::DisableAutoOrientation()
+{
+ MOZ_ASSERT(NS_IsMainThread() && mAutoOrientationEnabled);
+
+ hal::UnregisterSensorObserver(hal::SENSOR_ACCELERATION, this);
+ mAutoOrientationEnabled = false;
+}
+
+bool
+OrientationObserver::LockScreenOrientation(ScreenOrientationInternal aOrientation)
+{
+ MOZ_ASSERT(aOrientation | (eScreenOrientation_PortraitPrimary |
+ eScreenOrientation_PortraitSecondary |
+ eScreenOrientation_LandscapePrimary |
+ eScreenOrientation_LandscapeSecondary |
+ eScreenOrientation_Default));
+
+ if (aOrientation == eScreenOrientation_Default) {
+ aOrientation = (sOrientationOffset == sDefaultPortrait) ?
+ eScreenOrientation_PortraitPrimary :
+ eScreenOrientation_LandscapePrimary;
+ }
+
+ // If there are multiple orientations allowed, we should enable the
+ // auto-rotation.
+ if (aOrientation != eScreenOrientation_LandscapePrimary &&
+ aOrientation != eScreenOrientation_LandscapeSecondary &&
+ aOrientation != eScreenOrientation_PortraitPrimary &&
+ aOrientation != eScreenOrientation_PortraitSecondary) {
+ if (!mAutoOrientationEnabled) {
+ EnableAutoOrientation();
+ }
+ } else if (mAutoOrientationEnabled) {
+ DisableAutoOrientation();
+ }
+
+ mAllowedOrientations = aOrientation;
+
+ nsCOMPtr<nsIScreen> screen = GetPrimaryScreen();
+ NS_ENSURE_TRUE(screen, false);
+
+ uint32_t currRotation;
+ nsresult rv = screen->GetRotation(&currRotation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ ScreenOrientationInternal currOrientation = eScreenOrientation_None;
+ rv = ConvertToDomOrientation(currRotation, &currOrientation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Don't rotate if the current orientation matches one of the
+ // requested orientations.
+ if (currOrientation & aOrientation) {
+ return true;
+ }
+
+ // Return false on invalid orientation value.
+ uint32_t rotation;
+ rv = ConvertToScreenRotation(aOrientation, &rotation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = screen->SetRotation(rotation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // This conversion will disambiguate aOrientation.
+ ScreenOrientationInternal orientation;
+ rv = ConvertToDomOrientation(rotation, &orientation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+void
+OrientationObserver::UnlockScreenOrientation()
+{
+ if (!mAutoOrientationEnabled) {
+ EnableAutoOrientation();
+ }
+
+ mAllowedOrientations = sDefaultOrientations;
+}
diff --git a/widget/gonk/OrientationObserver.h b/widget/gonk/OrientationObserver.h
new file mode 100644
index 000000000..c841ea878
--- /dev/null
+++ b/widget/gonk/OrientationObserver.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OrientationObserver_h
+#define OrientationObserver_h
+
+#include "mozilla/Observer.h"
+#include "mozilla/dom/ScreenOrientation.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+class ProcessOrientation;
+namespace hal {
+class SensorData;
+typedef mozilla::Observer<SensorData> ISensorObserver;
+} // namespace hal
+} // namespace mozilla
+
+using mozilla::hal::ISensorObserver;
+using mozilla::hal::SensorData;
+using mozilla::dom::ScreenOrientationInternal;
+
+class OrientationObserver : public ISensorObserver {
+public:
+ OrientationObserver();
+ ~OrientationObserver();
+
+ // Call DisableAutoOrientation on the existing OrientatiOnobserver singleton,
+ // if it exists. If no OrientationObserver exists, do nothing.
+ static void ShutDown();
+
+ // Notification from sensor.
+ void Notify(const SensorData& aSensorData);
+
+ // Methods to enable/disable automatic orientation.
+ void EnableAutoOrientation();
+ void DisableAutoOrientation();
+
+ // Methods called by methods in hal_impl namespace.
+ bool LockScreenOrientation(ScreenOrientationInternal aOrientation);
+ void UnlockScreenOrientation();
+
+ static OrientationObserver* GetInstance();
+
+private:
+ bool mAutoOrientationEnabled;
+ uint32_t mAllowedOrientations;
+ mozilla::UniquePtr<mozilla::ProcessOrientation> mOrientation;
+
+ static const uint32_t sDefaultOrientations =
+ mozilla::dom::eScreenOrientation_PortraitPrimary |
+ mozilla::dom::eScreenOrientation_PortraitSecondary |
+ mozilla::dom::eScreenOrientation_LandscapePrimary |
+ mozilla::dom::eScreenOrientation_LandscapeSecondary;
+};
+
+#endif
diff --git a/widget/gonk/ProcessOrientation.cpp b/widget/gonk/ProcessOrientation.cpp
new file mode 100644
index 000000000..bbdcface8
--- /dev/null
+++ b/widget/gonk/ProcessOrientation.cpp
@@ -0,0 +1,519 @@
+/*
+ * Copyright (c) 2013, Linux Foundation. All rights reserved
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/basictypes.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Unused.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "OrientationObserver.h"
+#include "ProcessOrientation.h"
+#include "mozilla/HalSensor.h"
+#include "math.h"
+#include "limits.h"
+#include "android/log.h"
+
+#if 0
+#define LOGD(args...) __android_log_print(ANDROID_LOG_DEBUG, "ProcessOrientation" , ## args)
+#else
+#define LOGD(args...)
+#endif
+
+namespace mozilla {
+
+// We work with all angles in degrees in this class.
+#define RADIANS_TO_DEGREES (180/M_PI)
+
+// Number of nanoseconds per millisecond.
+#define NANOS_PER_MS 1000000
+
+// Indices into SensorEvent.values for the accelerometer sensor.
+#define ACCELEROMETER_DATA_X 0
+#define ACCELEROMETER_DATA_Y 1
+#define ACCELEROMETER_DATA_Z 2
+
+// The minimum amount of time that a predicted rotation must be stable before
+// it is accepted as a valid rotation proposal. This value can be quite small
+// because the low-pass filter already suppresses most of the noise so we're
+// really just looking for quick confirmation that the last few samples are in
+// agreement as to the desired orientation.
+#define PROPOSAL_SETTLE_TIME_NANOS (40*NANOS_PER_MS)
+
+// The minimum amount of time that must have elapsed since the device last
+// exited the flat state (time since it was picked up) before the proposed
+// rotation can change.
+#define PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS (500*NANOS_PER_MS)
+
+// The minimum amount of time that must have elapsed since the device stopped
+// swinging (time since device appeared to be in the process of being put down
+// or put away into a pocket) before the proposed rotation can change.
+#define PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS (300*NANOS_PER_MS)
+
+// The minimum amount of time that must have elapsed since the device stopped
+// undergoing external acceleration before the proposed rotation can change.
+#define PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS (500*NANOS_PER_MS)
+
+// If the tilt angle remains greater than the specified angle for a minimum of
+// the specified time, then the device is deemed to be lying flat
+// (just chillin' on a table).
+#define FLAT_ANGLE 75
+#define FLAT_TIME_NANOS (1000*NANOS_PER_MS)
+
+// If the tilt angle has increased by at least delta degrees within the
+// specified amount of time, then the device is deemed to be swinging away
+// from the user down towards flat (tilt = 90).
+#define SWING_AWAY_ANGLE_DELTA 20
+#define SWING_TIME_NANOS (300*NANOS_PER_MS)
+
+// The maximum sample inter-arrival time in milliseconds. If the acceleration
+// samples are further apart than this amount in time, we reset the state of
+// the low-pass filter and orientation properties. This helps to handle
+// boundary conditions when the device is turned on, wakes from suspend or
+// there is a significant gap in samples.
+#define MAX_FILTER_DELTA_TIME_NANOS (1000*NANOS_PER_MS)
+
+// The acceleration filter time constant.
+//
+// This time constant is used to tune the acceleration filter such that
+// impulses and vibrational noise (think car dock) is suppressed before we try
+// to calculate the tilt and orientation angles.
+//
+// The filter time constant is related to the filter cutoff frequency, which
+// is the frequency at which signals are attenuated by 3dB (half the passband
+// power). Each successive octave beyond this frequency is attenuated by an
+// additional 6dB.
+//
+// Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz
+// is given by Fc = 1 / (2pi * t).
+//
+// The higher the time constant, the lower the cutoff frequency, so more noise
+// will be suppressed.
+//
+// Filtering adds latency proportional the time constant (inversely
+// proportional to the cutoff frequency) so we don't want to make the time
+// constant too large or we can lose responsiveness. Likewise we don't want
+// to make it too small or we do a poor job suppressing acceleration spikes.
+// Empirically, 100ms seems to be too small and 500ms is too large. Android
+// default is 200.
+#define FILTER_TIME_CONSTANT_MS 200.0f
+
+// State for orientation detection. Thresholds for minimum and maximum
+// allowable deviation from gravity.
+//
+// If the device is undergoing external acceleration (being bumped, in a car
+// that is turning around a corner or a plane taking off) then the magnitude
+// may be substantially more or less than gravity. This can skew our
+// orientation detection by making us think that up is pointed in a different
+// direction.
+//
+// Conversely, if the device is in freefall, then there will be no gravity to
+// measure at all. This is problematic because we cannot detect the orientation
+// without gravity to tell us which way is up. A magnitude near 0 produces
+// singularities in the tilt and orientation calculations.
+//
+// In both cases, we postpone choosing an orientation.
+//
+// However, we need to tolerate some acceleration because the angular momentum
+// of turning the device can skew the observed acceleration for a short period
+// of time.
+#define NEAR_ZERO_MAGNITUDE 1 // m/s^2
+#define ACCELERATION_TOLERANCE 4 // m/s^2
+#define STANDARD_GRAVITY 9.80665f
+#define MIN_ACCELERATION_MAGNITUDE (STANDARD_GRAVITY-ACCELERATION_TOLERANCE)
+#define MAX_ACCELERATION_MAGNITUDE (STANDARD_GRAVITY+ACCELERATION_TOLERANCE)
+
+// Maximum absolute tilt angle at which to consider orientation data. Beyond
+// this (i.e. when screen is facing the sky or ground), we completely ignore
+// orientation data.
+#define MAX_TILT 75
+
+// The gap angle in degrees between adjacent orientation angles for
+// hysteresis.This creates a "dead zone" between the current orientation and a
+// proposed adjacent orientation. No orientation proposal is made when the
+// orientation angle is within the gap between the current orientation and the
+// adjacent orientation.
+#define ADJACENT_ORIENTATION_ANGLE_GAP 45
+
+const int
+ProcessOrientation::tiltTolerance[][4] = {
+ {-25, 70}, // ROTATION_0
+ {-25, 65}, // ROTATION_90
+ {-25, 60}, // ROTATION_180
+ {-25, 65} // ROTATION_270
+};
+
+int
+ProcessOrientation::GetProposedRotation()
+{
+ return mProposedRotation;
+}
+
+int
+ProcessOrientation::OnSensorChanged(const SensorData& event,
+ int deviceCurrentRotation)
+{
+ // The vector given in the SensorEvent points straight up (towards the sky)
+ // under ideal conditions (the phone is not accelerating). I'll call this up
+ // vector elsewhere.
+ const InfallibleTArray<float>& values = event.values();
+ float x = values[ACCELEROMETER_DATA_X];
+ float y = values[ACCELEROMETER_DATA_Y];
+ float z = values[ACCELEROMETER_DATA_Z];
+
+ LOGD
+ ("ProcessOrientation: Raw acceleration vector: x = %f, y = %f, z = %f,"
+ "magnitude = %f\n", x, y, z, sqrt(x * x + y * y + z * z));
+ // Apply a low-pass filter to the acceleration up vector in cartesian space.
+ // Reset the orientation listener state if the samples are too far apart in
+ // time or when we see values of (0, 0, 0) which indicates that we polled the
+ // accelerometer too soon after turning it on and we don't have any data yet.
+ const int64_t now = (int64_t) event.timestamp();
+ const int64_t then = mLastFilteredTimestampNanos;
+ const float timeDeltaMS = (now - then) * 0.000001f;
+ bool skipSample = false;
+ if (now < then
+ || now > then + MAX_FILTER_DELTA_TIME_NANOS
+ || (x == 0 && y == 0 && z == 0)) {
+ LOGD
+ ("ProcessOrientation: Resetting orientation listener.");
+ Reset();
+ skipSample = true;
+ } else {
+ const float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
+ x = alpha * (x - mLastFilteredX) + mLastFilteredX;
+ y = alpha * (y - mLastFilteredY) + mLastFilteredY;
+ z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
+ LOGD
+ ("ProcessOrientation: Filtered acceleration vector: x=%f, y=%f, z=%f,"
+ "magnitude=%f", z, y, z, sqrt(x * x + y * y + z * z));
+ skipSample = false;
+ }
+ mLastFilteredTimestampNanos = now;
+ mLastFilteredX = x;
+ mLastFilteredY = y;
+ mLastFilteredZ = z;
+
+ bool isAccelerating = false;
+ bool isFlat = false;
+ bool isSwinging = false;
+ if (skipSample) {
+ return -1;
+ }
+
+ // Calculate the magnitude of the acceleration vector.
+ const float magnitude = sqrt(x * x + y * y + z * z);
+ if (magnitude < NEAR_ZERO_MAGNITUDE) {
+ LOGD
+ ("ProcessOrientation: Ignoring sensor data, magnitude too close to"
+ " zero.");
+ ClearPredictedRotation();
+ } else {
+ // Determine whether the device appears to be undergoing external
+ // acceleration.
+ if (this->IsAccelerating(magnitude)) {
+ isAccelerating = true;
+ mAccelerationTimestampNanos = now;
+ }
+ // Calculate the tilt angle. This is the angle between the up vector and
+ // the x-y plane (the plane of the screen) in a range of [-90, 90]
+ // degrees.
+ // -90 degrees: screen horizontal and facing the ground (overhead)
+ // 0 degrees: screen vertical
+ // 90 degrees: screen horizontal and facing the sky (on table)
+ const int tiltAngle =
+ static_cast<int>(roundf(asin(z / magnitude) * RADIANS_TO_DEGREES));
+ AddTiltHistoryEntry(now, tiltAngle);
+
+ // Determine whether the device appears to be flat or swinging.
+ if (this->IsFlat(now)) {
+ isFlat = true;
+ mFlatTimestampNanos = now;
+ }
+ if (this->IsSwinging(now, tiltAngle)) {
+ isSwinging = true;
+ mSwingTimestampNanos = now;
+ }
+ // If the tilt angle is too close to horizontal then we cannot determine
+ // the orientation angle of the screen.
+ if (abs(tiltAngle) > MAX_TILT) {
+ LOGD
+ ("ProcessOrientation: Ignoring sensor data, tilt angle too high:"
+ " tiltAngle=%d", tiltAngle);
+ ClearPredictedRotation();
+ } else {
+ // Calculate the orientation angle.
+ // This is the angle between the x-y projection of the up vector onto
+ // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
+ int orientationAngle =
+ static_cast<int>(roundf(-atan2f(-x, y) * RADIANS_TO_DEGREES));
+ if (orientationAngle < 0) {
+ // atan2 returns [-180, 180]; normalize to [0, 360]
+ orientationAngle += 360;
+ }
+ // Find the nearest rotation.
+ int nearestRotation = (orientationAngle + 45) / 90;
+ if (nearestRotation == 4) {
+ nearestRotation = 0;
+ }
+ // Determine the predicted orientation.
+ if (IsTiltAngleAcceptable(nearestRotation, tiltAngle)
+ &&
+ IsOrientationAngleAcceptable
+ (nearestRotation, orientationAngle, deviceCurrentRotation)) {
+ UpdatePredictedRotation(now, nearestRotation);
+ LOGD
+ ("ProcessOrientation: Predicted: tiltAngle=%d, orientationAngle=%d,"
+ " predictedRotation=%d, predictedRotationAgeMS=%f",
+ tiltAngle,
+ orientationAngle,
+ mPredictedRotation,
+ ((now - mPredictedRotationTimestampNanos) * 0.000001f));
+ } else {
+ LOGD
+ ("ProcessOrientation: Ignoring sensor data, no predicted rotation:"
+ " tiltAngle=%d, orientationAngle=%d",
+ tiltAngle,
+ orientationAngle);
+ ClearPredictedRotation();
+ }
+ }
+ }
+
+ // Determine new proposed rotation.
+ const int oldProposedRotation = mProposedRotation;
+ if (mPredictedRotation < 0 || IsPredictedRotationAcceptable(now)) {
+ mProposedRotation = mPredictedRotation;
+ }
+ // Write final statistics about where we are in the orientation detection
+ // process.
+ LOGD
+ ("ProcessOrientation: Result: oldProposedRotation=%d,currentRotation=%d, "
+ "proposedRotation=%d, predictedRotation=%d, timeDeltaMS=%f, "
+ "isAccelerating=%d, isFlat=%d, isSwinging=%d, timeUntilSettledMS=%f, "
+ "timeUntilAccelerationDelayExpiredMS=%f, timeUntilFlatDelayExpiredMS=%f, "
+ "timeUntilSwingDelayExpiredMS=%f",
+ oldProposedRotation,
+ deviceCurrentRotation, mProposedRotation,
+ mPredictedRotation, timeDeltaMS, isAccelerating, isFlat,
+ isSwinging, RemainingMS(now,
+ mPredictedRotationTimestampNanos +
+ PROPOSAL_SETTLE_TIME_NANOS),
+ RemainingMS(now,
+ mAccelerationTimestampNanos +
+ PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS),
+ RemainingMS(now,
+ mFlatTimestampNanos +
+ PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS),
+ RemainingMS(now,
+ mSwingTimestampNanos +
+ PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS));
+
+ // Avoid unused-but-set compile warnings for these variables, when LOGD is
+ // a no-op, as it is by default:
+ Unused << isAccelerating;
+ Unused << isFlat;
+ Unused << isSwinging;
+
+ // Tell the listener.
+ if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) {
+ LOGD
+ ("ProcessOrientation: Proposed rotation changed! proposedRotation=%d, "
+ "oldProposedRotation=%d",
+ mProposedRotation,
+ oldProposedRotation);
+ return mProposedRotation;
+ }
+ // Don't rotate screen
+ return -1;
+}
+
+bool
+ProcessOrientation::IsTiltAngleAcceptable(int rotation, int tiltAngle)
+{
+ return (tiltAngle >= tiltTolerance[rotation][0]
+ && tiltAngle <= tiltTolerance[rotation][1]);
+}
+
+bool
+ProcessOrientation::IsOrientationAngleAcceptable(int rotation,
+ int orientationAngle,
+ int currentRotation)
+{
+ // If there is no current rotation, then there is no gap.
+ // The gap is used only to introduce hysteresis among advertised orientation
+ // changes to avoid flapping.
+ if (currentRotation < 0) {
+ return true;
+ }
+ // If the specified rotation is the same or is counter-clockwise adjacent
+ // to the current rotation, then we set a lower bound on the orientation
+ // angle. For example, if currentRotation is ROTATION_0 and proposed is
+ // ROTATION_90, then we want to check orientationAngle > 45 + GAP / 2.
+ if (rotation == currentRotation || rotation == (currentRotation + 1) % 4) {
+ int lowerBound = rotation * 90 - 45 + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (rotation == 0) {
+ if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
+ return false;
+ }
+ } else {
+ if (orientationAngle < lowerBound) {
+ return false;
+ }
+ }
+ }
+ // If the specified rotation is the same or is clockwise adjacent, then we
+ // set an upper bound on the orientation angle. For example, if
+ // currentRotation is ROTATION_0 and rotation is ROTATION_270, then we want
+ // to check orientationAngle < 315 - GAP / 2.
+ if (rotation == currentRotation || rotation == (currentRotation + 3) % 4) {
+ int upperBound = rotation * 90 + 45 - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (rotation == 0) {
+ if (orientationAngle <= 45 && orientationAngle > upperBound) {
+ return false;
+ }
+ } else {
+ if (orientationAngle > upperBound) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool
+ProcessOrientation::IsPredictedRotationAcceptable(int64_t now)
+{
+ // The predicted rotation must have settled long enough.
+ if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
+ return false;
+ }
+ // The last flat state (time since picked up) must have been sufficiently long
+ // ago.
+ if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
+ return false;
+ }
+ // The last swing state (time since last movement to put down) must have been
+ // sufficiently long ago.
+ if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
+ return false;
+ }
+ // The last acceleration state must have been sufficiently long ago.
+ if (now < mAccelerationTimestampNanos
+ + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) {
+ return false;
+ }
+ // Looks good!
+ return true;
+}
+
+int
+ProcessOrientation::Reset()
+{
+ mLastFilteredTimestampNanos = std::numeric_limits<int64_t>::min();
+ mProposedRotation = -1;
+ mFlatTimestampNanos = std::numeric_limits<int64_t>::min();
+ mSwingTimestampNanos = std::numeric_limits<int64_t>::min();
+ mAccelerationTimestampNanos = std::numeric_limits<int64_t>::min();
+ ClearPredictedRotation();
+ ClearTiltHistory();
+ return -1;
+}
+
+void
+ProcessOrientation::ClearPredictedRotation()
+{
+ mPredictedRotation = -1;
+ mPredictedRotationTimestampNanos = std::numeric_limits<int64_t>::min();
+}
+
+void
+ProcessOrientation::UpdatePredictedRotation(int64_t now, int rotation)
+{
+ if (mPredictedRotation != rotation) {
+ mPredictedRotation = rotation;
+ mPredictedRotationTimestampNanos = now;
+ }
+}
+
+bool
+ProcessOrientation::IsAccelerating(float magnitude)
+{
+ return magnitude < MIN_ACCELERATION_MAGNITUDE
+ || magnitude > MAX_ACCELERATION_MAGNITUDE;
+}
+
+void
+ProcessOrientation::ClearTiltHistory()
+{
+ mTiltHistory.history[0].timestampNanos = std::numeric_limits<int64_t>::min();
+ mTiltHistory.index = 1;
+}
+
+void
+ProcessOrientation::AddTiltHistoryEntry(int64_t now, float tilt)
+{
+ mTiltHistory.history[mTiltHistory.index].tiltAngle = tilt;
+ mTiltHistory.history[mTiltHistory.index].timestampNanos = now;
+ mTiltHistory.index = (mTiltHistory.index + 1) % TILT_HISTORY_SIZE;
+ mTiltHistory.history[mTiltHistory.index].timestampNanos = std::numeric_limits<int64_t>::min();
+}
+
+bool
+ProcessOrientation::IsFlat(int64_t now)
+{
+ for (int i = mTiltHistory.index; (i = NextTiltHistoryIndex(i)) >= 0;) {
+ if (mTiltHistory.history[i].tiltAngle < FLAT_ANGLE) {
+ break;
+ }
+ if (mTiltHistory.history[i].timestampNanos + FLAT_TIME_NANOS <= now) {
+ // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+ProcessOrientation::IsSwinging(int64_t now, float tilt)
+{
+ for (int i = mTiltHistory.index; (i = NextTiltHistoryIndex(i)) >= 0;) {
+ if (mTiltHistory.history[i].timestampNanos + SWING_TIME_NANOS < now) {
+ break;
+ }
+ if (mTiltHistory.history[i].tiltAngle + SWING_AWAY_ANGLE_DELTA <= tilt) {
+ // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
+ return true;
+ }
+ }
+ return false;
+}
+
+int
+ProcessOrientation::NextTiltHistoryIndex(int index)
+{
+ index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
+ return mTiltHistory.history[index].timestampNanos != std::numeric_limits<int64_t>::min() ? index : -1;
+}
+
+float
+ProcessOrientation::RemainingMS(int64_t now, int64_t until)
+{
+ return now >= until ? 0 : (until - now) * 0.000001f;
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/ProcessOrientation.h b/widget/gonk/ProcessOrientation.h
new file mode 100644
index 000000000..d6d4bc3b6
--- /dev/null
+++ b/widget/gonk/ProcessOrientation.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2013, Linux Foundation. All rights reserved
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ProcessOrientation_h
+#define ProcessOrientation_h
+
+#include "mozilla/Hal.h"
+
+namespace mozilla {
+
+// History of observed tilt angles.
+#define TILT_HISTORY_SIZE 40
+
+class ProcessOrientation {
+public:
+ ProcessOrientation() {};
+ ~ProcessOrientation() {};
+
+ int OnSensorChanged(const mozilla::hal::SensorData& event, int deviceCurrentRotation);
+ int Reset();
+
+private:
+ int GetProposedRotation();
+
+ // Returns true if the tilt angle is acceptable for a given predicted
+ // rotation.
+ bool IsTiltAngleAcceptable(int rotation, int tiltAngle);
+
+ // Returns true if the orientation angle is acceptable for a given predicted
+ // rotation. This function takes into account the gap between adjacent
+ // orientations for hysteresis.
+ bool IsOrientationAngleAcceptable(int rotation, int orientationAngle,
+ int currentRotation);
+
+ // Returns true if the predicted rotation is ready to be advertised as a
+ // proposed rotation.
+ bool IsPredictedRotationAcceptable(int64_t now);
+
+ void ClearPredictedRotation();
+ void UpdatePredictedRotation(int64_t now, int rotation);
+ bool IsAccelerating(float magnitude);
+ void ClearTiltHistory();
+ void AddTiltHistoryEntry(int64_t now, float tilt);
+ bool IsFlat(int64_t now);
+ bool IsSwinging(int64_t now, float tilt);
+ int NextTiltHistoryIndex(int index);
+ float RemainingMS(int64_t now, int64_t until);
+
+ // The tilt angle range in degrees for each orientation. Beyond these tilt
+ // angles, we don't even consider transitioning into the specified orientation.
+ // We place more stringent requirements on unnatural orientations than natural
+ // ones to make it less likely to accidentally transition into those states.
+ // The first value of each pair is negative so it applies a limit when the
+ // device is facing down (overhead reading in bed). The second value of each
+ // pair is positive so it applies a limit when the device is facing up
+ // (resting on a table). The ideal tilt angle is 0 (when the device is vertical)
+ // so the limits establish how close to vertical the device must be in order
+ // to change orientation.
+ static const int tiltTolerance[][4];
+
+ // Timestamp and value of the last accelerometer sample.
+ int64_t mLastFilteredTimestampNanos;
+ float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
+
+ // The last proposed rotation, -1 if unknown.
+ int mProposedRotation;
+
+ // Value of the current predicted rotation, -1 if unknown.
+ int mPredictedRotation;
+
+ // Timestamp of when the predicted rotation most recently changed.
+ int64_t mPredictedRotationTimestampNanos;
+
+ // Timestamp when the device last appeared to be flat for sure (the flat delay
+ // elapsed).
+ int64_t mFlatTimestampNanos;
+
+ // Timestamp when the device last appeared to be swinging.
+ int64_t mSwingTimestampNanos;
+
+ // Timestamp when the device last appeared to be undergoing external
+ // acceleration.
+ int64_t mAccelerationTimestampNanos;
+
+ struct {
+ struct {
+ float tiltAngle;
+ int64_t timestampNanos;
+ } history[TILT_HISTORY_SIZE];
+ int index;
+ } mTiltHistory;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/widget/gonk/WidgetTraceEvent.cpp b/widget/gonk/WidgetTraceEvent.cpp
new file mode 100644
index 000000000..558d9313e
--- /dev/null
+++ b/widget/gonk/WidgetTraceEvent.cpp
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+#include <mozilla/CondVar.h>
+#include <mozilla/Mutex.h>
+
+using mozilla::CondVar;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+
+namespace mozilla {
+ class TracerRunnable : public Runnable {
+ public:
+ 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);
+ 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
+
diff --git a/widget/gonk/hwchal/HwcHAL.cpp b/widget/gonk/hwchal/HwcHAL.cpp
new file mode 100644
index 000000000..1793b75e6
--- /dev/null
+++ b/widget/gonk/hwchal/HwcHAL.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2015 The Linux Foundation. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HwcHAL.h"
+#include "libdisplay/GonkDisplay.h"
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+
+HwcHAL::HwcHAL()
+ : HwcHALBase()
+{
+ // Some HALs don't want to open hwc twice.
+ // If GetDisplay already load hwc module, we don't need to load again
+ mHwc = (HwcDevice*)GetGonkDisplay()->GetHWCDevice();
+ if (!mHwc) {
+ printf_stderr("HwcHAL Error: Cannot load hwcomposer");
+ return;
+ }
+}
+
+HwcHAL::~HwcHAL()
+{
+ mHwc = nullptr;
+}
+
+bool
+HwcHAL::Query(QueryType aType)
+{
+ if (!mHwc || !mHwc->query) {
+ return false;
+ }
+
+ bool value = false;
+ int supported = 0;
+ if (mHwc->query(mHwc, static_cast<int>(aType), &supported) == 0/*android::NO_ERROR*/) {
+ value = !!supported;
+ }
+ return value;
+}
+
+int
+HwcHAL::Set(HwcList *aList,
+ uint32_t aDisp)
+{
+ MOZ_ASSERT(mHwc);
+ if (!mHwc) {
+ return -1;
+ }
+
+ HwcList *displays[HWC_NUM_DISPLAY_TYPES] = { nullptr };
+ displays[aDisp] = aList;
+ return mHwc->set(mHwc, HWC_NUM_DISPLAY_TYPES, displays);
+}
+
+int
+HwcHAL::ResetHwc()
+{
+ return Set(nullptr, HWC_DISPLAY_PRIMARY);
+}
+
+int
+HwcHAL::Prepare(HwcList *aList,
+ uint32_t aDisp,
+ hwc_rect_t aDispRect,
+ buffer_handle_t aHandle,
+ int aFenceFd)
+{
+ MOZ_ASSERT(mHwc);
+ if (!mHwc) {
+ printf_stderr("HwcHAL Error: HwcDevice doesn't exist. A fence might be leaked.");
+ return -1;
+ }
+
+ HwcList *displays[HWC_NUM_DISPLAY_TYPES] = { nullptr };
+ displays[aDisp] = aList;
+#if ANDROID_VERSION >= 18
+ aList->outbufAcquireFenceFd = -1;
+ aList->outbuf = nullptr;
+#endif
+ aList->retireFenceFd = -1;
+
+ const auto idx = aList->numHwLayers - 1;
+ aList->hwLayers[idx].hints = 0;
+ aList->hwLayers[idx].flags = 0;
+ aList->hwLayers[idx].transform = 0;
+ aList->hwLayers[idx].handle = aHandle;
+ aList->hwLayers[idx].blending = HWC_BLENDING_PREMULT;
+ aList->hwLayers[idx].compositionType = HWC_FRAMEBUFFER_TARGET;
+ SetCrop(aList->hwLayers[idx], aDispRect);
+ aList->hwLayers[idx].displayFrame = aDispRect;
+ aList->hwLayers[idx].visibleRegionScreen.numRects = 1;
+ aList->hwLayers[idx].visibleRegionScreen.rects = &aList->hwLayers[idx].displayFrame;
+ aList->hwLayers[idx].acquireFenceFd = aFenceFd;
+ aList->hwLayers[idx].releaseFenceFd = -1;
+#if ANDROID_VERSION >= 18
+ aList->hwLayers[idx].planeAlpha = 0xFF;
+#endif
+ return mHwc->prepare(mHwc, HWC_NUM_DISPLAY_TYPES, displays);
+}
+
+bool
+HwcHAL::SupportTransparency() const
+{
+#if ANDROID_VERSION >= 18
+ return true;
+#endif
+ return false;
+}
+
+uint32_t
+HwcHAL::GetGeometryChangedFlag(bool aGeometryChanged) const
+{
+#if ANDROID_VERSION >= 19
+ return aGeometryChanged ? HWC_GEOMETRY_CHANGED : 0;
+#else
+ return HWC_GEOMETRY_CHANGED;
+#endif
+}
+
+void
+HwcHAL::SetCrop(HwcLayer &aLayer,
+ const hwc_rect_t &aSrcCrop) const
+{
+ if (GetAPIVersion() >= HwcAPIVersion(1, 3)) {
+#if ANDROID_VERSION >= 19
+ aLayer.sourceCropf.left = aSrcCrop.left;
+ aLayer.sourceCropf.top = aSrcCrop.top;
+ aLayer.sourceCropf.right = aSrcCrop.right;
+ aLayer.sourceCropf.bottom = aSrcCrop.bottom;
+#endif
+ } else {
+ aLayer.sourceCrop = aSrcCrop;
+ }
+}
+
+bool
+HwcHAL::EnableVsync(bool aEnable)
+{
+ // Only support hardware vsync on kitkat, L and up due to inaccurate timings
+ // with JellyBean.
+#if (ANDROID_VERSION == 19 || ANDROID_VERSION >= 21)
+ if (!mHwc) {
+ return false;
+ }
+ return !mHwc->eventControl(mHwc,
+ HWC_DISPLAY_PRIMARY,
+ HWC_EVENT_VSYNC,
+ aEnable);
+#else
+ return false;
+#endif
+}
+
+bool
+HwcHAL::RegisterHwcEventCallback(const HwcHALProcs_t &aProcs)
+{
+ if (!mHwc || !mHwc->registerProcs) {
+ printf_stderr("Failed to get hwc\n");
+ return false;
+ }
+
+ // Disable Vsync first, and then register callback functions.
+ mHwc->eventControl(mHwc,
+ HWC_DISPLAY_PRIMARY,
+ HWC_EVENT_VSYNC,
+ false);
+ static const hwc_procs_t sHwcJBProcs = {aProcs.invalidate,
+ aProcs.vsync,
+ aProcs.hotplug};
+ mHwc->registerProcs(mHwc, &sHwcJBProcs);
+
+ // Only support hardware vsync on kitkat, L and up due to inaccurate timings
+ // with JellyBean.
+#if (ANDROID_VERSION == 19 || ANDROID_VERSION >= 21)
+ return true;
+#else
+ return false;
+#endif
+}
+
+uint32_t
+HwcHAL::GetAPIVersion() const
+{
+ if (!mHwc) {
+ // default value: HWC_MODULE_API_VERSION_0_1
+ return 1;
+ }
+ return mHwc->common.version;
+}
+
+// Create HwcHAL
+UniquePtr<HwcHALBase>
+HwcHALBase::CreateHwcHAL()
+{
+ return Move(MakeUnique<HwcHAL>());
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/hwchal/HwcHAL.h b/widget/gonk/hwchal/HwcHAL.h
new file mode 100644
index 000000000..05cb6a45f
--- /dev/null
+++ b/widget/gonk/hwchal/HwcHAL.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2015 The Linux Foundation. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_HwcHAL
+#define mozilla_HwcHAL
+
+#include "HwcHALBase.h"
+
+namespace mozilla {
+
+class HwcHAL final : public HwcHALBase {
+public:
+ explicit HwcHAL();
+
+ virtual ~HwcHAL();
+
+ virtual bool HasHwc() const override { return static_cast<bool>(mHwc); }
+
+ virtual void SetEGLInfo(hwc_display_t aDpy,
+ hwc_surface_t aSur) override { }
+
+ virtual bool Query(QueryType aType) override;
+
+ virtual int Set(HwcList *aList,
+ uint32_t aDisp) override;
+
+ virtual int ResetHwc() override;
+
+ virtual int Prepare(HwcList *aList,
+ uint32_t aDisp,
+ hwc_rect_t aDispRect,
+ buffer_handle_t aHandle,
+ int aFenceFd) override;
+
+ virtual bool SupportTransparency() const override;
+
+ virtual uint32_t GetGeometryChangedFlag(bool aGeometryChanged) const override;
+
+ virtual void SetCrop(HwcLayer &aLayer,
+ const hwc_rect_t &aSrcCrop) const override;
+
+ virtual bool EnableVsync(bool aEnable) override;
+
+ virtual bool RegisterHwcEventCallback(const HwcHALProcs_t &aProcs) override;
+
+private:
+ uint32_t GetAPIVersion() const;
+
+private:
+ HwcDevice *mHwc = nullptr;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_HwcHAL
diff --git a/widget/gonk/hwchal/HwcHALBase.h b/widget/gonk/hwchal/HwcHALBase.h
new file mode 100644
index 000000000..0ef00a325
--- /dev/null
+++ b/widget/gonk/hwchal/HwcHALBase.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2015 The Linux Foundation. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_HwcHALBase
+#define mozilla_HwcHALBase
+
+#include "mozilla/UniquePtr.h"
+#include "nsRect.h"
+
+#include <hardware/hwcomposer.h>
+
+#ifndef HWC_BLIT
+#if ANDROID_VERSION >= 21
+#define HWC_BLIT 0xFF
+#elif ANDROID_VERSION >= 17
+#define HWC_BLIT (HWC_FRAMEBUFFER_TARGET + 1)
+#else
+// ICS didn't support this. However, we define this
+// for passing compilation
+#define HWC_BLIT 0xFF
+#endif // #if ANDROID_VERSION
+#endif // #ifndef HWC_BLIT
+
+namespace mozilla {
+
+#if ANDROID_VERSION >= 17
+using HwcDevice = hwc_composer_device_1_t;
+using HwcList = hwc_display_contents_1_t;
+using HwcLayer = hwc_layer_1_t;
+#else
+using HwcDevice = hwc_composer_device_t;
+using HwcList = hwc_layer_list_t;
+using HwcLayer = hwc_layer_t;
+#endif
+
+// HwcHAL definition for HwcEvent callback types
+// Note: hwc_procs is different between ICS and later,
+// and the signature of invalidate is also different.
+// Use this wrap struct to hide the detail. BTW,
+// we don't have to register callback functions on ICS, so
+// there is no callbacks for ICS in HwcHALProcs.
+typedef struct HwcHALProcs {
+ void (*invalidate)(const struct hwc_procs* procs);
+ void (*vsync)(const struct hwc_procs* procs, int disp, int64_t timestamp);
+ void (*hotplug)(const struct hwc_procs* procs, int disp, int connected);
+} HwcHALProcs_t;
+
+// HwcHAL class
+// This class handle all the HAL related work
+// The purpose of HwcHAL is to make HwcComposer2D simpler.
+class HwcHALBase {
+
+public:
+ // Query Types. We can add more types easily in the future
+ enum class QueryType {
+ COLOR_FILL = 0x8,
+ RB_SWAP = 0x40
+ };
+
+public:
+ explicit HwcHALBase() = default;
+
+ virtual ~HwcHALBase() {}
+
+ // Create HwcHAL module, Only HwcComposer2D calls this.
+ // If other modules want to use HwcHAL, please use APIs in
+ // HwcComposer2D
+ static UniquePtr<HwcHALBase> CreateHwcHAL();
+
+ // Check if mHwc exists
+ virtual bool HasHwc() const = 0;
+
+ // Set EGL info (only ICS need this info)
+ virtual void SetEGLInfo(hwc_display_t aEGLDisplay,
+ hwc_surface_t aEGLSurface) = 0;
+
+ // HwcDevice query properties
+ virtual bool Query(QueryType aType) = 0;
+
+ // HwcDevice set
+ virtual int Set(HwcList *aList,
+ uint32_t aDisp) = 0;
+
+ // Reset HwcDevice
+ virtual int ResetHwc() = 0;
+
+ // HwcDevice prepare
+ virtual int Prepare(HwcList *aList,
+ uint32_t aDisp,
+ hwc_rect_t aDispRect,
+ buffer_handle_t aHandle,
+ int aFenceFd) = 0;
+
+ // Check transparency support
+ virtual bool SupportTransparency() const = 0;
+
+ // Get a geometry change flag
+ virtual uint32_t GetGeometryChangedFlag(bool aGeometryChanged) const = 0;
+
+ // Set crop help
+ virtual void SetCrop(HwcLayer &aLayer,
+ const hwc_rect_t &aSrcCrop) const = 0;
+
+ // Enable HW Vsync
+ virtual bool EnableVsync(bool aEnable) = 0;
+
+ // Register HW event callback functions
+ virtual bool RegisterHwcEventCallback(const HwcHALProcs_t &aProcs) = 0;
+
+protected:
+ constexpr static uint32_t HwcAPIVersion(uint32_t aMaj, uint32_t aMin) {
+ // HARDWARE_MAKE_API_VERSION_2, from Android hardware.h
+ return (((aMaj & 0xff) << 24) | ((aMin & 0xff) << 16) | (1 & 0xffff));
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_HwcHALBase
diff --git a/widget/gonk/libdisplay/BootAnimation.cpp b/widget/gonk/libdisplay/BootAnimation.cpp
new file mode 100644
index 000000000..c275179fc
--- /dev/null
+++ b/widget/gonk/libdisplay/BootAnimation.cpp
@@ -0,0 +1,726 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <endian.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <string>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <vector>
+#include "mozilla/FileUtils.h"
+#include "png.h"
+
+#include "android/log.h"
+#include "GonkDisplay.h"
+#include "hardware/gralloc.h"
+
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "Gonk", ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "Gonk", ## args)
+
+using namespace mozilla;
+using namespace std;
+
+static pthread_t sAnimationThread;
+static bool sRunAnimation;
+
+/* See http://www.pkware.com/documents/casestudies/APPNOTE.TXT */
+struct local_file_header {
+ uint32_t signature;
+ uint16_t min_version;
+ uint16_t general_flag;
+ uint16_t compression;
+ uint16_t lastmod_time;
+ uint16_t lastmod_date;
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+ uint16_t filename_size;
+ uint16_t extra_field_size;
+ char data[0];
+
+ uint32_t GetDataSize() const
+ {
+ return letoh32(uncompressed_size);
+ }
+
+ uint32_t GetSize() const
+ {
+ /* XXX account for data descriptor */
+ return sizeof(local_file_header) + letoh16(filename_size) +
+ letoh16(extra_field_size) + GetDataSize();
+ }
+
+ const char * GetData() const
+ {
+ return data + letoh16(filename_size) + letoh16(extra_field_size);
+ }
+} __attribute__((__packed__));
+
+struct data_descriptor {
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+} __attribute__((__packed__));
+
+struct cdir_entry {
+ uint32_t signature;
+ uint16_t creator_version;
+ uint16_t min_version;
+ uint16_t general_flag;
+ uint16_t compression;
+ uint16_t lastmod_time;
+ uint16_t lastmod_date;
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+ uint16_t filename_size;
+ uint16_t extra_field_size;
+ uint16_t file_comment_size;
+ uint16_t disk_num;
+ uint16_t internal_attr;
+ uint32_t external_attr;
+ uint32_t offset;
+ char data[0];
+
+ uint32_t GetDataSize() const
+ {
+ return letoh32(compressed_size);
+ }
+
+ uint32_t GetSize() const
+ {
+ return sizeof(cdir_entry) + letoh16(filename_size) +
+ letoh16(extra_field_size) + letoh16(file_comment_size);
+ }
+
+ bool Valid() const
+ {
+ return signature == htole32(0x02014b50);
+ }
+} __attribute__((__packed__));
+
+struct cdir_end {
+ uint32_t signature;
+ uint16_t disk_num;
+ uint16_t cdir_disk;
+ uint16_t disk_entries;
+ uint16_t cdir_entries;
+ uint32_t cdir_size;
+ uint32_t cdir_offset;
+ uint16_t comment_size;
+ char comment[0];
+
+ bool Valid() const
+ {
+ return signature == htole32(0x06054b50);
+ }
+} __attribute__((__packed__));
+
+/* We don't have access to libjar and the zip reader in android
+ * doesn't quite fit what we want to do. */
+class ZipReader {
+ const char *mBuf;
+ const cdir_end *mEnd;
+ const char *mCdir_limit;
+ uint32_t mBuflen;
+
+public:
+ ZipReader() : mBuf(nullptr) {}
+ ~ZipReader() {
+ if (mBuf)
+ munmap((void *)mBuf, mBuflen);
+ }
+
+ bool OpenArchive(const char *path)
+ {
+ int fd;
+ do {
+ fd = open(path, O_RDONLY);
+ } while (fd == -1 && errno == EINTR);
+ if (fd == -1)
+ return false;
+
+ struct stat sb;
+ if (fstat(fd, &sb) == -1 || sb.st_size < sizeof(cdir_end)) {
+ close(fd);
+ return false;
+ }
+
+ mBuflen = sb.st_size;
+ mBuf = (char *)mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ close(fd);
+
+ if (!mBuf) {
+ return false;
+ }
+
+ madvise(mBuf, sb.st_size, MADV_SEQUENTIAL);
+
+ mEnd = (cdir_end *)(mBuf + mBuflen - sizeof(cdir_end));
+ while (!mEnd->Valid() &&
+ (char *)mEnd > mBuf) {
+ mEnd = (cdir_end *)((char *)mEnd - 1);
+ }
+
+ mCdir_limit = mBuf + letoh32(mEnd->cdir_offset) + letoh32(mEnd->cdir_size);
+
+ if (!mEnd->Valid() || mCdir_limit > (char *)mEnd) {
+ munmap((void *)mBuf, mBuflen);
+ mBuf = nullptr;
+ return false;
+ }
+
+ return true;
+ }
+
+ /* Pass null to get the first cdir entry */
+ const cdir_entry * GetNextEntry(const cdir_entry *prev)
+ {
+ const cdir_entry *entry;
+ if (prev)
+ entry = (cdir_entry *)((char *)prev + prev->GetSize());
+ else
+ entry = (cdir_entry *)(mBuf + letoh32(mEnd->cdir_offset));
+
+ if (((char *)entry + entry->GetSize()) > mCdir_limit ||
+ !entry->Valid())
+ return nullptr;
+ return entry;
+ }
+
+ string GetEntryName(const cdir_entry *entry)
+ {
+ uint16_t len = letoh16(entry->filename_size);
+
+ string name;
+ name.append(entry->data, len);
+ return name;
+ }
+
+ const local_file_header * GetLocalEntry(const cdir_entry *entry)
+ {
+ const local_file_header * data =
+ (local_file_header *)(mBuf + letoh32(entry->offset));
+ if (((char *)data + data->GetSize()) > (char *)mEnd)
+ return nullptr;
+ return data;
+ }
+};
+
+struct AnimationFrame {
+ char path[256];
+ png_color_16 bgcolor;
+ char *buf;
+ const local_file_header *file;
+ uint32_t width;
+ uint32_t height;
+ uint16_t bytepp;
+ bool has_bgcolor;
+
+ AnimationFrame() : buf(nullptr) {}
+ AnimationFrame(const AnimationFrame &frame) : buf(nullptr) {
+ strncpy(path, frame.path, sizeof(path));
+ file = frame.file;
+ }
+ ~AnimationFrame()
+ {
+ if (buf)
+ free(buf);
+ }
+
+ bool operator<(const AnimationFrame &other) const
+ {
+ return strcmp(path, other.path) < 0;
+ }
+
+ void ReadPngFrame(int outputFormat);
+};
+
+struct AnimationPart {
+ int32_t count;
+ int32_t pause;
+ // If you alter the size of the path, change ReadFromString() as well.
+ char path[256];
+ vector<AnimationFrame> frames;
+
+ bool
+ ReadFromString(const char* aLine)
+ {
+ MOZ_ASSERT(aLine);
+ // this 255 value must be in sync with AnimationPart::path.
+ return sscanf(aLine, "p %d %d %255s", &count, &pause, path) == 3;
+ }
+};
+
+struct RawReadState {
+ const char *start;
+ uint32_t offset;
+ uint32_t length;
+};
+
+static void
+RawReader(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ RawReadState *state = (RawReadState *)png_get_io_ptr(png_ptr);
+ if (length > (state->length - state->offset))
+ png_error(png_ptr, "PNG read overrun");
+
+ memcpy(data, state->start + state->offset, length);
+ state->offset += length;
+}
+
+static void
+TransformTo565(png_structp png_ptr, png_row_infop row_info, png_bytep data)
+{
+ uint16_t *outbuf = (uint16_t *)data;
+ uint8_t *inbuf = (uint8_t *)data;
+ for (uint32_t i = 0; i < row_info->rowbytes; i += 3) {
+ *outbuf++ = ((inbuf[i] & 0xF8) << 8) |
+ ((inbuf[i + 1] & 0xFC) << 3) |
+ ((inbuf[i + 2] ) >> 3);
+ }
+}
+
+static uint16_t
+GetFormatBPP(int aFormat)
+{
+ uint16_t bpp = 0;
+
+ switch (aFormat) {
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ bpp = 4;
+ break;
+ case HAL_PIXEL_FORMAT_RGB_888:
+ bpp = 3;
+ break;
+ default:
+ LOGW("Unknown pixel format %d. Assuming RGB 565.", aFormat);
+ // FALL THROUGH
+ case HAL_PIXEL_FORMAT_RGB_565:
+ bpp = 2;
+ break;
+ }
+
+ return bpp;
+}
+
+void
+AnimationFrame::ReadPngFrame(int outputFormat)
+{
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ static const png_byte unused_chunks[] =
+ { 99, 72, 82, 77, '\0', /* cHRM */
+ 104, 73, 83, 84, '\0', /* hIST */
+ 105, 67, 67, 80, '\0', /* iCCP */
+ 105, 84, 88, 116, '\0', /* iTXt */
+ 111, 70, 70, 115, '\0', /* oFFs */
+ 112, 67, 65, 76, '\0', /* pCAL */
+ 115, 67, 65, 76, '\0', /* sCAL */
+ 112, 72, 89, 115, '\0', /* pHYs */
+ 115, 66, 73, 84, '\0', /* sBIT */
+ 115, 80, 76, 84, '\0', /* sPLT */
+ 116, 69, 88, 116, '\0', /* tEXt */
+ 116, 73, 77, 69, '\0', /* tIME */
+ 122, 84, 88, 116, '\0'}; /* zTXt */
+ static const png_byte tRNS_chunk[] =
+ {116, 82, 78, 83, '\0'}; /* tRNS */
+#endif
+
+ png_structp pngread = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ nullptr, nullptr, nullptr);
+
+ if (!pngread)
+ return;
+
+ png_infop pnginfo = png_create_info_struct(pngread);
+
+ if (!pnginfo) {
+ png_destroy_read_struct(&pngread, &pnginfo, nullptr);
+ return;
+ }
+
+ if (setjmp(png_jmpbuf(pngread))) {
+ // libpng reported an error and longjumped here. Clean up and return.
+ png_destroy_read_struct(&pngread, &pnginfo, nullptr);
+ return;
+ }
+
+ RawReadState state;
+ state.start = file->GetData();
+ state.length = file->GetDataSize();
+ state.offset = 0;
+
+ png_set_read_fn(pngread, &state, RawReader);
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ /* Ignore unused chunks */
+ png_set_keep_unknown_chunks(pngread, 1, unused_chunks,
+ (int)sizeof(unused_chunks)/5);
+
+ /* Ignore the tRNS chunk if we only want opaque output */
+ if (outputFormat == HAL_PIXEL_FORMAT_RGB_888 ||
+ outputFormat == HAL_PIXEL_FORMAT_RGB_565) {
+ png_set_keep_unknown_chunks(pngread, 1, tRNS_chunk, 1);
+ }
+#endif
+
+ png_read_info(pngread, pnginfo);
+
+ png_color_16p colorp;
+ has_bgcolor = (PNG_INFO_bKGD == png_get_bKGD(pngread, pnginfo, &colorp));
+ bgcolor = has_bgcolor ? *colorp : png_color_16();
+ width = png_get_image_width(pngread, pnginfo);
+ height = png_get_image_height(pngread, pnginfo);
+
+ LOG("Decoded %s: %d x %d frame with bgcolor? %s (%#x, %#x, %#x; gray:%#x)",
+ path, width, height, has_bgcolor ? "yes" : "no",
+ bgcolor.red, bgcolor.green, bgcolor.blue, bgcolor.gray);
+
+ bytepp = GetFormatBPP(outputFormat);
+
+ switch (outputFormat) {
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ png_set_bgr(pngread);
+ // FALL THROUGH
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ png_set_filler(pngread, 0xFF, PNG_FILLER_AFTER);
+ break;
+ case HAL_PIXEL_FORMAT_RGB_888:
+ png_set_strip_alpha(pngread);
+ break;
+ default:
+ LOGW("Unknown pixel format %d. Assuming RGB 565.", outputFormat);
+ // FALL THROUGH
+ case HAL_PIXEL_FORMAT_RGB_565:
+ png_set_strip_alpha(pngread);
+ png_set_read_user_transform_fn(pngread, TransformTo565);
+ break;
+ }
+
+ // An extra row is added to give libpng enough space when
+ // decoding 3/4 bytepp inputs for 2 bytepp output surfaces
+ buf = (char *)malloc(width * (height + 1) * bytepp);
+
+ vector<char *> rows(height + 1);
+ uint32_t stride = width * bytepp;
+ for (uint32_t i = 0; i < height; i++) {
+ rows[i] = buf + (stride * i);
+ }
+ rows[height] = nullptr;
+ png_set_strip_16(pngread);
+ png_set_palette_to_rgb(pngread);
+ png_set_gray_to_rgb(pngread);
+ png_read_image(pngread, (png_bytepp)&rows.front());
+ png_destroy_read_struct(&pngread, &pnginfo, nullptr);
+}
+
+/**
+ * Return a wchar_t that when used to |wmemset()| an image buffer will
+ * fill it with the color defined by |color16|. The packed wchar_t
+ * may comprise one or two pixels depending on |outputFormat|.
+ */
+static wchar_t
+AsBackgroundFill(const png_color_16& color16, int outputFormat)
+{
+ static_assert(sizeof(wchar_t) == sizeof(uint32_t),
+ "TODO: support 2-byte wchar_t");
+ union {
+ uint32_t r8g8b8;
+ struct {
+ uint8_t b8;
+ uint8_t g8;
+ uint8_t r8;
+ uint8_t x8;
+ };
+ } color;
+ color.b8 = color16.blue;
+ color.g8 = color16.green;
+ color.r8 = color16.red;
+ color.x8 = 0xFF;
+
+ switch (outputFormat) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ return color.r8g8b8;
+
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ swap(color.r8, color.b8);
+ return color.r8g8b8;
+
+ case HAL_PIXEL_FORMAT_RGB_565: {
+ // NB: we could do a higher-quality downsample here, but we
+ // want the results to be a pixel-perfect match with the fast
+ // downsample in TransformTo565().
+ uint16_t color565 = ((color.r8 & 0xF8) << 8) |
+ ((color.g8 & 0xFC) << 3) |
+ ((color.b8 ) >> 3);
+ return (color565 << 16) | color565;
+ }
+ default:
+ LOGW("Unhandled pixel format %d; falling back on black", outputFormat);
+ return 0;
+ }
+}
+
+void
+ShowSolidColorFrame(GonkDisplay *aDisplay,
+ const gralloc_module_t *grallocModule,
+ int32_t aFormat)
+{
+ LOGW("Show solid color frame for bootAnim");
+
+ ANativeWindowBuffer *buffer = aDisplay->DequeueBuffer();
+ void *mappedAddress = nullptr;
+
+ if (!buffer) {
+ LOGW("Failed to get an ANativeWindowBuffer");
+ return;
+ }
+
+ if (!grallocModule->lock(grallocModule, buffer->handle,
+ GRALLOC_USAGE_SW_READ_NEVER |
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_FB,
+ 0, 0, buffer->width, buffer->height, &mappedAddress)) {
+ // Just show a black solid color frame.
+ memset(mappedAddress, 0, buffer->height * buffer->stride * GetFormatBPP(aFormat));
+ grallocModule->unlock(grallocModule, buffer->handle);
+ }
+
+ aDisplay->QueueBuffer(buffer);
+}
+
+static void *
+AnimationThread(void *)
+{
+ GonkDisplay *display = GetGonkDisplay();
+ int32_t format = display->surfaceformat;
+
+ const hw_module_t *module = nullptr;
+ if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module)) {
+ LOGW("Could not get gralloc module");
+ return nullptr;
+ }
+ const gralloc_module_t *grmodule =
+ reinterpret_cast<gralloc_module_t const*>(module);
+
+ ZipReader reader;
+ if (!reader.OpenArchive("/system/media/bootanimation.zip")) {
+ LOGW("Could not open boot animation");
+ ShowSolidColorFrame(display, grmodule, format);
+ return nullptr;
+ }
+
+ const cdir_entry *entry = nullptr;
+ const local_file_header *file = nullptr;
+ while ((entry = reader.GetNextEntry(entry))) {
+ string name = reader.GetEntryName(entry);
+ if (!name.compare("desc.txt")) {
+ file = reader.GetLocalEntry(entry);
+ break;
+ }
+ }
+
+ if (!file) {
+ LOGW("Could not find desc.txt in boot animation");
+ ShowSolidColorFrame(display, grmodule, format);
+ return nullptr;
+ }
+
+ string descCopy;
+ descCopy.append(file->GetData(), entry->GetDataSize());
+ int32_t width, height, fps;
+ const char *line = descCopy.c_str();
+ const char *end;
+ bool headerRead = true;
+ vector<AnimationPart> parts;
+ bool animPlayed = false;
+
+ /*
+ * bootanimation.zip
+ *
+ * This is the boot animation file format that Android uses.
+ * It's a zip file with a directories containing png frames
+ * and a desc.txt that describes how they should be played.
+ *
+ * desc.txt contains two types of lines
+ * 1. [width] [height] [fps]
+ * There is one of these lines per bootanimation.
+ * If the width and height are smaller than the screen,
+ * the frames are centered on a black background.
+ * XXX: Currently we stretch instead of centering the frame.
+ * 2. p [count] [pause] [path]
+ * This describes one animation part.
+ * Each animation part is played in sequence.
+ * An animation part contains all the files/frames in the
+ * directory specified in [path]
+ * [count] indicates the number of times this part repeats.
+ * [pause] indicates the number of frames that this part
+ * should pause for after playing the full sequence but
+ * before repeating.
+ */
+
+ do {
+ end = strstr(line, "\n");
+
+ AnimationPart part;
+ if (headerRead &&
+ sscanf(line, "%d %d %d", &width, &height, &fps) == 3) {
+ headerRead = false;
+ } else if (part.ReadFromString(line)) {
+ parts.push_back(part);
+ }
+ } while (end && *(line = end + 1));
+
+ for (uint32_t i = 0; i < parts.size(); i++) {
+ AnimationPart &part = parts[i];
+ entry = nullptr;
+ char search[256];
+ snprintf(search, sizeof(search), "%s/", part.path);
+ while ((entry = reader.GetNextEntry(entry))) {
+ string name = reader.GetEntryName(entry);
+ if (name.find(search) ||
+ !entry->GetDataSize() ||
+ name.length() >= 256)
+ continue;
+
+ part.frames.resize(part.frames.size() + 1);
+ AnimationFrame &frame = part.frames.back();
+ strcpy(frame.path, name.c_str());
+ frame.file = reader.GetLocalEntry(entry);
+ }
+
+ sort(part.frames.begin(), part.frames.end());
+ }
+
+ long int frameDelayUs = 1000000 / fps;
+
+ for (uint32_t i = 0; i < parts.size(); i++) {
+ AnimationPart &part = parts[i];
+
+ int32_t j = 0;
+ while (sRunAnimation && (!part.count || j++ < part.count)) {
+ for (uint32_t k = 0; k < part.frames.size(); k++) {
+ struct timeval tv1, tv2;
+ gettimeofday(&tv1, nullptr);
+ AnimationFrame &frame = part.frames[k];
+ if (!frame.buf) {
+ frame.ReadPngFrame(format);
+ }
+
+ ANativeWindowBuffer *buf = display->DequeueBuffer();
+ if (!buf) {
+ LOGW("Failed to get an ANativeWindowBuffer");
+ break;
+ }
+
+ void *vaddr;
+ if (grmodule->lock(grmodule, buf->handle,
+ GRALLOC_USAGE_SW_READ_NEVER |
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_FB,
+ 0, 0, width, height, &vaddr)) {
+ LOGW("Failed to lock buffer_handle_t");
+ display->QueueBuffer(buf);
+ break;
+ }
+
+ if (frame.has_bgcolor) {
+ wchar_t bgfill = AsBackgroundFill(frame.bgcolor, format);
+ wmemset((wchar_t*)vaddr, bgfill,
+ (buf->height * buf->stride * frame.bytepp) / sizeof(wchar_t));
+ }
+
+ if ((uint32_t)buf->height == frame.height && (uint32_t)buf->stride == frame.width) {
+ memcpy(vaddr, frame.buf,
+ frame.width * frame.height * frame.bytepp);
+ } else if ((uint32_t)buf->height >= frame.height &&
+ (uint32_t)buf->width >= frame.width) {
+ int startx = (buf->width - frame.width) / 2;
+ int starty = (buf->height - frame.height) / 2;
+
+ int src_stride = frame.width * frame.bytepp;
+ int dst_stride = buf->stride * frame.bytepp;
+
+ char *src = frame.buf;
+ char *dst = (char *) vaddr + starty * dst_stride + startx * frame.bytepp;
+
+ for (uint32_t i = 0; i < frame.height; i++) {
+ memcpy(dst, src, src_stride);
+ src += src_stride;
+ dst += dst_stride;
+ }
+ }
+ grmodule->unlock(grmodule, buf->handle);
+
+ gettimeofday(&tv2, nullptr);
+
+ timersub(&tv2, &tv1, &tv2);
+
+ if (tv2.tv_usec < frameDelayUs) {
+ usleep(frameDelayUs - tv2.tv_usec);
+ } else {
+ LOGW("Frame delay is %ld us but decoding took %ld us",
+ frameDelayUs, tv2.tv_usec);
+ }
+
+ animPlayed = true;
+ display->QueueBuffer(buf);
+
+ if (part.count && j >= part.count) {
+ free(frame.buf);
+ frame.buf = nullptr;
+ }
+ }
+ usleep(frameDelayUs * part.pause);
+ }
+ }
+
+ if (!animPlayed) {
+ ShowSolidColorFrame(display, grmodule, format);
+ }
+
+ return nullptr;
+}
+
+namespace mozilla {
+
+__attribute__ ((visibility ("default")))
+void
+StartBootAnimation()
+{
+ GetGonkDisplay(); // Ensure GonkDisplay exist
+ sRunAnimation = true;
+ pthread_create(&sAnimationThread, nullptr, AnimationThread, nullptr);
+}
+
+__attribute__ ((visibility ("default")))
+void
+StopBootAnimation()
+{
+ if (sRunAnimation) {
+ sRunAnimation = false;
+ pthread_join(sAnimationThread, nullptr);
+ GetGonkDisplay()->NotifyBootAnimationStopped();
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/libdisplay/BootAnimation.h b/widget/gonk/libdisplay/BootAnimation.h
new file mode 100644
index 000000000..9fdc20eca
--- /dev/null
+++ b/widget/gonk/libdisplay/BootAnimation.h
@@ -0,0 +1,30 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BOOTANIMATION_H
+#define BOOTANIMATION_H
+
+namespace mozilla {
+
+MOZ_EXPORT __attribute__ ((weak))
+void StartBootAnimation();
+
+/* Stop the boot animation if it's still running. */
+MOZ_EXPORT __attribute__ ((weak))
+void StopBootAnimation();
+
+} // namespace mozilla
+
+#endif /* BOOTANIMATION_H */
diff --git a/widget/gonk/libdisplay/DisplaySurface.h b/widget/gonk/libdisplay/DisplaySurface.h
new file mode 100644
index 000000000..398541c49
--- /dev/null
+++ b/widget/gonk/libdisplay/DisplaySurface.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SF_DISPLAY_SURFACE_H
+#define ANDROID_SF_DISPLAY_SURFACE_H
+
+#include <gui/ConsumerBase.h>
+#include <system/window.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+class IGraphicBufferProducer;
+class String8;
+
+#if ANDROID_VERSION >= 21
+typedef IGraphicBufferConsumer StreamConsumer;
+#else
+typedef BufferQueue StreamConsumer;
+#endif
+
+class DisplaySurface : public ConsumerBase {
+public:
+ // beginFrame is called at the beginning of the composition loop, before
+ // the configuration is known. The DisplaySurface should do anything it
+ // needs to do to enable HWComposer to decide how to compose the frame.
+ // We pass in mustRecompose so we can keep VirtualDisplaySurface's state
+ // machine happy without actually queueing a buffer if nothing has changed.
+ virtual status_t beginFrame(bool mustRecompose) = 0;
+
+ // prepareFrame is called after the composition configuration is known but
+ // before composition takes place. The DisplaySurface can use the
+ // composition type to decide how to manage the flow of buffers between
+ // GLES and HWC for this frame.
+ enum CompositionType {
+ COMPOSITION_UNKNOWN = 0,
+ COMPOSITION_GLES = 1,
+ COMPOSITION_HWC = 2,
+ COMPOSITION_MIXED = COMPOSITION_GLES | COMPOSITION_HWC
+ };
+ virtual status_t prepareFrame(CompositionType compositionType) = 0;
+
+ // Should be called when composition rendering is complete for a frame (but
+ // eglSwapBuffers hasn't necessarily been called). Required by certain
+ // older drivers for synchronization.
+ // TODO: Remove this when we drop support for HWC 1.0.
+ virtual status_t compositionComplete() = 0;
+
+ // Inform the surface that GLES composition is complete for this frame, and
+ // the surface should make sure that HWComposer has the correct buffer for
+ // this frame. Some implementations may only push a new buffer to
+ // HWComposer if GLES composition took place, others need to push a new
+ // buffer on every frame.
+ //
+ // advanceFrame must be followed by a call to onFrameCommitted before
+ // advanceFrame may be called again.
+ virtual status_t advanceFrame() = 0;
+
+ // onFrameCommitted is called after the frame has been committed to the
+ // hardware composer. The surface collects the release fence for this
+ // frame's buffer.
+ virtual void onFrameCommitted() = 0;
+
+ virtual void resizeBuffers(const uint32_t w, const uint32_t h) = 0;
+
+ // setReleaseFenceFd stores a fence file descriptor that will signal when the
+ // current buffer is no longer being read. This fence will be returned to
+ // the producer when the current buffer is released by updateTexImage().
+ // Multiple fences can be set for a given buffer; they will be merged into
+ // a single union fence. The SurfaceTexture will close the file descriptor
+ // when finished with it.
+ virtual status_t setReleaseFenceFd(int fenceFd) = 0;
+
+ virtual int GetPrevDispAcquireFd() = 0;
+
+ buffer_handle_t lastHandle;
+
+protected:
+ DisplaySurface(const sp<StreamConsumer>& sc)
+#if ANDROID_VERSION >= 19
+ : ConsumerBase(sc, true)
+#else
+ : ConsumerBase(sc)
+#endif
+ , lastHandle(0)
+ { }
+ virtual ~DisplaySurface() {}
+};
+
+// ---------------------------------------------------------------------------
+} // namespace android
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_SF_DISPLAY_SURFACE_H
+
diff --git a/widget/gonk/libdisplay/FramebufferSurface.cpp b/widget/gonk/libdisplay/FramebufferSurface.cpp
new file mode 100644
index 000000000..a289acbb8
--- /dev/null
+++ b/widget/gonk/libdisplay/FramebufferSurface.cpp
@@ -0,0 +1,207 @@
+/*
+ **
+ ** Copyright 2012 The Android Open Source Project
+ **
+ ** Licensed under the Apache License Version 2.0(the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing software
+ ** distributed under the License is distributed on an "AS IS" BASIS
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <cutils/log.h>
+
+#include <utils/String8.h>
+
+#include <ui/Rect.h>
+
+#include <EGL/egl.h>
+
+#include <hardware/hardware.h>
+#if ANDROID_VERSION == 17
+#include <gui/SurfaceTextureClient.h>
+#endif
+#include <ui/GraphicBuffer.h>
+
+#include "FramebufferSurface.h"
+#include "GraphicBufferAlloc.h"
+
+#ifndef NUM_FRAMEBUFFER_SURFACE_BUFFERS
+#define NUM_FRAMEBUFFER_SURFACE_BUFFERS (2)
+#endif
+
+// ----------------------------------------------------------------------------
+namespace android {
+// ----------------------------------------------------------------------------
+
+/*
+ * This implements the (main) framebuffer management. This class
+ * was adapted from the version in SurfaceFlinger
+ */
+FramebufferSurface::FramebufferSurface(int disp,
+ uint32_t width,
+ uint32_t height,
+ uint32_t format,
+ const sp<StreamConsumer>& sc)
+ : DisplaySurface(sc)
+ , mDisplayType(disp)
+ , mCurrentBufferSlot(-1)
+ , mCurrentBuffer(0)
+{
+ mName = "FramebufferSurface";
+
+#if ANDROID_VERSION >= 19
+ sp<IGraphicBufferConsumer> consumer = mConsumer;
+#else
+ sp<BufferQueue> consumer = mBufferQueue;
+ consumer->setSynchronousMode(true);
+#endif
+ consumer->setConsumerName(mName);
+ consumer->setConsumerUsageBits(GRALLOC_USAGE_HW_FB |
+ GRALLOC_USAGE_HW_RENDER |
+ GRALLOC_USAGE_HW_COMPOSER);
+ consumer->setDefaultBufferFormat(format);
+ consumer->setDefaultBufferSize(width, height);
+ consumer->setDefaultMaxBufferCount(NUM_FRAMEBUFFER_SURFACE_BUFFERS);
+}
+
+status_t FramebufferSurface::beginFrame(bool /*mustRecompose*/) {
+ return NO_ERROR;
+}
+
+status_t FramebufferSurface::prepareFrame(CompositionType /*compositionType*/) {
+ return NO_ERROR;
+}
+
+status_t FramebufferSurface::advanceFrame() {
+ // Once we remove FB HAL support, we can call nextBuffer() from here
+ // instead of using onFrameAvailable(). No real benefit, except it'll be
+ // more like VirtualDisplaySurface.
+ return NO_ERROR;
+}
+
+status_t FramebufferSurface::nextBuffer(sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence) {
+ Mutex::Autolock lock(mMutex);
+
+ BufferQueue::BufferItem item;
+#if ANDROID_VERSION >= 19
+ status_t err = acquireBufferLocked(&item, 0);
+#else
+ status_t err = acquireBufferLocked(&item);
+#endif
+ if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
+ outBuffer = mCurrentBuffer;
+ return NO_ERROR;
+ } else if (err != NO_ERROR) {
+ ALOGE("error acquiring buffer: %s (%d)", strerror(-err), err);
+ return err;
+ }
+
+ // If the BufferQueue has freed and reallocated a buffer in mCurrentSlot
+ // then we may have acquired the slot we already own. If we had released
+ // our current buffer before we call acquireBuffer then that release call
+ // would have returned STALE_BUFFER_SLOT, and we would have called
+ // freeBufferLocked on that slot. Because the buffer slot has already
+ // been overwritten with the new buffer all we have to do is skip the
+ // releaseBuffer call and we should be in the same state we'd be in if we
+ // had released the old buffer first.
+ if (mCurrentBufferSlot != BufferQueue::INVALID_BUFFER_SLOT &&
+ item.mBuf != mCurrentBufferSlot) {
+ // Release the previous buffer.
+#if ANDROID_VERSION >= 19
+ err = releaseBufferLocked(mCurrentBufferSlot, mCurrentBuffer,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
+#else
+ err = releaseBufferLocked(mCurrentBufferSlot, EGL_NO_DISPLAY,
+ EGL_NO_SYNC_KHR);
+#endif
+ if (err != NO_ERROR && err != StreamConsumer::STALE_BUFFER_SLOT) {
+ ALOGE("error releasing buffer: %s (%d)", strerror(-err), err);
+ return err;
+ }
+ }
+ mCurrentBufferSlot = item.mBuf;
+ mCurrentBuffer = mSlots[mCurrentBufferSlot].mGraphicBuffer;
+ outFence = item.mFence;
+ outBuffer = mCurrentBuffer;
+ return NO_ERROR;
+}
+
+// Overrides ConsumerBase::onFrameAvailable(), does not call base class impl.
+#if ANDROID_VERSION >= 22
+void FramebufferSurface::onFrameAvailable(const ::android::BufferItem &item) {
+#else
+void FramebufferSurface::onFrameAvailable() {
+#endif
+ sp<GraphicBuffer> buf;
+ sp<Fence> acquireFence;
+ status_t err = nextBuffer(buf, acquireFence);
+ if (err != NO_ERROR) {
+ ALOGE("error latching nnext FramebufferSurface buffer: %s (%d)",
+ strerror(-err), err);
+ return;
+ }
+ if (acquireFence.get() && acquireFence->isValid())
+ mPrevFBAcquireFence = new Fence(acquireFence->dup());
+ else
+ mPrevFBAcquireFence = Fence::NO_FENCE;
+
+ lastHandle = buf->handle;
+}
+
+void FramebufferSurface::freeBufferLocked(int slotIndex) {
+ ConsumerBase::freeBufferLocked(slotIndex);
+ if (slotIndex == mCurrentBufferSlot) {
+ mCurrentBufferSlot = BufferQueue::INVALID_BUFFER_SLOT;
+ }
+}
+
+status_t FramebufferSurface::setReleaseFenceFd(int fenceFd) {
+ status_t err = NO_ERROR;
+ if (fenceFd >= 0) {
+ sp<Fence> fence(new Fence(fenceFd));
+ if (mCurrentBufferSlot != BufferQueue::INVALID_BUFFER_SLOT) {
+#if ANDROID_VERSION >= 19
+ status_t err = addReleaseFence(mCurrentBufferSlot, mCurrentBuffer, fence);
+#else
+ status_t err = addReleaseFence(mCurrentBufferSlot, fence);
+#endif
+ ALOGE_IF(err, "setReleaseFenceFd: failed to add the fence: %s (%d)",
+ strerror(-err), err);
+ }
+ }
+ return err;
+}
+
+int FramebufferSurface::GetPrevDispAcquireFd() {
+ if (mPrevFBAcquireFence.get() && mPrevFBAcquireFence->isValid()) {
+ return mPrevFBAcquireFence->dup();
+ }
+ return -1;
+}
+
+void FramebufferSurface::onFrameCommitted() {
+ // XXX This role is almost same to setReleaseFenceFd().
+}
+
+status_t FramebufferSurface::compositionComplete()
+{
+ // Actual implementaiton is in GonkDisplay::SwapBuffers()
+ // XXX need to move that to here.
+ return NO_ERROR;
+}
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+// ----------------------------------------------------------------------------
diff --git a/widget/gonk/libdisplay/FramebufferSurface.h b/widget/gonk/libdisplay/FramebufferSurface.h
new file mode 100644
index 000000000..c1cc84272
--- /dev/null
+++ b/widget/gonk/libdisplay/FramebufferSurface.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SF_FRAMEBUFFER_SURFACE_H
+#define ANDROID_SF_FRAMEBUFFER_SURFACE_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "DisplaySurface.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+class Rect;
+class String8;
+
+// ---------------------------------------------------------------------------
+
+class FramebufferSurface : public DisplaySurface {
+public:
+ FramebufferSurface(int disp, uint32_t width, uint32_t height, uint32_t format, const sp<StreamConsumer>& sc);
+
+ // From DisplaySurface
+ virtual status_t beginFrame(bool mustRecompose);
+ virtual status_t prepareFrame(CompositionType compositionType);
+ virtual status_t compositionComplete();
+ virtual status_t advanceFrame();
+ virtual void onFrameCommitted();
+ // Cannot resize a buffers in a FramebufferSurface. Only works with virtual
+ // displays.
+ virtual void resizeBuffers(const uint32_t /*w*/, const uint32_t /*h*/) { };
+
+ // setReleaseFenceFd stores a fence file descriptor that will signal when the
+ // current buffer is no longer being read. This fence will be returned to
+ // the producer when the current buffer is released by updateTexImage().
+ // Multiple fences can be set for a given buffer; they will be merged into
+ // a single union fence. The SurfaceTexture will close the file descriptor
+ // when finished with it.
+ status_t setReleaseFenceFd(int fenceFd);
+
+ virtual int GetPrevDispAcquireFd();
+
+private:
+ virtual ~FramebufferSurface() { }; // this class cannot be overloaded
+
+#if ANDROID_VERSION >= 22
+ virtual void onFrameAvailable(const ::android::BufferItem &item);
+#else
+ virtual void onFrameAvailable();
+#endif
+ virtual void freeBufferLocked(int slotIndex);
+
+ // nextBuffer waits for and then latches the next buffer from the
+ // BufferQueue and releases the previously latched buffer to the
+ // BufferQueue. The new buffer is returned in the 'buffer' argument.
+ status_t nextBuffer(sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence);
+
+ // mDisplayType must match one of the HWC display types
+ int mDisplayType;
+
+ // mCurrentBufferIndex is the slot index of the current buffer or
+ // INVALID_BUFFER_SLOT to indicate that either there is no current buffer
+ // or the buffer is not associated with a slot.
+ int mCurrentBufferSlot;
+
+ // mCurrentBuffer is the current buffer or NULL to indicate that there is
+ // no current buffer.
+ sp<GraphicBuffer> mCurrentBuffer;
+
+ android::sp<android::Fence> mPrevFBAcquireFence;
+};
+
+// ---------------------------------------------------------------------------
+}; // namespace android
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_SF_FRAMEBUFFER_SURFACE_H
+
diff --git a/widget/gonk/libdisplay/GonkDisplay.h b/widget/gonk/libdisplay/GonkDisplay.h
new file mode 100644
index 000000000..96978a6e9
--- /dev/null
+++ b/widget/gonk/libdisplay/GonkDisplay.h
@@ -0,0 +1,91 @@
+/* Copyright 2013 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GONKDISPLAY_H
+#define GONKDISPLAY_H
+
+#include <system/window.h>
+#include <utils/StrongPointer.h>
+#include "mozilla/Types.h"
+
+namespace android {
+class DisplaySurface;
+class IGraphicBufferProducer;
+}
+
+namespace mozilla {
+
+typedef void * EGLDisplay;
+typedef void * EGLSurface;
+
+class MOZ_EXPORT GonkDisplay {
+public:
+ /**
+ * This enum is for types of display. DISPLAY_PRIMARY refers to the default
+ * built-in display, DISPLAY_EXTERNAL refers to displays connected with
+ * HDMI, and DISPLAY_VIRTUAL are displays which makes composited output
+ * available within the system. Currently, displays of external are detected
+ * via the hotplug detection in HWC, and displays of virtual are connected
+ * via Wifi Display.
+ */
+ enum DisplayType {
+ DISPLAY_PRIMARY,
+ DISPLAY_EXTERNAL,
+ DISPLAY_VIRTUAL,
+ NUM_DISPLAY_TYPES
+ };
+
+ struct NativeData {
+ android::sp<ANativeWindow> mNativeWindow;
+#if ANDROID_VERSION >= 17
+ android::sp<android::DisplaySurface> mDisplaySurface;
+#endif
+ float mXdpi;
+ };
+
+ virtual void SetEnabled(bool enabled) = 0;
+
+ typedef void (*OnEnabledCallbackType)(bool enabled);
+
+ virtual void OnEnabled(OnEnabledCallbackType callback) = 0;
+
+ virtual void* GetHWCDevice() = 0;
+
+ /**
+ * Only GonkDisplayICS uses arguments.
+ */
+ virtual bool SwapBuffers(EGLDisplay dpy, EGLSurface sur) = 0;
+
+ virtual ANativeWindowBuffer* DequeueBuffer() = 0;
+
+ virtual bool QueueBuffer(ANativeWindowBuffer* buf) = 0;
+
+ virtual void UpdateDispSurface(EGLDisplay dpy, EGLSurface sur) = 0;
+
+ virtual NativeData GetNativeData(
+ GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink = nullptr) = 0;
+
+ virtual void NotifyBootAnimationStopped() = 0;
+
+ float xdpi;
+ int32_t surfaceformat;
+};
+
+MOZ_EXPORT __attribute__ ((weak))
+GonkDisplay* GetGonkDisplay();
+
+}
+#endif /* GONKDISPLAY_H */
diff --git a/widget/gonk/libdisplay/GonkDisplayJB.cpp b/widget/gonk/libdisplay/GonkDisplayJB.cpp
new file mode 100644
index 000000000..197b85a47
--- /dev/null
+++ b/widget/gonk/libdisplay/GonkDisplayJB.cpp
@@ -0,0 +1,461 @@
+/* Copyright 2013 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GonkDisplayJB.h"
+#if ANDROID_VERSION == 17
+#include <gui/SurfaceTextureClient.h>
+#else
+#include <gui/Surface.h>
+#include <gui/GraphicBufferAlloc.h>
+#endif
+
+#include <hardware/hardware.h>
+#include <hardware/hwcomposer.h>
+#include <hardware/power.h>
+#include <suspend/autosuspend.h>
+
+#if ANDROID_VERSION >= 19
+#include "VirtualDisplaySurface.h"
+#endif
+#include "FramebufferSurface.h"
+#if ANDROID_VERSION == 17
+#include "GraphicBufferAlloc.h"
+#endif
+#include "mozilla/Assertions.h"
+
+#define DEFAULT_XDPI 75.0
+
+using namespace android;
+
+namespace mozilla {
+
+static GonkDisplayJB* sGonkDisplay = nullptr;
+
+GonkDisplayJB::GonkDisplayJB()
+ : mModule(nullptr)
+ , mFBModule(nullptr)
+ , mHwc(nullptr)
+ , mFBDevice(nullptr)
+ , mPowerModule(nullptr)
+ , mList(nullptr)
+ , mWidth(0)
+ , mHeight(0)
+ , mEnabledCallback(nullptr)
+{
+ int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &mFBModule);
+ ALOGW_IF(err, "%s module not found", GRALLOC_HARDWARE_MODULE_ID);
+ if (!err) {
+ err = framebuffer_open(mFBModule, &mFBDevice);
+ ALOGW_IF(err, "could not open framebuffer");
+ }
+
+ if (!err && mFBDevice) {
+ mWidth = mFBDevice->width;
+ mHeight = mFBDevice->height;
+ xdpi = mFBDevice->xdpi;
+ /* The emulator actually reports RGBA_8888, but EGL doesn't return
+ * any matching configuration. We force RGBX here to fix it. */
+ surfaceformat = HAL_PIXEL_FORMAT_RGBX_8888;
+ }
+
+ err = hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule);
+ ALOGW_IF(err, "%s module not found", HWC_HARDWARE_MODULE_ID);
+ if (!err) {
+ err = hwc_open_1(mModule, &mHwc);
+ ALOGE_IF(err, "%s device failed to initialize (%s)",
+ HWC_HARDWARE_COMPOSER, strerror(-err));
+ }
+
+ /* Fallback on the FB rendering path instead of trying to support HWC 1.0 */
+ if (!err && mHwc->common.version == HWC_DEVICE_API_VERSION_1_0) {
+ hwc_close_1(mHwc);
+ mHwc = nullptr;
+ }
+
+ if (!err && mHwc) {
+ if (mFBDevice) {
+ framebuffer_close(mFBDevice);
+ mFBDevice = nullptr;
+ }
+
+ int32_t values[3];
+ const uint32_t attrs[] = {
+ HWC_DISPLAY_WIDTH,
+ HWC_DISPLAY_HEIGHT,
+ HWC_DISPLAY_DPI_X,
+ HWC_DISPLAY_NO_ATTRIBUTE
+ };
+ mHwc->getDisplayAttributes(mHwc, 0, 0, attrs, values);
+
+ mWidth = values[0];
+ mHeight = values[1];
+ xdpi = values[2] / 1000.0f;
+ surfaceformat = HAL_PIXEL_FORMAT_RGBA_8888;
+ }
+
+ err = hw_get_module(POWER_HARDWARE_MODULE_ID,
+ (hw_module_t const**)&mPowerModule);
+ if (!err)
+ mPowerModule->init(mPowerModule);
+ ALOGW_IF(err, "Couldn't load %s module (%s)", POWER_HARDWARE_MODULE_ID, strerror(-err));
+
+ mAlloc = new GraphicBufferAlloc();
+
+ CreateFramebufferSurface(mSTClient, mDispSurface, mWidth, mHeight);
+
+ mList = (hwc_display_contents_1_t *)calloc(1, sizeof(*mList) + (sizeof(hwc_layer_1_t)*2));
+
+ uint32_t usage = GRALLOC_USAGE_HW_FB | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER;
+ if (mFBDevice) {
+ // If device uses fb, they can not use single buffer for boot animation
+ mSTClient->perform(mSTClient.get(), NATIVE_WINDOW_SET_BUFFER_COUNT, 2);
+ mSTClient->perform(mSTClient.get(), NATIVE_WINDOW_SET_USAGE, usage);
+ } else if (mHwc) {
+ PowerOnDisplay(HWC_DISPLAY_PRIMARY);
+ // For devices w/ hwc v1.0 or no hwc, this buffer can not be created,
+ // only create this buffer for devices w/ hwc version > 1.0.
+ CreateFramebufferSurface(mBootAnimSTClient, mBootAnimDispSurface, mWidth, mHeight);
+ }
+}
+
+GonkDisplayJB::~GonkDisplayJB()
+{
+ if (mHwc)
+ hwc_close_1(mHwc);
+ if (mFBDevice)
+ framebuffer_close(mFBDevice);
+ free(mList);
+}
+
+void
+GonkDisplayJB::CreateFramebufferSurface(android::sp<ANativeWindow>& aNativeWindow,
+ android::sp<android::DisplaySurface>& aDisplaySurface,
+ uint32_t aWidth,
+ uint32_t aHeight)
+{
+#if ANDROID_VERSION >= 21
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer, mAlloc);
+#elif ANDROID_VERSION >= 19
+ sp<BufferQueue> consumer = new BufferQueue(mAlloc);
+ sp<IGraphicBufferProducer> producer = consumer;
+#elif ANDROID_VERSION >= 18
+ sp<BufferQueue> consumer = new BufferQueue(true, mAlloc);
+ sp<IGraphicBufferProducer> producer = consumer;
+#else
+ sp<BufferQueue> consumer = new BufferQueue(true, mAlloc);
+#endif
+
+ aDisplaySurface = new FramebufferSurface(0, aWidth, aHeight, surfaceformat, consumer);
+
+#if ANDROID_VERSION == 17
+ aNativeWindow = new SurfaceTextureClient(
+ static_cast<sp<ISurfaceTexture>>(aDisplaySurface->getBufferQueue()));
+#else
+ aNativeWindow = new Surface(producer);
+#endif
+}
+
+void
+GonkDisplayJB::CreateVirtualDisplaySurface(android::IGraphicBufferProducer* aSink,
+ android::sp<ANativeWindow>& aNativeWindow,
+ android::sp<android::DisplaySurface>& aDisplaySurface)
+{
+#if ANDROID_VERSION >= 21
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer, mAlloc);
+#elif ANDROID_VERSION >= 19
+ sp<BufferQueue> consumer = new BufferQueue(mAlloc);
+ sp<IGraphicBufferProducer> producer = consumer;
+#endif
+
+#if ANDROID_VERSION >= 19
+ sp<VirtualDisplaySurface> virtualDisplay;
+ virtualDisplay = new VirtualDisplaySurface(-1, aSink, producer, consumer, String8("VirtualDisplaySurface"));
+ aDisplaySurface = virtualDisplay;
+ aNativeWindow = new Surface(virtualDisplay);
+#endif
+}
+
+void
+GonkDisplayJB::SetEnabled(bool enabled)
+{
+ if (enabled) {
+ autosuspend_disable();
+ mPowerModule->setInteractive(mPowerModule, true);
+ }
+
+ if (!enabled && mEnabledCallback) {
+ mEnabledCallback(enabled);
+ }
+
+#if ANDROID_VERSION >= 21
+ if (mHwc) {
+ if (mHwc->common.version >= HWC_DEVICE_API_VERSION_1_4) {
+ mHwc->setPowerMode(mHwc, HWC_DISPLAY_PRIMARY,
+ (enabled ? HWC_POWER_MODE_NORMAL : HWC_POWER_MODE_OFF));
+ } else {
+ mHwc->blank(mHwc, HWC_DISPLAY_PRIMARY, !enabled);
+ }
+ } else if (mFBDevice && mFBDevice->enableScreen) {
+ mFBDevice->enableScreen(mFBDevice, enabled);
+ }
+#else
+ if (mHwc && mHwc->blank) {
+ mHwc->blank(mHwc, HWC_DISPLAY_PRIMARY, !enabled);
+ } else if (mFBDevice && mFBDevice->enableScreen) {
+ mFBDevice->enableScreen(mFBDevice, enabled);
+ }
+#endif
+
+ if (enabled && mEnabledCallback) {
+ mEnabledCallback(enabled);
+ }
+
+ if (!enabled) {
+ autosuspend_enable();
+ mPowerModule->setInteractive(mPowerModule, false);
+ }
+}
+
+void
+GonkDisplayJB::OnEnabled(OnEnabledCallbackType callback)
+{
+ mEnabledCallback = callback;
+}
+
+void*
+GonkDisplayJB::GetHWCDevice()
+{
+ return mHwc;
+}
+
+bool
+GonkDisplayJB::SwapBuffers(EGLDisplay dpy, EGLSurface sur)
+{
+ // Should be called when composition rendering is complete for a frame.
+ // Only HWC v1.0 needs this call.
+ // HWC > v1.0 case, do not call compositionComplete().
+ // mFBDevice is present only when HWC is v1.0.
+ if (mFBDevice && mFBDevice->compositionComplete) {
+ mFBDevice->compositionComplete(mFBDevice);
+ }
+ return Post(mDispSurface->lastHandle, mDispSurface->GetPrevDispAcquireFd());
+}
+
+bool
+GonkDisplayJB::Post(buffer_handle_t buf, int fence)
+{
+ if (!mHwc) {
+ if (fence >= 0)
+ close(fence);
+ return !mFBDevice->post(mFBDevice, buf);
+ }
+
+ hwc_display_contents_1_t *displays[HWC_NUM_DISPLAY_TYPES] = {NULL};
+ const hwc_rect_t r = { 0, 0, static_cast<int>(mWidth), static_cast<int>(mHeight) };
+ displays[HWC_DISPLAY_PRIMARY] = mList;
+ mList->retireFenceFd = -1;
+ mList->numHwLayers = 2;
+ mList->flags = HWC_GEOMETRY_CHANGED;
+#if ANDROID_VERSION >= 18
+ mList->outbuf = nullptr;
+ mList->outbufAcquireFenceFd = -1;
+#endif
+ mList->hwLayers[0].compositionType = HWC_FRAMEBUFFER;
+ mList->hwLayers[0].hints = 0;
+ /* Skip this layer so the hwc module doesn't complain about null handles */
+ mList->hwLayers[0].flags = HWC_SKIP_LAYER;
+ mList->hwLayers[0].backgroundColor = {0};
+ mList->hwLayers[0].acquireFenceFd = -1;
+ mList->hwLayers[0].releaseFenceFd = -1;
+ /* hwc module checks displayFrame even though it shouldn't */
+ mList->hwLayers[0].displayFrame = r;
+ mList->hwLayers[1].compositionType = HWC_FRAMEBUFFER_TARGET;
+ mList->hwLayers[1].hints = 0;
+ mList->hwLayers[1].flags = 0;
+ mList->hwLayers[1].handle = buf;
+ mList->hwLayers[1].transform = 0;
+ mList->hwLayers[1].blending = HWC_BLENDING_NONE;
+#if ANDROID_VERSION >= 19
+ if (mHwc->common.version >= HWC_DEVICE_API_VERSION_1_3) {
+ mList->hwLayers[1].sourceCropf.left = 0;
+ mList->hwLayers[1].sourceCropf.top = 0;
+ mList->hwLayers[1].sourceCropf.right = mWidth;
+ mList->hwLayers[1].sourceCropf.bottom = mHeight;
+ } else {
+ mList->hwLayers[1].sourceCrop = r;
+ }
+#else
+ mList->hwLayers[1].sourceCrop = r;
+#endif
+ mList->hwLayers[1].displayFrame = r;
+ mList->hwLayers[1].visibleRegionScreen.numRects = 1;
+ mList->hwLayers[1].visibleRegionScreen.rects = &mList->hwLayers[1].displayFrame;
+ mList->hwLayers[1].acquireFenceFd = fence;
+ mList->hwLayers[1].releaseFenceFd = -1;
+#if ANDROID_VERSION >= 18
+ mList->hwLayers[1].planeAlpha = 0xFF;
+#endif
+ mHwc->prepare(mHwc, HWC_NUM_DISPLAY_TYPES, displays);
+ int err = mHwc->set(mHwc, HWC_NUM_DISPLAY_TYPES, displays);
+
+ if (!mBootAnimDispSurface.get()) {
+ mDispSurface->setReleaseFenceFd(mList->hwLayers[1].releaseFenceFd);
+ } else {
+ mBootAnimDispSurface->setReleaseFenceFd(mList->hwLayers[1].releaseFenceFd);
+ }
+
+ if (mList->retireFenceFd >= 0)
+ close(mList->retireFenceFd);
+ return !err;
+}
+
+ANativeWindowBuffer*
+GonkDisplayJB::DequeueBuffer()
+{
+ // Check for bootAnim or normal display flow.
+ sp<ANativeWindow> nativeWindow =
+ !mBootAnimSTClient.get() ? mSTClient : mBootAnimSTClient;
+
+ ANativeWindowBuffer *buf;
+ int fenceFd = -1;
+ nativeWindow->dequeueBuffer(nativeWindow.get(), &buf, &fenceFd);
+ sp<Fence> fence(new Fence(fenceFd));
+#if ANDROID_VERSION == 17
+ fence->waitForever(1000, "GonkDisplayJB_DequeueBuffer");
+ // 1000 is what Android uses. It is a warning timeout in ms.
+ // This timeout was removed in ANDROID_VERSION 18.
+#else
+ fence->waitForever("GonkDisplayJB_DequeueBuffer");
+#endif
+ return buf;
+}
+
+bool
+GonkDisplayJB::QueueBuffer(ANativeWindowBuffer* buf)
+{
+ bool success = false;
+ int error = DoQueueBuffer(buf);
+ // Check for bootAnim or normal display flow.
+ if (!mBootAnimSTClient.get()) {
+ success = Post(mDispSurface->lastHandle, mDispSurface->GetPrevDispAcquireFd());
+ } else {
+ success = Post(mBootAnimDispSurface->lastHandle, mBootAnimDispSurface->GetPrevDispAcquireFd());
+ }
+ return error == 0 && success;
+}
+
+int
+GonkDisplayJB::DoQueueBuffer(ANativeWindowBuffer* buf)
+{
+ int error = 0;
+ // Check for bootAnim or normal display flow.
+ if (!mBootAnimSTClient.get()) {
+ error = mSTClient->queueBuffer(mSTClient.get(), buf, -1);
+ } else {
+ error = mBootAnimSTClient->queueBuffer(mBootAnimSTClient.get(), buf, -1);
+ }
+ return error;
+}
+
+void
+GonkDisplayJB::UpdateDispSurface(EGLDisplay dpy, EGLSurface sur)
+{
+ if (sur != EGL_NO_SURFACE) {
+ eglSwapBuffers(dpy, sur);
+ } else {
+ // When BasicCompositor is used as Compositor,
+ // EGLSurface does not exit.
+ ANativeWindowBuffer* buf = DequeueBuffer();
+ DoQueueBuffer(buf);
+ }
+}
+
+void
+GonkDisplayJB::NotifyBootAnimationStopped()
+{
+ if (mBootAnimSTClient.get()) {
+ mBootAnimSTClient = nullptr;
+ mBootAnimDispSurface = nullptr;
+ }
+}
+
+void
+GonkDisplayJB::PowerOnDisplay(int aDpy)
+{
+ MOZ_ASSERT(mHwc);
+#if ANDROID_VERSION >= 21
+ if (mHwc->common.version >= HWC_DEVICE_API_VERSION_1_4) {
+ mHwc->setPowerMode(mHwc, aDpy, HWC_POWER_MODE_NORMAL);
+ } else {
+ mHwc->blank(mHwc, aDpy, 0);
+ }
+#else
+ mHwc->blank(mHwc, aDpy, 0);
+#endif
+}
+
+GonkDisplay::NativeData
+GonkDisplayJB::GetNativeData(GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink)
+{
+ NativeData data;
+
+ if (aDisplayType == DISPLAY_PRIMARY) {
+ data.mNativeWindow = mSTClient;
+ data.mDisplaySurface = mDispSurface;
+ data.mXdpi = xdpi;
+ } else if (aDisplayType == DISPLAY_EXTERNAL) {
+ int32_t values[3];
+ const uint32_t attrs[] = {
+ HWC_DISPLAY_WIDTH,
+ HWC_DISPLAY_HEIGHT,
+ HWC_DISPLAY_DPI_X,
+ HWC_DISPLAY_NO_ATTRIBUTE
+ };
+ mHwc->getDisplayAttributes(mHwc, aDisplayType, 0, attrs, values);
+ int width = values[0];
+ int height = values[1];
+ // FIXME!! values[2] returns 0 for external display, which doesn't
+ // sound right, Bug 1169176 is the follow-up bug for this issue.
+ data.mXdpi = values[2] ? values[2] / 1000.f : DEFAULT_XDPI;
+ PowerOnDisplay(HWC_DISPLAY_EXTERNAL);
+ CreateFramebufferSurface(data.mNativeWindow,
+ data.mDisplaySurface,
+ width,
+ height);
+ } else if (aDisplayType == DISPLAY_VIRTUAL) {
+ data.mXdpi = xdpi;
+ CreateVirtualDisplaySurface(aSink,
+ data.mNativeWindow,
+ data.mDisplaySurface);
+ }
+
+ return data;
+}
+
+__attribute__ ((visibility ("default")))
+GonkDisplay*
+GetGonkDisplay()
+{
+ if (!sGonkDisplay)
+ sGonkDisplay = new GonkDisplayJB();
+ return sGonkDisplay;
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/libdisplay/GonkDisplayJB.h b/widget/gonk/libdisplay/GonkDisplayJB.h
new file mode 100644
index 000000000..60bcdffc4
--- /dev/null
+++ b/widget/gonk/libdisplay/GonkDisplayJB.h
@@ -0,0 +1,85 @@
+/* Copyright 2013 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GONKDISPLAYJB_H
+#define GONKDISPLAYJB_H
+
+#include "DisplaySurface.h"
+#include "GonkDisplay.h"
+#include "hardware/hwcomposer.h"
+#include "hardware/power.h"
+#include "ui/Fence.h"
+#include "utils/RefBase.h"
+
+namespace mozilla {
+
+class MOZ_EXPORT GonkDisplayJB : public GonkDisplay {
+public:
+ GonkDisplayJB();
+ ~GonkDisplayJB();
+
+ virtual void SetEnabled(bool enabled);
+
+ virtual void OnEnabled(OnEnabledCallbackType callback);
+
+ virtual void* GetHWCDevice();
+
+ virtual bool SwapBuffers(EGLDisplay dpy, EGLSurface sur);
+
+ virtual ANativeWindowBuffer* DequeueBuffer();
+
+ virtual bool QueueBuffer(ANativeWindowBuffer* buf);
+
+ virtual void UpdateDispSurface(EGLDisplay dpy, EGLSurface sur);
+
+ bool Post(buffer_handle_t buf, int fence);
+
+ virtual NativeData GetNativeData(
+ GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink = nullptr);
+
+ virtual void NotifyBootAnimationStopped();
+
+private:
+ void CreateFramebufferSurface(android::sp<ANativeWindow>& aNativeWindow,
+ android::sp<android::DisplaySurface>& aDisplaySurface,
+ uint32_t aWidth, uint32_t aHeight);
+ void CreateVirtualDisplaySurface(android::IGraphicBufferProducer* aSink,
+ android::sp<ANativeWindow>& aNativeWindow,
+ android::sp<android::DisplaySurface>& aDisplaySurface);
+
+ void PowerOnDisplay(int aDpy);
+
+ int DoQueueBuffer(ANativeWindowBuffer* buf);
+
+ hw_module_t const* mModule;
+ hw_module_t const* mFBModule;
+ hwc_composer_device_1_t* mHwc;
+ framebuffer_device_t* mFBDevice;
+ power_module_t* mPowerModule;
+ android::sp<android::DisplaySurface> mDispSurface;
+ android::sp<ANativeWindow> mSTClient;
+ android::sp<android::DisplaySurface> mBootAnimDispSurface;
+ android::sp<ANativeWindow> mBootAnimSTClient;
+ android::sp<android::IGraphicBufferAlloc> mAlloc;
+ hwc_display_contents_1_t* mList;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ OnEnabledCallbackType mEnabledCallback;
+};
+
+}
+
+#endif /* GONKDISPLAYJB_H */
diff --git a/widget/gonk/libdisplay/GraphicBufferAlloc.cpp b/widget/gonk/libdisplay/GraphicBufferAlloc.cpp
new file mode 100644
index 000000000..5722b7fe3
--- /dev/null
+++ b/widget/gonk/libdisplay/GraphicBufferAlloc.cpp
@@ -0,0 +1,53 @@
+/*
+ **
+ ** Copyright 2012 The Android Open Source Project
+ **
+ ** Licensed under the Apache License Version 2.0(the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing software
+ ** distributed under the License is distributed on an "AS IS" BASIS
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include <cutils/log.h>
+
+#include <ui/GraphicBuffer.h>
+
+#include "GraphicBufferAlloc.h"
+
+// ----------------------------------------------------------------------------
+namespace android {
+// ----------------------------------------------------------------------------
+
+GraphicBufferAlloc::GraphicBufferAlloc() {
+}
+
+GraphicBufferAlloc::~GraphicBufferAlloc() {
+}
+
+sp<GraphicBuffer> GraphicBufferAlloc::createGraphicBuffer(uint32_t w, uint32_t h,
+ PixelFormat format, uint32_t usage, status_t* error) {
+ sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(w, h, format, usage));
+ status_t err = graphicBuffer->initCheck();
+ *error = err;
+ if (err != 0 || graphicBuffer->handle == 0) {
+ if (err == NO_MEMORY) {
+ GraphicBuffer::dumpAllocationsToSystemLog();
+ }
+ ALOGE("GraphicBufferAlloc::createGraphicBuffer(w=%d, h=%d) "
+ "failed (%s), handle=%p",
+ w, h, strerror(-err), graphicBuffer->handle);
+ return 0;
+ }
+ return graphicBuffer;
+}
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+// ----------------------------------------------------------------------------
diff --git a/widget/gonk/libdisplay/GraphicBufferAlloc.h b/widget/gonk/libdisplay/GraphicBufferAlloc.h
new file mode 100644
index 000000000..b08750c2f
--- /dev/null
+++ b/widget/gonk/libdisplay/GraphicBufferAlloc.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SF_GRAPHIC_BUFFER_ALLOC_H
+#define ANDROID_SF_GRAPHIC_BUFFER_ALLOC_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <gui/IGraphicBufferAlloc.h>
+#include <ui/PixelFormat.h>
+#include <utils/Errors.h>
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+class GraphicBuffer;
+
+class GraphicBufferAlloc : public BnGraphicBufferAlloc {
+public:
+ GraphicBufferAlloc();
+ virtual ~GraphicBufferAlloc();
+ virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h,
+ PixelFormat format, uint32_t usage, status_t* error);
+};
+
+
+// ---------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_SF_GRAPHIC_BUFFER_ALLOC_H
diff --git a/widget/gonk/libdisplay/VirtualDisplaySurface.cpp b/widget/gonk/libdisplay/VirtualDisplaySurface.cpp
new file mode 100644
index 000000000..746707885
--- /dev/null
+++ b/widget/gonk/libdisplay/VirtualDisplaySurface.cpp
@@ -0,0 +1,635 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#include "VirtualDisplaySurface.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+#if defined(FORCE_HWC_COPY_FOR_VIRTUAL_DISPLAYS)
+static const bool sForceHwcCopy = true;
+#else
+static const bool sForceHwcCopy = false;
+#endif
+
+#define VDS_LOGE(msg, ...) ALOGE("[%s] " msg, \
+ mDisplayName.string(), ##__VA_ARGS__)
+#define VDS_LOGW_IF(cond, msg, ...) ALOGW_IF(cond, "[%s] " msg, \
+ mDisplayName.string(), ##__VA_ARGS__)
+#define VDS_LOGV(msg, ...) ALOGV("[%s] " msg, \
+ mDisplayName.string(), ##__VA_ARGS__)
+
+__attribute__((unused))
+static const char* dbgCompositionTypeStr(DisplaySurface::CompositionType type) {
+ switch (type) {
+ case DisplaySurface::COMPOSITION_UNKNOWN: return "UNKNOWN";
+ case DisplaySurface::COMPOSITION_GLES: return "GLES";
+ case DisplaySurface::COMPOSITION_HWC: return "HWC";
+ case DisplaySurface::COMPOSITION_MIXED: return "MIXED";
+ default: return "<INVALID>";
+ }
+}
+
+VirtualDisplaySurface::VirtualDisplaySurface(int32_t dispId,
+ const sp<IGraphicBufferProducer>& sink,
+ const sp<IGraphicBufferProducer>& bqProducer,
+ const sp<StreamConsumer>& bqConsumer,
+ const String8& name)
+: DisplaySurface(bqConsumer),
+ mDisplayId(dispId),
+ mDisplayName(name),
+ mOutputUsage(GRALLOC_USAGE_HW_COMPOSER),
+ mProducerSlotSource(0),
+ mDbgState(DBG_STATE_IDLE),
+ mDbgLastCompositionType(COMPOSITION_UNKNOWN),
+ mMustRecompose(false)
+{
+ mSource[SOURCE_SINK] = sink;
+ mSource[SOURCE_SCRATCH] = bqProducer;
+
+ resetPerFrameState();
+
+ int sinkWidth, sinkHeight;
+ sink->query(NATIVE_WINDOW_WIDTH, &sinkWidth);
+ sink->query(NATIVE_WINDOW_HEIGHT, &sinkHeight);
+ mSinkBufferWidth = sinkWidth;
+ mSinkBufferHeight = sinkHeight;
+
+ // Pick the buffer format to request from the sink when not rendering to it
+ // with GLES. If the consumer needs CPU access, use the default format
+ // set by the consumer. Otherwise allow gralloc to decide the format based
+ // on usage bits.
+ int sinkUsage;
+ sink->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &sinkUsage);
+ if (sinkUsage & (GRALLOC_USAGE_SW_READ_MASK | GRALLOC_USAGE_SW_WRITE_MASK)) {
+ int sinkFormat;
+ sink->query(NATIVE_WINDOW_FORMAT, &sinkFormat);
+ mDefaultOutputFormat = sinkFormat;
+ } else {
+ mDefaultOutputFormat = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
+ }
+ mOutputFormat = mDefaultOutputFormat;
+
+ ConsumerBase::mName = String8::format("VDS: %s", mDisplayName.string());
+ mConsumer->setConsumerName(ConsumerBase::mName);
+ mConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER);
+ mConsumer->setDefaultBufferSize(sinkWidth, sinkHeight);
+ mConsumer->setDefaultMaxBufferCount(2);
+}
+
+VirtualDisplaySurface::~VirtualDisplaySurface() {
+}
+
+status_t VirtualDisplaySurface::beginFrame(bool mustRecompose) {
+ if (mDisplayId < 0)
+ return NO_ERROR;
+
+ mMustRecompose = mustRecompose;
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_IDLE,
+ "Unexpected beginFrame() in %s state", dbgStateStr());
+ mDbgState = DBG_STATE_BEGUN;
+
+ return refreshOutputBuffer();
+}
+
+status_t VirtualDisplaySurface::prepareFrame(CompositionType compositionType) {
+ if (mDisplayId < 0)
+ return NO_ERROR;
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_BEGUN,
+ "Unexpected prepareFrame() in %s state", dbgStateStr());
+ mDbgState = DBG_STATE_PREPARED;
+
+ mCompositionType = compositionType;
+ if (sForceHwcCopy && mCompositionType == COMPOSITION_GLES) {
+ // Some hardware can do RGB->YUV conversion more efficiently in hardware
+ // controlled by HWC than in hardware controlled by the video encoder.
+ // Forcing GLES-composed frames to go through an extra copy by the HWC
+ // allows the format conversion to happen there, rather than passing RGB
+ // directly to the consumer.
+ //
+ // On the other hand, when the consumer prefers RGB or can consume RGB
+ // inexpensively, this forces an unnecessary copy.
+ mCompositionType = COMPOSITION_MIXED;
+ }
+
+ if (mCompositionType != mDbgLastCompositionType) {
+ VDS_LOGV("prepareFrame: composition type changed to %s",
+ dbgCompositionTypeStr(mCompositionType));
+ mDbgLastCompositionType = mCompositionType;
+ }
+
+ if (mCompositionType != COMPOSITION_GLES &&
+ (mOutputFormat != mDefaultOutputFormat ||
+ mOutputUsage != GRALLOC_USAGE_HW_COMPOSER)) {
+ // We must have just switched from GLES-only to MIXED or HWC
+ // composition. Stop using the format and usage requested by the GLES
+ // driver; they may be suboptimal when HWC is writing to the output
+ // buffer. For example, if the output is going to a video encoder, and
+ // HWC can write directly to YUV, some hardware can skip a
+ // memory-to-memory RGB-to-YUV conversion step.
+ //
+ // If we just switched *to* GLES-only mode, we'll change the
+ // format/usage and get a new buffer when the GLES driver calls
+ // dequeueBuffer().
+ mOutputFormat = mDefaultOutputFormat;
+ mOutputUsage = GRALLOC_USAGE_HW_COMPOSER;
+ refreshOutputBuffer();
+ }
+
+ return NO_ERROR;
+}
+
+status_t VirtualDisplaySurface::compositionComplete() {
+ return NO_ERROR;
+}
+
+status_t VirtualDisplaySurface::advanceFrame() {
+ return NO_ERROR;
+
+// XXX Add HWC support
+
+#if 0
+ if (mDisplayId < 0)
+ return NO_ERROR;
+
+ if (mCompositionType == COMPOSITION_HWC) {
+ VDS_LOGW_IF(mDbgState != DBG_STATE_PREPARED,
+ "Unexpected advanceFrame() in %s state on HWC frame",
+ dbgStateStr());
+ } else {
+ VDS_LOGW_IF(mDbgState != DBG_STATE_GLES_DONE,
+ "Unexpected advanceFrame() in %s state on GLES/MIXED frame",
+ dbgStateStr());
+ }
+ mDbgState = DBG_STATE_HWC;
+
+ if (mOutputProducerSlot < 0 ||
+ (mCompositionType != COMPOSITION_HWC && mFbProducerSlot < 0)) {
+ // Last chance bailout if something bad happened earlier. For example,
+ // in a GLES configuration, if the sink disappears then dequeueBuffer
+ // will fail, the GLES driver won't queue a buffer, but SurfaceFlinger
+ // will soldier on. So we end up here without a buffer. There should
+ // be lots of scary messages in the log just before this.
+ VDS_LOGE("advanceFrame: no buffer, bailing out");
+ return NO_MEMORY;
+ }
+
+ sp<GraphicBuffer> fbBuffer = mFbProducerSlot >= 0 ?
+ mProducerBuffers[mFbProducerSlot] : sp<GraphicBuffer>(NULL);
+ sp<GraphicBuffer> outBuffer = mProducerBuffers[mOutputProducerSlot];
+ VDS_LOGV("advanceFrame: fb=%d(%p) out=%d(%p)",
+ mFbProducerSlot, fbBuffer.get(),
+ mOutputProducerSlot, outBuffer.get());
+
+ // At this point we know the output buffer acquire fence,
+ // so update HWC state with it.
+ mHwc.setOutputBuffer(mDisplayId, mOutputFence, outBuffer);
+
+ status_t result = NO_ERROR;
+ if (fbBuffer != NULL) {
+ result = mHwc.fbPost(mDisplayId, mFbFence, fbBuffer);
+ }
+
+ return result;
+#endif
+}
+
+void VirtualDisplaySurface::onFrameCommitted() {
+ return;
+
+// XXX Add HWC support
+
+#if 0
+ if (mDisplayId < 0)
+ return;
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_HWC,
+ "Unexpected onFrameCommitted() in %s state", dbgStateStr());
+ mDbgState = DBG_STATE_IDLE;
+
+ sp<Fence> fbFence = mHwc.getAndResetReleaseFence(mDisplayId);
+ if (mCompositionType == COMPOSITION_MIXED && mFbProducerSlot >= 0) {
+ // release the scratch buffer back to the pool
+ Mutex::Autolock lock(mMutex);
+ int sslot = mapProducer2SourceSlot(SOURCE_SCRATCH, mFbProducerSlot);
+ VDS_LOGV("onFrameCommitted: release scratch sslot=%d", sslot);
+ addReleaseFenceLocked(sslot, mProducerBuffers[mFbProducerSlot], fbFence);
+ releaseBufferLocked(sslot, mProducerBuffers[mFbProducerSlot],
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
+ }
+
+ if (mOutputProducerSlot >= 0) {
+ int sslot = mapProducer2SourceSlot(SOURCE_SINK, mOutputProducerSlot);
+ QueueBufferOutput qbo;
+ sp<Fence> outFence = mHwc.getLastRetireFence(mDisplayId);
+ VDS_LOGV("onFrameCommitted: queue sink sslot=%d", sslot);
+ if (mMustRecompose) {
+ status_t result = mSource[SOURCE_SINK]->queueBuffer(sslot,
+ QueueBufferInput(
+ systemTime(), false /* isAutoTimestamp */,
+ Rect(mSinkBufferWidth, mSinkBufferHeight),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0 /* transform */,
+ true /* async*/,
+ outFence),
+ &qbo);
+ if (result == NO_ERROR) {
+ updateQueueBufferOutput(qbo);
+ }
+ } else {
+ // If the surface hadn't actually been updated, then we only went
+ // through the motions of updating the display to keep our state
+ // machine happy. We cancel the buffer to avoid triggering another
+ // re-composition and causing an infinite loop.
+ mSource[SOURCE_SINK]->cancelBuffer(sslot, outFence);
+ }
+ }
+
+ resetPerFrameState();
+#endif
+}
+
+void VirtualDisplaySurface::resizeBuffers(const uint32_t w, const uint32_t h) {
+ uint32_t tmpW, tmpH, transformHint, numPendingBuffers;
+ mQueueBufferOutput.deflate(&tmpW, &tmpH, &transformHint, &numPendingBuffers);
+ mQueueBufferOutput.inflate(w, h, transformHint, numPendingBuffers);
+
+ mSinkBufferWidth = w;
+ mSinkBufferHeight = h;
+}
+
+status_t VirtualDisplaySurface::requestBuffer(int pslot,
+ sp<GraphicBuffer>* outBuf) {
+ if (mDisplayId < 0)
+ return mSource[SOURCE_SINK]->requestBuffer(pslot, outBuf);
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
+ "Unexpected requestBuffer pslot=%d in %s state",
+ pslot, dbgStateStr());
+
+ *outBuf = mProducerBuffers[pslot];
+ return NO_ERROR;
+}
+
+status_t VirtualDisplaySurface::setBufferCount(int bufferCount) {
+ return mSource[SOURCE_SINK]->setBufferCount(bufferCount);
+}
+
+status_t VirtualDisplaySurface::dequeueBuffer(Source source,
+ uint32_t format, uint32_t usage, int* sslot, sp<Fence>* fence) {
+ LOG_FATAL_IF(mDisplayId < 0, "mDisplayId=%d but should not be < 0.", mDisplayId);
+ // Don't let a slow consumer block us
+ bool async = (source == SOURCE_SINK);
+
+ status_t result = mSource[source]->dequeueBuffer(sslot, fence, async,
+ mSinkBufferWidth, mSinkBufferHeight, format, usage);
+ if (result < 0)
+ return result;
+ int pslot = mapSource2ProducerSlot(source, *sslot);
+ VDS_LOGV("dequeueBuffer(%s): sslot=%d pslot=%d result=%d",
+ dbgSourceStr(source), *sslot, pslot, result);
+ uint64_t sourceBit = static_cast<uint64_t>(source) << pslot;
+
+ if ((mProducerSlotSource & (1ULL << pslot)) != sourceBit) {
+ // This slot was previously dequeued from the other source; must
+ // re-request the buffer.
+ result |= BUFFER_NEEDS_REALLOCATION;
+ mProducerSlotSource &= ~(1ULL << pslot);
+ mProducerSlotSource |= sourceBit;
+ }
+
+ if (result & RELEASE_ALL_BUFFERS) {
+ for (uint32_t i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if ((mProducerSlotSource & (1ULL << i)) == sourceBit)
+ mProducerBuffers[i].clear();
+ }
+ }
+ if (result & BUFFER_NEEDS_REALLOCATION) {
+ result = mSource[source]->requestBuffer(*sslot, &mProducerBuffers[pslot]);
+ if (result < 0) {
+ mProducerBuffers[pslot].clear();
+ mSource[source]->cancelBuffer(*sslot, *fence);
+ return result;
+ }
+ VDS_LOGV("dequeueBuffer(%s): buffers[%d]=%p fmt=%d usage=%#x",
+ dbgSourceStr(source), pslot, mProducerBuffers[pslot].get(),
+ mProducerBuffers[pslot]->getPixelFormat(),
+ mProducerBuffers[pslot]->getUsage());
+ }
+
+ return result;
+}
+
+status_t VirtualDisplaySurface::dequeueBuffer(int* pslot, sp<Fence>* fence, bool async,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
+ if (mDisplayId < 0)
+ return mSource[SOURCE_SINK]->dequeueBuffer(pslot, fence, async, w, h, format, usage);
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_PREPARED,
+ "Unexpected dequeueBuffer() in %s state", dbgStateStr());
+ mDbgState = DBG_STATE_GLES;
+
+ VDS_LOGW_IF(!async, "EGL called dequeueBuffer with !async despite eglSwapInterval(0)");
+ VDS_LOGV("dequeueBuffer %dx%d fmt=%d usage=%#x", w, h, format, usage);
+
+ status_t result = NO_ERROR;
+ Source source = fbSourceForCompositionType(mCompositionType);
+
+ if (source == SOURCE_SINK) {
+
+ if (mOutputProducerSlot < 0) {
+ // Last chance bailout if something bad happened earlier. For example,
+ // in a GLES configuration, if the sink disappears then dequeueBuffer
+ // will fail, the GLES driver won't queue a buffer, but SurfaceFlinger
+ // will soldier on. So we end up here without a buffer. There should
+ // be lots of scary messages in the log just before this.
+ VDS_LOGE("dequeueBuffer: no buffer, bailing out");
+ return NO_MEMORY;
+ }
+
+ // We already dequeued the output buffer. If the GLES driver wants
+ // something incompatible, we have to cancel and get a new one. This
+ // will mean that HWC will see a different output buffer between
+ // prepare and set, but since we're in GLES-only mode already it
+ // shouldn't matter.
+
+ usage |= GRALLOC_USAGE_HW_COMPOSER;
+ const sp<GraphicBuffer>& buf = mProducerBuffers[mOutputProducerSlot];
+ if ((usage & ~buf->getUsage()) != 0 ||
+ (format != 0 && format != (uint32_t)buf->getPixelFormat()) ||
+ (w != 0 && w != mSinkBufferWidth) ||
+ (h != 0 && h != mSinkBufferHeight)) {
+ VDS_LOGV("dequeueBuffer: dequeueing new output buffer: "
+ "want %dx%d fmt=%d use=%#x, "
+ "have %dx%d fmt=%d use=%#x",
+ w, h, format, usage,
+ mSinkBufferWidth, mSinkBufferHeight,
+ buf->getPixelFormat(), buf->getUsage());
+ mOutputFormat = format;
+ mOutputUsage = usage;
+ result = refreshOutputBuffer();
+ if (result < 0)
+ return result;
+ }
+ }
+
+ if (source == SOURCE_SINK) {
+ *pslot = mOutputProducerSlot;
+ *fence = mOutputFence;
+ } else {
+ int sslot;
+ result = dequeueBuffer(source, format, usage, &sslot, fence);
+ if (result >= 0) {
+ *pslot = mapSource2ProducerSlot(source, sslot);
+ }
+ }
+ return result;
+}
+
+status_t VirtualDisplaySurface::detachBuffer(int /* slot */) {
+ VDS_LOGE("detachBuffer is not available for VirtualDisplaySurface");
+ return INVALID_OPERATION;
+}
+
+status_t VirtualDisplaySurface::detachNextBuffer(
+ sp<GraphicBuffer>* /* outBuffer */, sp<Fence>* /* outFence */) {
+ VDS_LOGE("detachNextBuffer is not available for VirtualDisplaySurface");
+ return INVALID_OPERATION;
+}
+
+status_t VirtualDisplaySurface::attachBuffer(int* /* outSlot */,
+ const sp<GraphicBuffer>& /* buffer */) {
+ VDS_LOGE("attachBuffer is not available for VirtualDisplaySurface");
+ return INVALID_OPERATION;
+}
+
+status_t VirtualDisplaySurface::queueBuffer(int pslot,
+ const QueueBufferInput& input, QueueBufferOutput* output) {
+ if (mDisplayId < 0)
+ return mSource[SOURCE_SINK]->queueBuffer(pslot, input, output);
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
+ "Unexpected queueBuffer(pslot=%d) in %s state", pslot,
+ dbgStateStr());
+ mDbgState = DBG_STATE_GLES_DONE;
+
+ VDS_LOGV("queueBuffer pslot=%d", pslot);
+
+ status_t result;
+ if (mCompositionType == COMPOSITION_MIXED) {
+ // Queue the buffer back into the scratch pool
+ QueueBufferOutput scratchQBO;
+ int sslot = mapProducer2SourceSlot(SOURCE_SCRATCH, pslot);
+ result = mSource[SOURCE_SCRATCH]->queueBuffer(sslot, input, &scratchQBO);
+ if (result != NO_ERROR)
+ return result;
+
+ // Now acquire the buffer from the scratch pool -- should be the same
+ // slot and fence as we just queued.
+ Mutex::Autolock lock(mMutex);
+ BufferQueue::BufferItem item;
+ result = acquireBufferLocked(&item, 0);
+ if (result != NO_ERROR)
+ return result;
+ VDS_LOGW_IF(item.mBuf != sslot,
+ "queueBuffer: acquired sslot %d from SCRATCH after queueing sslot %d",
+ item.mBuf, sslot);
+ mFbProducerSlot = mapSource2ProducerSlot(SOURCE_SCRATCH, item.mBuf);
+ mFbFence = mSlots[item.mBuf].mFence;
+
+ } else {
+ LOG_FATAL_IF(mCompositionType != COMPOSITION_GLES,
+ "Unexpected queueBuffer in state %s for compositionType %s",
+ dbgStateStr(), dbgCompositionTypeStr(mCompositionType));
+
+ // Extract the GLES release fence for HWC to acquire
+ int64_t timestamp;
+ bool isAutoTimestamp;
+ Rect crop;
+ int scalingMode;
+ uint32_t transform;
+ bool async;
+ input.deflate(&timestamp, &isAutoTimestamp, &crop, &scalingMode,
+ &transform, &async, &mFbFence);
+
+ mFbProducerSlot = pslot;
+ mOutputFence = mFbFence;
+ }
+
+ *output = mQueueBufferOutput;
+ return NO_ERROR;
+}
+
+void VirtualDisplaySurface::cancelBuffer(int pslot, const sp<Fence>& fence) {
+ if (mDisplayId < 0)
+ return mSource[SOURCE_SINK]->cancelBuffer(mapProducer2SourceSlot(SOURCE_SINK, pslot), fence);
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
+ "Unexpected cancelBuffer(pslot=%d) in %s state", pslot,
+ dbgStateStr());
+ VDS_LOGV("cancelBuffer pslot=%d", pslot);
+ Source source = fbSourceForCompositionType(mCompositionType);
+ return mSource[source]->cancelBuffer(
+ mapProducer2SourceSlot(source, pslot), fence);
+}
+
+int VirtualDisplaySurface::query(int what, int* value) {
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ *value = mSinkBufferWidth;
+ break;
+ case NATIVE_WINDOW_HEIGHT:
+ *value = mSinkBufferHeight;
+ break;
+ default:
+ return mSource[SOURCE_SINK]->query(what, value);
+ }
+ return NO_ERROR;
+}
+
+#if ANDROID_VERSION >= 21
+status_t VirtualDisplaySurface::connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp,
+ QueueBufferOutput* output) {
+ QueueBufferOutput qbo;
+ status_t result = mSource[SOURCE_SINK]->connect(listener, api,
+ producerControlledByApp, &qbo);
+ if (result == NO_ERROR) {
+ updateQueueBufferOutput(qbo);
+ *output = mQueueBufferOutput;
+ }
+ return result;
+}
+#else
+status_t VirtualDisplaySurface::connect(const sp<IBinder>& token,
+ int api, bool producerControlledByApp,
+ QueueBufferOutput* output) {
+ QueueBufferOutput qbo;
+ status_t result = mSource[SOURCE_SINK]->connect(token, api, producerControlledByApp, &qbo);
+ if (result == NO_ERROR) {
+ updateQueueBufferOutput(qbo);
+ *output = mQueueBufferOutput;
+ }
+ return result;
+}
+#endif
+
+status_t VirtualDisplaySurface::disconnect(int api) {
+ return mSource[SOURCE_SINK]->disconnect(api);
+}
+
+#if ANDROID_VERSION >= 21
+status_t VirtualDisplaySurface::setSidebandStream(const sp<NativeHandle>& /*stream*/) {
+ return INVALID_OPERATION;
+}
+#endif
+
+void VirtualDisplaySurface::allocateBuffers(bool /* async */,
+ uint32_t /* width */, uint32_t /* height */, uint32_t /* format */,
+ uint32_t /* usage */) {
+ // TODO: Should we actually allocate buffers for a virtual display?
+}
+
+void VirtualDisplaySurface::updateQueueBufferOutput(
+ const QueueBufferOutput& qbo) {
+ uint32_t w, h, transformHint, numPendingBuffers;
+ qbo.deflate(&w, &h, &transformHint, &numPendingBuffers);
+ mQueueBufferOutput.inflate(w, h, 0, numPendingBuffers);
+}
+
+void VirtualDisplaySurface::resetPerFrameState() {
+ mCompositionType = COMPOSITION_UNKNOWN;
+ mFbFence = Fence::NO_FENCE;
+ mOutputFence = Fence::NO_FENCE;
+ mOutputProducerSlot = -1;
+ mFbProducerSlot = -1;
+}
+
+status_t VirtualDisplaySurface::refreshOutputBuffer() {
+
+ return INVALID_OPERATION;
+
+// XXX Add HWC support
+
+#if 0
+ if (mOutputProducerSlot >= 0) {
+ mSource[SOURCE_SINK]->cancelBuffer(
+ mapProducer2SourceSlot(SOURCE_SINK, mOutputProducerSlot),
+ mOutputFence);
+ }
+
+ int sslot;
+ status_t result = dequeueBuffer(SOURCE_SINK, mOutputFormat, mOutputUsage,
+ &sslot, &mOutputFence);
+ if (result < 0)
+ return result;
+ mOutputProducerSlot = mapSource2ProducerSlot(SOURCE_SINK, sslot);
+
+ // On GLES-only frames, we don't have the right output buffer acquire fence
+ // until after GLES calls queueBuffer(). So here we just set the buffer
+ // (for use in HWC prepare) but not the fence; we'll call this again with
+ // the proper fence once we have it.
+ result = mHwc.setOutputBuffer(mDisplayId, Fence::NO_FENCE,
+ mProducerBuffers[mOutputProducerSlot]);
+
+ return result;
+#endif
+}
+
+// This slot mapping function is its own inverse, so two copies are unnecessary.
+// Both are kept to make the intent clear where the function is called, and for
+// the (unlikely) chance that we switch to a different mapping function.
+int VirtualDisplaySurface::mapSource2ProducerSlot(Source source, int sslot) {
+ if (source == SOURCE_SCRATCH) {
+ return BufferQueue::NUM_BUFFER_SLOTS - sslot - 1;
+ } else {
+ return sslot;
+ }
+}
+int VirtualDisplaySurface::mapProducer2SourceSlot(Source source, int pslot) {
+ return mapSource2ProducerSlot(source, pslot);
+}
+
+VirtualDisplaySurface::Source
+VirtualDisplaySurface::fbSourceForCompositionType(CompositionType type) {
+ return type == COMPOSITION_MIXED ? SOURCE_SCRATCH : SOURCE_SINK;
+}
+
+const char* VirtualDisplaySurface::dbgStateStr() const {
+ switch (mDbgState) {
+ case DBG_STATE_IDLE: return "IDLE";
+ case DBG_STATE_PREPARED: return "PREPARED";
+ case DBG_STATE_GLES: return "GLES";
+ case DBG_STATE_GLES_DONE: return "GLES_DONE";
+ case DBG_STATE_HWC: return "HWC";
+ default: return "INVALID";
+ }
+}
+
+const char* VirtualDisplaySurface::dbgSourceStr(Source s) {
+ switch (s) {
+ case SOURCE_SINK: return "SINK";
+ case SOURCE_SCRATCH: return "SCRATCH";
+ default: return "INVALID";
+ }
+}
+
+// ---------------------------------------------------------------------------
+} // namespace android
+// ---------------------------------------------------------------------------
diff --git a/widget/gonk/libdisplay/VirtualDisplaySurface.h b/widget/gonk/libdisplay/VirtualDisplaySurface.h
new file mode 100644
index 000000000..9125d8751
--- /dev/null
+++ b/widget/gonk/libdisplay/VirtualDisplaySurface.h
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
+#define ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
+
+#include <gui/IGraphicBufferProducer.h>
+
+#include "DisplaySurface.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+class HWComposer;
+class IProducerListener;
+
+/* This DisplaySurface implementation supports virtual displays, where GLES
+ * and/or HWC compose into a buffer that is then passed to an arbitrary
+ * consumer (the sink) running in another process.
+ *
+ * The simplest case is when the virtual display will never use the h/w
+ * composer -- either the h/w composer doesn't support writing to buffers, or
+ * there are more virtual displays than it supports simultaneously. In this
+ * case, the GLES driver works directly with the output buffer queue, and
+ * calls to the VirtualDisplay from SurfaceFlinger and DisplayHardware do
+ * nothing.
+ *
+ * If h/w composer might be used, then each frame will fall into one of three
+ * configurations: GLES-only, HWC-only, and MIXED composition. In all of these,
+ * we must provide a FB target buffer and output buffer for the HWC set() call.
+ *
+ * In GLES-only composition, the GLES driver is given a buffer from the sink to
+ * render into. When the GLES driver queues the buffer to the
+ * VirtualDisplaySurface, the VirtualDisplaySurface holds onto it instead of
+ * immediately queueing it to the sink. The buffer is used as both the FB
+ * target and output buffer for HWC, though on these frames the HWC doesn't
+ * do any work for this display and doesn't write to the output buffer. After
+ * composition is complete, the buffer is queued to the sink.
+ *
+ * In HWC-only composition, the VirtualDisplaySurface dequeues a buffer from
+ * the sink and passes it to HWC as both the FB target buffer and output
+ * buffer. The HWC doesn't need to read from the FB target buffer, but does
+ * write to the output buffer. After composition is complete, the buffer is
+ * queued to the sink.
+ *
+ * On MIXED frames, things become more complicated, since some h/w composer
+ * implementations can't read from and write to the same buffer. This class has
+ * an internal BufferQueue that it uses as a scratch buffer pool. The GLES
+ * driver is given a scratch buffer to render into. When it finishes rendering,
+ * the buffer is queued and then immediately acquired by the
+ * VirtualDisplaySurface. The scratch buffer is then used as the FB target
+ * buffer for HWC, and a separate buffer is dequeued from the sink and used as
+ * the HWC output buffer. When HWC composition is complete, the scratch buffer
+ * is released and the output buffer is queued to the sink.
+ */
+class VirtualDisplaySurface : public DisplaySurface,
+ public BnGraphicBufferProducer {
+public:
+ VirtualDisplaySurface(int32_t dispId,
+ const sp<IGraphicBufferProducer>& sink,
+ const sp<IGraphicBufferProducer>& bqProducer,
+ const sp<StreamConsumer>& bqConsumer,
+ const String8& name);
+
+ //
+ // DisplaySurface interface
+ //
+ virtual status_t beginFrame(bool mustRecompose);
+ virtual status_t prepareFrame(CompositionType compositionType);
+ virtual status_t compositionComplete();
+ virtual status_t advanceFrame();
+ virtual void onFrameCommitted();
+ virtual void resizeBuffers(const uint32_t w, const uint32_t h);
+
+ virtual status_t setReleaseFenceFd(int fenceFd) { return INVALID_OPERATION; }
+ virtual int GetPrevDispAcquireFd() { return -1; };
+
+private:
+ enum Source {SOURCE_SINK = 0, SOURCE_SCRATCH = 1};
+
+ virtual ~VirtualDisplaySurface();
+
+ //
+ // IGraphicBufferProducer interface, used by the GLES driver.
+ //
+ virtual status_t requestBuffer(int pslot, sp<GraphicBuffer>* outBuf);
+ virtual status_t setBufferCount(int bufferCount);
+ virtual status_t dequeueBuffer(int* pslot, sp<Fence>* fence, bool async,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
+ virtual status_t detachBuffer(int slot);
+ virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence);
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer);
+ virtual status_t queueBuffer(int pslot,
+ const QueueBufferInput& input, QueueBufferOutput* output);
+ virtual void cancelBuffer(int pslot, const sp<Fence>& fence);
+ virtual int query(int what, int* value);
+#if ANDROID_VERSION >= 21
+ virtual status_t connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp, QueueBufferOutput* output);
+#else
+ virtual status_t connect(const sp<IBinder>& token,
+ int api, bool producerControlledByApp, QueueBufferOutput* output);
+#endif
+ virtual status_t disconnect(int api);
+#if ANDROID_VERSION >= 21
+ virtual status_t setSidebandStream(const sp<NativeHandle>& stream);
+#endif
+ virtual void allocateBuffers(bool async, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t usage);
+
+ //
+ // Utility methods
+ //
+ static Source fbSourceForCompositionType(CompositionType type);
+ status_t dequeueBuffer(Source source, uint32_t format, uint32_t usage,
+ int* sslot, sp<Fence>* fence);
+ void updateQueueBufferOutput(const QueueBufferOutput& qbo);
+ void resetPerFrameState();
+ status_t refreshOutputBuffer();
+
+ // Both the sink and scratch buffer pools have their own set of slots
+ // ("source slots", or "sslot"). We have to merge these into the single
+ // set of slots used by the GLES producer ("producer slots" or "pslot") and
+ // internally in the VirtualDisplaySurface. To minimize the number of times
+ // a producer slot switches which source it comes from, we map source slot
+ // numbers to producer slot numbers differently for each source.
+ static int mapSource2ProducerSlot(Source source, int sslot);
+ static int mapProducer2SourceSlot(Source source, int pslot);
+
+ //
+ // Immutable after construction
+ //
+ const int32_t mDisplayId;
+ const String8 mDisplayName;
+ sp<IGraphicBufferProducer> mSource[2]; // indexed by SOURCE_*
+ uint32_t mDefaultOutputFormat;
+
+ //
+ // Inter-frame state
+ //
+
+ // To avoid buffer reallocations, we track the buffer usage and format
+ // we used on the previous frame and use it again on the new frame. If
+ // the composition type changes or the GLES driver starts requesting
+ // different usage/format, we'll get a new buffer.
+ uint32_t mOutputFormat;
+ uint32_t mOutputUsage;
+
+ // Since we present a single producer interface to the GLES driver, but
+ // are internally muxing between the sink and scratch producers, we have
+ // to keep track of which source last returned each producer slot from
+ // dequeueBuffer. Each bit in mProducerSlotSource corresponds to a producer
+ // slot. Both mProducerSlotSource and mProducerBuffers are indexed by a
+ // "producer slot"; see the mapSlot*() functions.
+ uint64_t mProducerSlotSource;
+ sp<GraphicBuffer> mProducerBuffers[BufferQueue::NUM_BUFFER_SLOTS];
+
+ // The QueueBufferOutput with the latest info from the sink, and with the
+ // transform hint cleared. Since we defer queueBuffer from the GLES driver
+ // to the sink, we have to return the previous version.
+ QueueBufferOutput mQueueBufferOutput;
+
+ // Details of the current sink buffer. These become valid when a buffer is
+ // dequeued from the sink, and are used when queueing the buffer.
+ uint32_t mSinkBufferWidth, mSinkBufferHeight;
+
+ //
+ // Intra-frame state
+ //
+
+ // Composition type and GLES buffer source for the current frame.
+ // Valid after prepareFrame(), cleared in onFrameCommitted.
+ CompositionType mCompositionType;
+
+ // mFbFence is the fence HWC should wait for before reading the framebuffer
+ // target buffer.
+ sp<Fence> mFbFence;
+
+ // mOutputFence is the fence HWC should wait for before writing to the
+ // output buffer.
+ sp<Fence> mOutputFence;
+
+ // Producer slot numbers for the buffers to use for HWC framebuffer target
+ // and output.
+ int mFbProducerSlot;
+ int mOutputProducerSlot;
+
+ // Debug only -- track the sequence of events in each frame so we can make
+ // sure they happen in the order we expect. This class implicitly models
+ // a state machine; this enum/variable makes it explicit.
+ //
+ // +-----------+-------------------+-------------+
+ // | State | Event || Next State |
+ // +-----------+-------------------+-------------+
+ // | IDLE | beginFrame || BEGUN |
+ // | BEGUN | prepareFrame || PREPARED |
+ // | PREPARED | dequeueBuffer [1] || GLES |
+ // | PREPARED | advanceFrame [2] || HWC |
+ // | GLES | queueBuffer || GLES_DONE |
+ // | GLES_DONE | advanceFrame || HWC |
+ // | HWC | onFrameCommitted || IDLE |
+ // +-----------+-------------------++------------+
+ // [1] COMPOSITION_GLES and COMPOSITION_MIXED frames.
+ // [2] COMPOSITION_HWC frames.
+ //
+ enum DbgState {
+ // no buffer dequeued, don't know anything about the next frame
+ DBG_STATE_IDLE,
+ // output buffer dequeued, framebuffer source not yet known
+ DBG_STATE_BEGUN,
+ // output buffer dequeued, framebuffer source known but not provided
+ // to GLES yet.
+ DBG_STATE_PREPARED,
+ // GLES driver has a buffer dequeued
+ DBG_STATE_GLES,
+ // GLES driver has queued the buffer, we haven't sent it to HWC yet
+ DBG_STATE_GLES_DONE,
+ // HWC has the buffer for this frame
+ DBG_STATE_HWC,
+ };
+ DbgState mDbgState;
+ CompositionType mDbgLastCompositionType;
+
+ const char* dbgStateStr() const;
+ static const char* dbgSourceStr(Source s);
+
+ bool mMustRecompose;
+};
+
+// ---------------------------------------------------------------------------
+} // namespace android
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
+
diff --git a/widget/gonk/libdisplay/moz.build b/widget/gonk/libdisplay/moz.build
new file mode 100644
index 000000000..917320592
--- /dev/null
+++ b/widget/gonk/libdisplay/moz.build
@@ -0,0 +1,59 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# Copyright 2013 Mozilla Foundation and Mozilla contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+SOURCES += [
+ 'BootAnimation.cpp',
+]
+
+if CONFIG['ANDROID_VERSION'] >= '19':
+ SOURCES += [
+ 'FramebufferSurface.cpp',
+ 'GonkDisplayJB.cpp',
+ 'VirtualDisplaySurface.cpp',
+ ]
+elif CONFIG['ANDROID_VERSION'] == '18':
+ SOURCES += [
+ 'FramebufferSurface.cpp',
+ 'GonkDisplayJB.cpp',
+ ]
+elif CONFIG['ANDROID_VERSION'] == '17':
+ SOURCES += [
+ 'FramebufferSurface.cpp',
+ 'GonkDisplayJB.cpp',
+ 'GraphicBufferAlloc.cpp',
+ ]
+elif CONFIG['ANDROID_VERSION'] and CONFIG['COMPILE_ENVIRONMENT']:
+ error('Unsupported platform version: %s' % (CONFIG['ANDROID_VERSION']))
+
+Library('display')
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FORCE_STATIC_LIB = True
+
+DEFINES['XPCOM_GLUE'] = True
+
+DISABLE_STL_WRAPPING = True
+
+LOCAL_INCLUDES += [
+ '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
+ 'frameworks/native/include/gui',
+ 'frameworks/native/opengl/include',
+ 'hardware/libhardware/include',
+ 'hardware/libhardware_legacy/include',
+ 'system/core/libsuspend/include',
+ ]
+]
diff --git a/widget/gonk/libui/EventHub.cpp b/widget/gonk/libui/EventHub.cpp
new file mode 100644
index 000000000..9da29bbeb
--- /dev/null
+++ b/widget/gonk/libui/EventHub.cpp
@@ -0,0 +1,1549 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EventHub"
+
+// #define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+#include "EventHub.h"
+
+#include <hardware_legacy/power.h>
+
+#include <cutils/properties.h>
+#include "cutils_log.h"
+#include <utils/Timers.h>
+#include <utils/threads.h>
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "KeyLayoutMap.h"
+#include "KeyCharacterMap.h"
+#include "VirtualKeyMap.h"
+
+#include <string.h>
+#include <stdint.h>
+#include <dirent.h>
+
+#include <sys/inotify.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/limits.h>
+#include "sha1.h"
+
+/* this macro is used to tell if "bit" is set in "array"
+ * it selects a byte from the array, and does a boolean AND
+ * operation with a byte that only has the relevant bit set.
+ * eg. to check for the 12th bit, we do (array[1] & 1<<4)
+ */
+#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
+
+/* this macro computes the number of bytes needed to represent a bit array of the specified size */
+#define sizeof_bit_array(bits) ((bits + 7) / 8)
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+
+namespace android {
+
+static const char *WAKE_LOCK_ID = "KeyEvents";
+static const char *DEVICE_PATH = "/dev/input";
+
+/* return the larger integer */
+static inline int max(int v1, int v2)
+{
+ return (v1 > v2) ? v1 : v2;
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static String8 sha1(const String8& in) {
+ SHA1_CTX ctx;
+ SHA1Init(&ctx);
+ SHA1Update(&ctx, reinterpret_cast<const u_char*>(in.string()), in.size());
+ u_char digest[SHA1_DIGEST_LENGTH];
+ SHA1Final(digest, &ctx);
+
+ String8 out;
+ for (size_t i = 0; i < SHA1_DIGEST_LENGTH; i++) {
+ out.appendFormat("%02x", digest[i]);
+ }
+ return out;
+}
+
+static void setDescriptor(InputDeviceIdentifier& identifier) {
+ // Compute a device descriptor that uniquely identifies the device.
+ // The descriptor is assumed to be a stable identifier. Its value should not
+ // change between reboots, reconnections, firmware updates or new releases of Android.
+ // Ideally, we also want the descriptor to be short and relatively opaque.
+ String8 rawDescriptor;
+ rawDescriptor.appendFormat(":%04x:%04x:", identifier.vendor, identifier.product);
+ if (!identifier.uniqueId.isEmpty()) {
+ rawDescriptor.append("uniqueId:");
+ rawDescriptor.append(identifier.uniqueId);
+ } if (identifier.vendor == 0 && identifier.product == 0) {
+ // If we don't know the vendor and product id, then the device is probably
+ // built-in so we need to rely on other information to uniquely identify
+ // the input device. Usually we try to avoid relying on the device name or
+ // location but for built-in input device, they are unlikely to ever change.
+ if (!identifier.name.isEmpty()) {
+ rawDescriptor.append("name:");
+ rawDescriptor.append(identifier.name);
+ } else if (!identifier.location.isEmpty()) {
+ rawDescriptor.append("location:");
+ rawDescriptor.append(identifier.location);
+ }
+ }
+ identifier.descriptor = sha1(rawDescriptor);
+ ALOGV("Created descriptor: raw=%s, cooked=%s", rawDescriptor.string(),
+ identifier.descriptor.string());
+}
+
+// --- Global Functions ---
+
+uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses) {
+ // Touch devices get dibs on touch-related axes.
+ if (deviceClasses & INPUT_DEVICE_CLASS_TOUCH) {
+ switch (axis) {
+ case ABS_X:
+ case ABS_Y:
+ case ABS_PRESSURE:
+ case ABS_TOOL_WIDTH:
+ case ABS_DISTANCE:
+ case ABS_TILT_X:
+ case ABS_TILT_Y:
+ case ABS_MT_SLOT:
+ case ABS_MT_TOUCH_MAJOR:
+ case ABS_MT_TOUCH_MINOR:
+ case ABS_MT_WIDTH_MAJOR:
+ case ABS_MT_WIDTH_MINOR:
+ case ABS_MT_ORIENTATION:
+ case ABS_MT_POSITION_X:
+ case ABS_MT_POSITION_Y:
+ case ABS_MT_TOOL_TYPE:
+ case ABS_MT_BLOB_ID:
+ case ABS_MT_TRACKING_ID:
+ case ABS_MT_PRESSURE:
+ case ABS_MT_DISTANCE:
+ return INPUT_DEVICE_CLASS_TOUCH;
+ }
+ }
+
+ // Joystick devices get the rest.
+ return deviceClasses & INPUT_DEVICE_CLASS_JOYSTICK;
+}
+
+// --- EventHub::Device ---
+
+EventHub::Device::Device(int fd, int32_t id, const String8& path,
+ const InputDeviceIdentifier& identifier) :
+ next(NULL),
+ fd(fd), id(id), path(path), identifier(identifier),
+ classes(0), configuration(NULL), virtualKeyMap(NULL),
+ ffEffectPlaying(false), ffEffectId(-1),
+ timestampOverrideSec(0), timestampOverrideUsec(0) {
+ memset(keyBitmask, 0, sizeof(keyBitmask));
+ memset(absBitmask, 0, sizeof(absBitmask));
+ memset(relBitmask, 0, sizeof(relBitmask));
+ memset(swBitmask, 0, sizeof(swBitmask));
+ memset(ledBitmask, 0, sizeof(ledBitmask));
+ memset(ffBitmask, 0, sizeof(ffBitmask));
+ memset(propBitmask, 0, sizeof(propBitmask));
+}
+
+EventHub::Device::~Device() {
+ close();
+ delete configuration;
+ delete virtualKeyMap;
+}
+
+void EventHub::Device::close() {
+ if (fd >= 0) {
+ ::close(fd);
+ fd = -1;
+ }
+}
+
+
+// --- EventHub ---
+
+const uint32_t EventHub::EPOLL_ID_INOTIFY;
+const uint32_t EventHub::EPOLL_ID_WAKE;
+const int EventHub::EPOLL_SIZE_HINT;
+const int EventHub::EPOLL_MAX_EVENTS;
+
+EventHub::EventHub(void) :
+ mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1),
+ mOpeningDevices(0), mClosingDevices(0),
+ mNeedToSendFinishedDeviceScan(false),
+ mNeedToReopenDevices(false), mNeedToScanDevices(true),
+ mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+
+ mEpollFd = epoll_create(EPOLL_SIZE_HINT);
+ LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
+
+ mINotifyFd = inotify_init();
+ int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
+ LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s. errno=%d",
+ DEVICE_PATH, errno);
+
+ struct epoll_event eventItem;
+ memset(&eventItem, 0, sizeof(eventItem));
+ eventItem.events = EPOLLIN;
+ eventItem.data.u32 = EPOLL_ID_INOTIFY;
+ result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
+
+ int wakeFds[2];
+ result = pipe(wakeFds);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
+
+ mWakeReadPipeFd = wakeFds[0];
+ mWakeWritePipeFd = wakeFds[1];
+
+ result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
+ errno);
+
+ result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
+ errno);
+
+ eventItem.data.u32 = EPOLL_ID_WAKE;
+ result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
+ errno);
+}
+
+EventHub::~EventHub(void) {
+ closeAllDevicesLocked();
+
+ while (mClosingDevices) {
+ Device* device = mClosingDevices;
+ mClosingDevices = device->next;
+ delete device;
+ }
+
+ ::close(mEpollFd);
+ ::close(mINotifyFd);
+ ::close(mWakeReadPipeFd);
+ ::close(mWakeWritePipeFd);
+
+ release_wake_lock(WAKE_LOCK_ID);
+}
+
+InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return InputDeviceIdentifier();
+ return device->identifier;
+}
+
+uint32_t EventHub::getDeviceClasses(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return 0;
+ return device->classes;
+}
+
+void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->configuration) {
+ *outConfiguration = *device->configuration;
+ } else {
+ outConfiguration->clear();
+ }
+}
+
+status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const {
+ outAxisInfo->clear();
+
+ if (axis >= 0 && axis <= ABS_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) {
+ struct input_absinfo info;
+ if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
+ ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
+ axis, device->identifier.name.string(), device->fd, errno);
+ return -errno;
+ }
+
+ if (info.minimum != info.maximum) {
+ outAxisInfo->valid = true;
+ outAxisInfo->minValue = info.minimum;
+ outAxisInfo->maxValue = info.maximum;
+ outAxisInfo->flat = info.flat;
+ outAxisInfo->fuzz = info.fuzz;
+ outAxisInfo->resolution = info.resolution;
+ }
+ return OK;
+ }
+ }
+ return -1;
+}
+
+bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
+ if (axis >= 0 && axis <= REL_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ return test_bit(axis, device->relBitmask);
+ }
+ }
+ return false;
+}
+
+bool EventHub::hasInputProperty(int32_t deviceId, int property) const {
+ if (property >= 0 && property <= INPUT_PROP_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ return test_bit(property, device->propBitmask);
+ }
+ }
+ return false;
+}
+
+int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+ if (scanCode >= 0 && scanCode <= KEY_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(scanCode, device->keyBitmask)) {
+ uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)];
+ memset(keyState, 0, sizeof(keyState));
+ if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) {
+ return test_bit(scanCode, keyState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && device->keyMap.haveKeyLayout()) {
+ Vector<int32_t> scanCodes;
+ device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode, &scanCodes);
+ if (scanCodes.size() != 0) {
+ uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)];
+ memset(keyState, 0, sizeof(keyState));
+ if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) {
+ for (size_t i = 0; i < scanCodes.size(); i++) {
+ int32_t sc = scanCodes.itemAt(i);
+ if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, keyState)) {
+ return AKEY_STATE_DOWN;
+ }
+ }
+ return AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
+ if (sw >= 0 && sw <= SW_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(sw, device->swBitmask)) {
+ uint8_t swState[sizeof_bit_array(SW_MAX + 1)];
+ memset(swState, 0, sizeof(swState));
+ if (ioctl(device->fd, EVIOCGSW(sizeof(swState)), swState) >= 0) {
+ return test_bit(sw, swState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
+ *outValue = 0;
+
+ if (axis >= 0 && axis <= ABS_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) {
+ struct input_absinfo info;
+ if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
+ ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
+ axis, device->identifier.name.string(), device->fd, errno);
+ return -errno;
+ }
+
+ *outValue = info.value;
+ return OK;
+ }
+ }
+ return -1;
+}
+
+bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->keyMap.haveKeyLayout()) {
+ Vector<int32_t> scanCodes;
+ for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) {
+ scanCodes.clear();
+
+ status_t err = device->keyMap.keyLayoutMap->findScanCodesForKey(
+ keyCodes[codeIndex], &scanCodes);
+ if (! err) {
+ // check the possible scan codes identified by the layout map against the
+ // map of codes actually emitted by the driver
+ for (size_t sc = 0; sc < scanCodes.size(); sc++) {
+ if (test_bit(scanCodes[sc], device->keyBitmask)) {
+ outFlags[codeIndex] = 1;
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+
+ if (device) {
+ // Check the key character map first.
+ sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();
+ if (kcm != NULL) {
+ if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
+ *outFlags = 0;
+ return NO_ERROR;
+ }
+ }
+
+ // Check the key layout next.
+ if (device->keyMap.haveKeyLayout()) {
+ if (!device->keyMap.keyLayoutMap->mapKey(
+ scanCode, usageCode, outKeycode, outFlags)) {
+ return NO_ERROR;
+ }
+ }
+ }
+
+ *outKeycode = 0;
+ *outFlags = 0;
+ return NAME_NOT_FOUND;
+}
+
+status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+
+ if (device && device->keyMap.haveKeyLayout()) {
+ status_t err = device->keyMap.keyLayoutMap->mapAxis(scanCode, outAxisInfo);
+ if (err == NO_ERROR) {
+ return NO_ERROR;
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+void EventHub::setExcludedDevices(const Vector<String8>& devices) {
+ AutoMutex _l(mLock);
+
+ mExcludedDevices = devices;
+}
+
+bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && scanCode >= 0 && scanCode <= KEY_MAX) {
+ if (test_bit(scanCode, device->keyBitmask)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && led >= 0 && led <= LED_MAX) {
+ if (test_bit(led, device->ledBitmask)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && led >= 0 && led <= LED_MAX) {
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_LED;
+ ev.code = led;
+ ev.value = on ? 1 : 0;
+
+ ssize_t nWrite;
+ do {
+ nWrite = write(device->fd, &ev, sizeof(struct input_event));
+ } while (nWrite == -1 && errno == EINTR);
+ }
+}
+
+void EventHub::getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const {
+ outVirtualKeys.clear();
+
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->virtualKeyMap) {
+ outVirtualKeys.appendVector(device->virtualKeyMap->getVirtualKeys());
+ }
+}
+
+sp<KeyCharacterMap> EventHub::getKeyCharacterMap(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ return device->getKeyCharacterMap();
+ }
+ return NULL;
+}
+
+bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId,
+ const sp<KeyCharacterMap>& map) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ if (map != device->overlayKeyMap) {
+ device->overlayKeyMap = map;
+ device->combinedKeyMap = KeyCharacterMap::combine(
+ device->keyMap.keyCharacterMap, map);
+ return true;
+ }
+ }
+ return false;
+}
+
+void EventHub::vibrate(int32_t deviceId, nsecs_t duration) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ ff_effect effect;
+ memset(&effect, 0, sizeof(effect));
+ effect.type = FF_RUMBLE;
+ effect.id = device->ffEffectId;
+ effect.u.rumble.strong_magnitude = 0xc000;
+ effect.u.rumble.weak_magnitude = 0xc000;
+ effect.replay.length = (duration + 999999LL) / 1000000LL;
+ effect.replay.delay = 0;
+ if (ioctl(device->fd, EVIOCSFF, &effect)) {
+ ALOGW("Could not upload force feedback effect to device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectId = effect.id;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 1;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not start force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectPlaying = true;
+ }
+}
+
+void EventHub::cancelVibrate(int32_t deviceId) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ if (device->ffEffectPlaying) {
+ device->ffEffectPlaying = false;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 0;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not stop force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ }
+ }
+}
+
+EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
+ if (deviceId == BUILT_IN_KEYBOARD_ID) {
+ deviceId = mBuiltInKeyboardId;
+ }
+ ssize_t index = mDevices.indexOfKey(deviceId);
+ return index >= 0 ? mDevices.valueAt(index) : NULL;
+}
+
+EventHub::Device* EventHub::getDeviceByPathLocked(const char* devicePath) const {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ Device* device = mDevices.valueAt(i);
+ if (device->path == devicePath) {
+ return device;
+ }
+ }
+ return NULL;
+}
+
+size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
+ ALOG_ASSERT(bufferSize >= 1);
+
+ AutoMutex _l(mLock);
+
+ struct input_event readBuffer[bufferSize];
+
+ RawEvent* event = buffer;
+ size_t capacity = bufferSize;
+ bool awoken = false;
+ for (;;) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ // Reopen input devices if needed.
+ if (mNeedToReopenDevices) {
+ mNeedToReopenDevices = false;
+
+ ALOGI("Reopening all input devices due to a configuration change.");
+
+ closeAllDevicesLocked();
+ mNeedToScanDevices = true;
+ break; // return to the caller before we actually rescan
+ }
+
+ // Report any devices that had last been added/removed.
+ while (mClosingDevices) {
+ Device* device = mClosingDevices;
+ ALOGV("Reporting device closed: id=%d, name=%s\n",
+ device->id, device->path.string());
+ mClosingDevices = device->next;
+ event->when = now;
+ event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;
+ event->type = DEVICE_REMOVED;
+ event += 1;
+ delete device;
+ mNeedToSendFinishedDeviceScan = true;
+ if (--capacity == 0) {
+ break;
+ }
+ }
+
+ if (mNeedToScanDevices) {
+ mNeedToScanDevices = false;
+ scanDevicesLocked();
+ mNeedToSendFinishedDeviceScan = true;
+ }
+
+ while (mOpeningDevices != NULL) {
+ Device* device = mOpeningDevices;
+ ALOGV("Reporting device opened: id=%d, name=%s\n",
+ device->id, device->path.string());
+ mOpeningDevices = device->next;
+ event->when = now;
+ event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
+ event->type = DEVICE_ADDED;
+ event += 1;
+ mNeedToSendFinishedDeviceScan = true;
+ if (--capacity == 0) {
+ break;
+ }
+ }
+
+ if (mNeedToSendFinishedDeviceScan) {
+ mNeedToSendFinishedDeviceScan = false;
+ event->when = now;
+ event->type = FINISHED_DEVICE_SCAN;
+ event += 1;
+ if (--capacity == 0) {
+ break;
+ }
+ }
+
+ // Grab the next input event.
+ bool deviceChanged = false;
+ while (mPendingEventIndex < mPendingEventCount) {
+ const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
+ if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
+ if (eventItem.events & EPOLLIN) {
+ mPendingINotify = true;
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
+ }
+ continue;
+ }
+
+ if (eventItem.data.u32 == EPOLL_ID_WAKE) {
+ if (eventItem.events & EPOLLIN) {
+ ALOGV("awoken after wake()");
+ awoken = true;
+ char buffer[16];
+ ssize_t nRead;
+ do {
+ nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
+ } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
+ eventItem.events);
+ }
+ continue;
+ }
+
+ ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
+ if (deviceIndex < 0) {
+ ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.",
+ eventItem.events, eventItem.data.u32);
+ continue;
+ }
+
+ Device* device = mDevices.valueAt(deviceIndex);
+ if (eventItem.events & EPOLLIN) {
+ int32_t readSize = read(device->fd, readBuffer,
+ sizeof(struct input_event) * capacity);
+ if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
+ // Device was removed before INotify noticed.
+ ALOGW("could not get event, removed? (fd: %d size: %d bufferSize: %d "
+ "capacity: %d errno: %d)\n",
+ device->fd, readSize, bufferSize, capacity, errno);
+ deviceChanged = true;
+ closeDeviceLocked(device);
+ } else if (readSize < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ ALOGW("could not get event (errno=%d)", errno);
+ }
+ } else if ((readSize % sizeof(struct input_event)) != 0) {
+ ALOGE("could not get event (wrong size: %d)", readSize);
+ } else {
+ int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
+
+ size_t count = size_t(readSize) / sizeof(struct input_event);
+ for (size_t i = 0; i < count; i++) {
+ struct input_event& iev = readBuffer[i];
+ ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",
+ device->path.string(),
+ (int) iev.time.tv_sec, (int) iev.time.tv_usec,
+ iev.type, iev.code, iev.value);
+
+ // Some input devices may have a better concept of the time
+ // when an input event was actually generated than the kernel
+ // which simply timestamps all events on entry to evdev.
+ // This is a custom Android extension of the input protocol
+ // mainly intended for use with uinput based device drivers.
+ if (iev.type == EV_MSC) {
+ if (iev.code == MSC_ANDROID_TIME_SEC) {
+ device->timestampOverrideSec = iev.value;
+ continue;
+ } else if (iev.code == MSC_ANDROID_TIME_USEC) {
+ device->timestampOverrideUsec = iev.value;
+ continue;
+ }
+ }
+ if (device->timestampOverrideSec || device->timestampOverrideUsec) {
+ iev.time.tv_sec = device->timestampOverrideSec;
+ iev.time.tv_usec = device->timestampOverrideUsec;
+ if (iev.type == EV_SYN && iev.code == SYN_REPORT) {
+ device->timestampOverrideSec = 0;
+ device->timestampOverrideUsec = 0;
+ }
+ ALOGV("applied override time %d.%06d",
+ int(iev.time.tv_sec), int(iev.time.tv_usec));
+ }
+
+#ifdef HAVE_POSIX_CLOCKS
+ // Use the time specified in the event instead of the current time
+ // so that downstream code can get more accurate estimates of
+ // event dispatch latency from the time the event is enqueued onto
+ // the evdev client buffer.
+ //
+ // The event's timestamp fortuitously uses the same monotonic clock
+ // time base as the rest of Android. The kernel event device driver
+ // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
+ // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
+ // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
+ // system call that also queries ktime_get_ts().
+ event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
+ + nsecs_t(iev.time.tv_usec) * 1000LL;
+ ALOGV("event time %lld, now %lld", event->when, now);
+
+ // Bug 7291243: Add a guard in case the kernel generates timestamps
+ // that appear to be far into the future because they were generated
+ // using the wrong clock source.
+ //
+ // This can happen because when the input device is initially opened
+ // it has a default clock source of CLOCK_REALTIME. Any input events
+ // enqueued right after the device is opened will have timestamps
+ // generated using CLOCK_REALTIME. We later set the clock source
+ // to CLOCK_MONOTONIC but it is already too late.
+ //
+ // Invalid input event timestamps can result in ANRs, crashes and
+ // and other issues that are hard to track down. We must not let them
+ // propagate through the system.
+ //
+ // Log a warning so that we notice the problem and recover gracefully.
+ if (event->when >= now + 10 * 1000000000LL) {
+ // Double-check. Time may have moved on.
+ nsecs_t time = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (event->when > time) {
+ ALOGW("An input event from %s has a timestamp that appears to "
+ "have been generated using the wrong clock source "
+ "(expected CLOCK_MONOTONIC): "
+ "event time %lld, current time %lld, call time %lld. "
+ "Using current time instead.",
+ device->path.string(), event->when, time, now);
+ event->when = time;
+ } else {
+ ALOGV("Event time is ok but failed the fast path and required "
+ "an extra call to systemTime: "
+ "event time %lld, current time %lld, call time %lld.",
+ event->when, time, now);
+ }
+ }
+#else
+ event->when = now;
+#endif
+ event->deviceId = deviceId;
+ event->type = iev.type;
+ event->code = iev.code;
+ event->value = iev.value;
+ event += 1;
+ capacity -= 1;
+ }
+ if (capacity == 0) {
+ // The result buffer is full. Reset the pending event index
+ // so we will try to read the device again on the next iteration.
+ mPendingEventIndex -= 1;
+ break;
+ }
+ }
+ } else if (eventItem.events & EPOLLHUP) {
+ ALOGI("Removing device %s due to epoll hang-up event.",
+ device->identifier.name.string());
+ deviceChanged = true;
+ closeDeviceLocked(device);
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for device %s.",
+ eventItem.events, device->identifier.name.string());
+ }
+ }
+
+ // readNotify() will modify the list of devices so this must be done after
+ // processing all other events to ensure that we read all remaining events
+ // before closing the devices.
+ if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
+ mPendingINotify = false;
+ readNotifyLocked();
+ deviceChanged = true;
+ }
+
+ // Report added or removed devices immediately.
+ if (deviceChanged) {
+ continue;
+ }
+
+ // Return now if we have collected any events or if we were explicitly awoken.
+ if (event != buffer || awoken) {
+ break;
+ }
+
+ // Poll for events. Mind the wake lock dance!
+ // We hold a wake lock at all times except during epoll_wait(). This works due to some
+ // subtle choreography. When a device driver has pending (unread) events, it acquires
+ // a kernel wake lock. However, once the last pending event has been read, the device
+ // driver will release the kernel wake lock. To prevent the system from going to sleep
+ // when this happens, the EventHub holds onto its own user wake lock while the client
+ // is processing events. Thus the system can only sleep if there are no events
+ // pending or currently being processed.
+ //
+ // The timeout is advisory only. If the device is asleep, it will not wake just to
+ // service the timeout.
+ mPendingEventIndex = 0;
+
+ mLock.unlock(); // release lock before poll, must be before release_wake_lock
+ release_wake_lock(WAKE_LOCK_ID);
+
+ int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
+
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+ mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock
+
+ if (pollResult == 0) {
+ // Timed out.
+ mPendingEventCount = 0;
+ break;
+ }
+
+ if (pollResult < 0) {
+ // An error occurred.
+ mPendingEventCount = 0;
+
+ // Sleep after errors to avoid locking up the system.
+ // Hopefully the error is transient.
+ if (errno != EINTR) {
+ ALOGW("poll failed (errno=%d)\n", errno);
+ usleep(100000);
+ }
+ } else {
+ // Some events occurred.
+ mPendingEventCount = size_t(pollResult);
+ }
+ }
+
+ // All done, return the number of events we read.
+ return event - buffer;
+}
+
+void EventHub::wake() {
+ ALOGV("wake() called");
+
+ ssize_t nWrite;
+ do {
+ nWrite = write(mWakeWritePipeFd, "W", 1);
+ } while (nWrite == -1 && errno == EINTR);
+
+ if (nWrite != 1 && errno != EAGAIN) {
+ ALOGW("Could not write wake signal, errno=%d", errno);
+ }
+}
+
+void EventHub::scanDevicesLocked() {
+ status_t res = scanDirLocked(DEVICE_PATH);
+ if(res < 0) {
+ ALOGE("scan dir failed for %s\n", DEVICE_PATH);
+ }
+ if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
+ createVirtualKeyboardLocked();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+static bool containsNonZeroByte(const uint8_t* array, uint32_t startIndex, uint32_t endIndex) {
+ const uint8_t* end = array + endIndex;
+ array += startIndex;
+ while (array != end) {
+ if (*(array++) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static const int32_t GAMEPAD_KEYCODES[] = {
+ AKEYCODE_BUTTON_A, AKEYCODE_BUTTON_B, AKEYCODE_BUTTON_C,
+ AKEYCODE_BUTTON_X, AKEYCODE_BUTTON_Y, AKEYCODE_BUTTON_Z,
+ AKEYCODE_BUTTON_L1, AKEYCODE_BUTTON_R1,
+ AKEYCODE_BUTTON_L2, AKEYCODE_BUTTON_R2,
+ AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR,
+ AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE,
+ AKEYCODE_BUTTON_1, AKEYCODE_BUTTON_2, AKEYCODE_BUTTON_3, AKEYCODE_BUTTON_4,
+ AKEYCODE_BUTTON_5, AKEYCODE_BUTTON_6, AKEYCODE_BUTTON_7, AKEYCODE_BUTTON_8,
+ AKEYCODE_BUTTON_9, AKEYCODE_BUTTON_10, AKEYCODE_BUTTON_11, AKEYCODE_BUTTON_12,
+ AKEYCODE_BUTTON_13, AKEYCODE_BUTTON_14, AKEYCODE_BUTTON_15, AKEYCODE_BUTTON_16,
+};
+
+status_t EventHub::openDeviceLocked(const char *devicePath) {
+ char buffer[80];
+
+ ALOGV("Opening device: %s", devicePath);
+
+ int fd = open(devicePath, O_RDWR | O_CLOEXEC);
+ if(fd < 0) {
+ ALOGE("could not open %s, %s\n", devicePath, strerror(errno));
+ return -1;
+ }
+
+ InputDeviceIdentifier identifier;
+
+ // Get device name.
+ if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.name.setTo(buffer);
+ }
+
+ // Check to see if the device is on our excluded list
+ for (size_t i = 0; i < mExcludedDevices.size(); i++) {
+ const String8& item = mExcludedDevices.itemAt(i);
+ if (identifier.name == item) {
+ ALOGI("ignoring event id %s driver %s\n", devicePath, item.string());
+ close(fd);
+ return -1;
+ }
+ }
+
+ // Get device driver version.
+ int driverVersion;
+ if(ioctl(fd, EVIOCGVERSION, &driverVersion)) {
+ ALOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ // Get device identifier.
+ struct input_id inputId;
+ if(ioctl(fd, EVIOCGID, &inputId)) {
+ ALOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno));
+ close(fd);
+ return -1;
+ }
+ identifier.bus = inputId.bustype;
+ identifier.product = inputId.product;
+ identifier.vendor = inputId.vendor;
+ identifier.version = inputId.version;
+
+ // Get device physical location.
+ if(ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.location.setTo(buffer);
+ }
+
+ // Get device unique id.
+ if(ioctl(fd, EVIOCGUNIQ(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get idstring for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.uniqueId.setTo(buffer);
+ }
+
+ // Fill in the descriptor.
+ setDescriptor(identifier);
+
+ // Make file descriptor non-blocking for use with poll().
+ if (fcntl(fd, F_SETFL, O_NONBLOCK)) {
+ ALOGE("Error %d making device file descriptor non-blocking.", errno);
+ close(fd);
+ return -1;
+ }
+
+ // Allocate device. (The device object takes ownership of the fd at this point.)
+ int32_t deviceId = mNextDeviceId++;
+ Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
+
+ ALOGV("add device %d: %s\n", deviceId, devicePath);
+ ALOGV(" bus: %04x\n"
+ " vendor %04x\n"
+ " product %04x\n"
+ " version %04x\n",
+ identifier.bus, identifier.vendor, identifier.product, identifier.version);
+ ALOGV(" name: \"%s\"\n", identifier.name.string());
+ ALOGV(" location: \"%s\"\n", identifier.location.string());
+ ALOGV(" unique id: \"%s\"\n", identifier.uniqueId.string());
+ ALOGV(" descriptor: \"%s\"\n", identifier.descriptor.string());
+ ALOGV(" driver: v%d.%d.%d\n",
+ driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff);
+
+ // Load the configuration file for the device.
+ loadConfigurationLocked(device);
+
+ // Figure out the kinds of events the device reports.
+ ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
+ ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
+ ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
+ ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
+ ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
+ ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
+ ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
+
+ // See if this is a keyboard. Ignore everything in the button range except for
+ // joystick and gamepad buttons which are handled like keyboards for the most part.
+ bool haveKeyboardKeys = containsNonZeroByte(device->keyBitmask, 0, sizeof_bit_array(BTN_MISC))
+ || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(KEY_OK),
+ sizeof_bit_array(KEY_MAX + 1));
+ bool haveGamepadButtons = containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_MISC),
+ sizeof_bit_array(BTN_MOUSE))
+ || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_JOYSTICK),
+ sizeof_bit_array(BTN_DIGI));
+ if (haveKeyboardKeys || haveGamepadButtons) {
+ device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+ }
+
+ // See if this is a cursor device such as a trackball or mouse.
+ if (test_bit(BTN_MOUSE, device->keyBitmask)
+ && test_bit(REL_X, device->relBitmask)
+ && test_bit(REL_Y, device->relBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_CURSOR;
+ }
+
+ // See if this is a touch pad.
+ // Is this a new modern multi-touch driver?
+ if (test_bit(ABS_MT_POSITION_X, device->absBitmask)
+ && test_bit(ABS_MT_POSITION_Y, device->absBitmask)) {
+ // Some joysticks such as the PS3 controller report axes that conflict
+ // with the ABS_MT range. Try to confirm that the device really is
+ // a touch screen.
+ if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) {
+ device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;
+ }
+ // Is this an old style single-touch driver?
+ } else if (test_bit(BTN_TOUCH, device->keyBitmask)
+ && test_bit(ABS_X, device->absBitmask)
+ && test_bit(ABS_Y, device->absBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_TOUCH;
+ }
+
+ // See if this device is a joystick.
+ // Assumes that joysticks always have gamepad buttons in order to distinguish them
+ // from other devices such as accelerometers that also have absolute axes.
+ if (haveGamepadButtons) {
+ uint32_t assumedClasses = device->classes | INPUT_DEVICE_CLASS_JOYSTICK;
+ for (int i = 0; i <= ABS_MAX; i++) {
+ if (test_bit(i, device->absBitmask)
+ && (getAbsAxisUsage(i, assumedClasses) & INPUT_DEVICE_CLASS_JOYSTICK)) {
+ device->classes = assumedClasses;
+ break;
+ }
+ }
+ }
+
+ // Check whether this device has switches.
+ for (int i = 0; i <= SW_MAX; i++) {
+ if (test_bit(i, device->swBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_SWITCH;
+ break;
+ }
+ }
+
+ // Check whether this device supports the vibrator.
+ if (test_bit(FF_RUMBLE, device->ffBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_VIBRATOR;
+ }
+
+ // Configure virtual keys.
+ if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) {
+ // Load the virtual keys for the touch screen, if any.
+ // We do this now so that we can make sure to load the keymap if necessary.
+ status_t status = loadVirtualKeyMapLocked(device);
+ if (!status) {
+ device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+ }
+ }
+
+ // Load the key map.
+ // We need to do this for joysticks too because the key layout may specify axes.
+ status_t keyMapStatus = NAME_NOT_FOUND;
+ if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
+ // Load the keymap for the device.
+ keyMapStatus = loadKeyMapLocked(device);
+ }
+
+ // Configure the keyboard, gamepad or virtual keyboard.
+ if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+ // Register the keyboard as a built-in keyboard if it is eligible.
+ if (!keyMapStatus
+ && mBuiltInKeyboardId == NO_BUILT_IN_KEYBOARD
+ && isEligibleBuiltInKeyboard(device->identifier,
+ device->configuration, &device->keyMap)) {
+ mBuiltInKeyboardId = device->id;
+ }
+
+ // 'Q' key support = cheap test of whether this is an alpha-capable kbd
+ if (hasKeycodeLocked(device, AKEYCODE_Q)) {
+ device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
+ }
+
+ // See if this device has a DPAD.
+ if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) {
+ device->classes |= INPUT_DEVICE_CLASS_DPAD;
+ }
+
+ // See if this device has a gamepad.
+ for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) {
+ if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
+ device->classes |= INPUT_DEVICE_CLASS_GAMEPAD;
+ break;
+ }
+ }
+
+ // Disable kernel key repeat since we handle it ourselves
+ unsigned int repeatRate[] = {0,0};
+ if (ioctl(fd, EVIOCSREP, repeatRate)) {
+ ALOGW("Unable to disable kernel key repeat for %s: %s", devicePath, strerror(errno));
+ }
+ }
+
+ // If the device isn't recognized as something we handle, don't monitor it.
+ if (device->classes == 0) {
+ ALOGV("Dropping device: id=%d, path='%s', name='%s'",
+ deviceId, devicePath, device->identifier.name.string());
+ delete device;
+ return -1;
+ }
+
+ // Determine whether the device is external or internal.
+ if (isExternalDeviceLocked(device)) {
+ device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;
+ }
+
+ // Register with epoll.
+ struct epoll_event eventItem;
+ memset(&eventItem, 0, sizeof(eventItem));
+ eventItem.events = EPOLLIN;
+ eventItem.data.u32 = deviceId;
+ if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
+ ALOGE("Could not add device fd to epoll instance. errno=%d", errno);
+ delete device;
+ return -1;
+ }
+
+ // Enable wake-lock behavior on kernels that support it.
+ // TODO: Only need this for devices that can really wake the system.
+ bool usingSuspendBlockIoctl = !ioctl(fd, EVIOCSSUSPENDBLOCK, 1);
+
+ // Tell the kernel that we want to use the monotonic clock for reporting timestamps
+ // associated with input events. This is important because the input system
+ // uses the timestamps extensively and assumes they were recorded using the monotonic
+ // clock.
+ //
+ // In older kernel, before Linux 3.4, there was no way to tell the kernel which
+ // clock to use to input event timestamps. The standard kernel behavior was to
+ // record a real time timestamp, which isn't what we want. Android kernels therefore
+ // contained a patch to the evdev_event() function in drivers/input/evdev.c to
+ // replace the call to do_gettimeofday() with ktime_get_ts() to cause the monotonic
+ // clock to be used instead of the real time clock.
+ //
+ // As of Linux 3.4, there is a new EVIOCSCLOCKID ioctl to set the desired clock.
+ // Therefore, we no longer require the Android-specific kernel patch described above
+ // as long as we make sure to set select the monotonic clock. We do that here.
+ int clockId = CLOCK_MONOTONIC;
+ bool usingClockIoctl = !ioctl(fd, EVIOCSCLOCKID, &clockId);
+
+ ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, "
+ "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, "
+ "usingSuspendBlockIoctl=%s, usingClockIoctl=%s",
+ deviceId, fd, devicePath, device->identifier.name.string(),
+ device->classes,
+ device->configurationFile.string(),
+ device->keyMap.keyLayoutFile.string(),
+ device->keyMap.keyCharacterMapFile.string(),
+ toString(mBuiltInKeyboardId == deviceId),
+ toString(usingSuspendBlockIoctl), toString(usingClockIoctl));
+
+ addDeviceLocked(device);
+ return 0;
+}
+
+void EventHub::createVirtualKeyboardLocked() {
+ InputDeviceIdentifier identifier;
+ identifier.name = "Virtual";
+ identifier.uniqueId = "<virtual>";
+ setDescriptor(identifier);
+
+ Device* device = new Device(-1, VIRTUAL_KEYBOARD_ID, String8("<virtual>"), identifier);
+ device->classes = INPUT_DEVICE_CLASS_KEYBOARD
+ | INPUT_DEVICE_CLASS_ALPHAKEY
+ | INPUT_DEVICE_CLASS_DPAD
+ | INPUT_DEVICE_CLASS_VIRTUAL;
+ loadKeyMapLocked(device);
+ addDeviceLocked(device);
+}
+
+void EventHub::addDeviceLocked(Device* device) {
+ mDevices.add(device->id, device);
+ device->next = mOpeningDevices;
+ mOpeningDevices = device;
+}
+
+void EventHub::loadConfigurationLocked(Device* device) {
+ device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
+ device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
+ if (device->configurationFile.isEmpty()) {
+ ALOGD("No input device configuration file found for device '%s'.",
+ device->identifier.name.string());
+ } else {
+ status_t status = PropertyMap::load(device->configurationFile,
+ &device->configuration);
+ if (status) {
+ ALOGE("Error loading input device configuration file for device '%s'. "
+ "Using default configuration.",
+ device->identifier.name.string());
+ }
+ }
+}
+
+status_t EventHub::loadVirtualKeyMapLocked(Device* device) {
+ // The virtual key map is supplied by the kernel as a system board property file.
+ String8 path;
+ path.append("/sys/board_properties/virtualkeys.");
+ path.append(device->identifier.name);
+ if (access(path.string(), R_OK)) {
+ return NAME_NOT_FOUND;
+ }
+ return VirtualKeyMap::load(path, &device->virtualKeyMap);
+}
+
+status_t EventHub::loadKeyMapLocked(Device* device) {
+ return device->keyMap.load(device->identifier, device->configuration);
+}
+
+bool EventHub::isExternalDeviceLocked(Device* device) {
+ if (device->configuration) {
+ bool value;
+ if (device->configuration->tryGetProperty(String8("device.internal"), value)) {
+ return !value;
+ }
+ }
+ return device->identifier.bus == BUS_USB || device->identifier.bus == BUS_BLUETOOTH;
+}
+
+bool EventHub::hasKeycodeLocked(Device* device, int keycode) const {
+ if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) {
+ return false;
+ }
+
+ Vector<int32_t> scanCodes;
+ device->keyMap.keyLayoutMap->findScanCodesForKey(keycode, &scanCodes);
+ const size_t N = scanCodes.size();
+ for (size_t i=0; i<N && i<=KEY_MAX; i++) {
+ int32_t sc = scanCodes.itemAt(i);
+ if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+status_t EventHub::closeDeviceByPathLocked(const char *devicePath) {
+ Device* device = getDeviceByPathLocked(devicePath);
+ if (device) {
+ closeDeviceLocked(device);
+ return 0;
+ }
+ ALOGV("Remove device: %s not found, device may already have been removed.", devicePath);
+ return -1;
+}
+
+void EventHub::closeAllDevicesLocked() {
+ while (mDevices.size() > 0) {
+ closeDeviceLocked(mDevices.valueAt(mDevices.size() - 1));
+ }
+}
+
+void EventHub::closeDeviceLocked(Device* device) {
+ ALOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x\n",
+ device->path.string(), device->identifier.name.string(), device->id,
+ device->fd, device->classes);
+
+ if (device->id == mBuiltInKeyboardId) {
+ ALOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this",
+ device->path.string(), mBuiltInKeyboardId);
+ mBuiltInKeyboardId = NO_BUILT_IN_KEYBOARD;
+ }
+
+ if (!device->isVirtual()) {
+ if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, device->fd, NULL)) {
+ ALOGW("Could not remove device fd from epoll instance. errno=%d", errno);
+ }
+ }
+
+ mDevices.removeItem(device->id);
+ device->close();
+
+ // Unlink for opening devices list if it is present.
+ Device* pred = NULL;
+ bool found = false;
+ for (Device* entry = mOpeningDevices; entry != NULL; ) {
+ if (entry == device) {
+ found = true;
+ break;
+ }
+ pred = entry;
+ entry = entry->next;
+ }
+ if (found) {
+ // Unlink the device from the opening devices list then delete it.
+ // We don't need to tell the client that the device was closed because
+ // it does not even know it was opened in the first place.
+ ALOGI("Device %s was immediately closed after opening.", device->path.string());
+ if (pred) {
+ pred->next = device->next;
+ } else {
+ mOpeningDevices = device->next;
+ }
+ delete device;
+ } else {
+ // Link into closing devices list.
+ // The device will be deleted later after we have informed the client.
+ device->next = mClosingDevices;
+ mClosingDevices = device;
+ }
+}
+
+status_t EventHub::readNotifyLocked() {
+ int res;
+ char devname[PATH_MAX];
+ char *filename;
+ char event_buf[512];
+ int event_size;
+ int event_pos = 0;
+ struct inotify_event *event;
+
+ ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd);
+ res = read(mINotifyFd, event_buf, sizeof(event_buf));
+ if(res < (int)sizeof(*event)) {
+ if(errno == EINTR)
+ return 0;
+ ALOGW("could not get event, %s\n", strerror(errno));
+ return -1;
+ }
+ //printf("got %d bytes of event information\n", res);
+
+ strcpy(devname, DEVICE_PATH);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+
+ while(res >= (int)sizeof(*event)) {
+ event = (struct inotify_event *)(event_buf + event_pos);
+ //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
+ if(event->len) {
+ strcpy(filename, event->name);
+ if(event->mask & IN_CREATE) {
+ openDeviceLocked(devname);
+ } else {
+ ALOGI("Removing device '%s' due to inotify event\n", devname);
+ closeDeviceByPathLocked(devname);
+ }
+ }
+ event_size = sizeof(*event) + event->len;
+ res -= event_size;
+ event_pos += event_size;
+ }
+ return 0;
+}
+
+status_t EventHub::scanDirLocked(const char *dirname)
+{
+ char devname[PATH_MAX];
+ char *filename;
+ DIR *dir;
+ struct dirent *de;
+ dir = opendir(dirname);
+ if(dir == NULL)
+ return -1;
+ strcpy(devname, dirname);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+ while((de = readdir(dir))) {
+ if(de->d_name[0] == '.' &&
+ (de->d_name[1] == '\0' ||
+ (de->d_name[1] == '.' && de->d_name[2] == '\0')))
+ continue;
+ strcpy(filename, de->d_name);
+ openDeviceLocked(devname);
+ }
+ closedir(dir);
+ return 0;
+}
+
+void EventHub::requestReopenDevices() {
+ ALOGV("requestReopenDevices() called");
+
+ AutoMutex _l(mLock);
+ mNeedToReopenDevices = true;
+}
+
+void EventHub::dump(String8& dump) {
+ dump.append("Event Hub State:\n");
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ dump.appendFormat(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId);
+
+ dump.append(INDENT "Devices:\n");
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ const Device* device = mDevices.valueAt(i);
+ if (mBuiltInKeyboardId == device->id) {
+ dump.appendFormat(INDENT2 "%d: %s (aka device 0 - built-in keyboard)\n",
+ device->id, device->identifier.name.string());
+ } else {
+ dump.appendFormat(INDENT2 "%d: %s\n", device->id,
+ device->identifier.name.string());
+ }
+ dump.appendFormat(INDENT3 "Classes: 0x%08x\n", device->classes);
+ dump.appendFormat(INDENT3 "Path: %s\n", device->path.string());
+ dump.appendFormat(INDENT3 "Descriptor: %s\n", device->identifier.descriptor.string());
+ dump.appendFormat(INDENT3 "Location: %s\n", device->identifier.location.string());
+ dump.appendFormat(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.string());
+ dump.appendFormat(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, "
+ "product=0x%04x, version=0x%04x\n",
+ device->identifier.bus, device->identifier.vendor,
+ device->identifier.product, device->identifier.version);
+ dump.appendFormat(INDENT3 "KeyLayoutFile: %s\n",
+ device->keyMap.keyLayoutFile.string());
+ dump.appendFormat(INDENT3 "KeyCharacterMapFile: %s\n",
+ device->keyMap.keyCharacterMapFile.string());
+ dump.appendFormat(INDENT3 "ConfigurationFile: %s\n",
+ device->configurationFile.string());
+ dump.appendFormat(INDENT3 "HaveKeyboardLayoutOverlay: %s\n",
+ toString(device->overlayKeyMap != NULL));
+ }
+ } // release lock
+}
+
+void EventHub::monitor() {
+ // Acquire and release the lock to ensure that the event hub has not deadlocked.
+ mLock.lock();
+ mLock.unlock();
+}
+
+
+}; // namespace android
diff --git a/widget/gonk/libui/EventHub.h b/widget/gonk/libui/EventHub.h
new file mode 100644
index 000000000..e4e658b21
--- /dev/null
+++ b/widget/gonk/libui/EventHub.h
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+#ifndef _RUNTIME_EVENT_HUB_H
+#define _RUNTIME_EVENT_HUB_H
+
+#include "Input.h"
+#include "InputDevice.h"
+#include "Keyboard.h"
+#include "KeyLayoutMap.h"
+#include "KeyCharacterMap.h"
+#include "VirtualKeyMap.h"
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include "cutils_log.h"
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include <utils/PropertyMap.h>
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+
+#include "linux_input.h"
+#include <sys/epoll.h>
+
+/* Convenience constants. */
+
+#define BTN_FIRST 0x100 // first button code
+#define BTN_LAST 0x15f // last button code
+
+/*
+ * These constants are used privately in Android to pass raw timestamps
+ * through evdev from uinput device drivers because there is currently no
+ * other way to transfer this information. The evdev driver automatically
+ * timestamps all input events with the time they were posted and clobbers
+ * whatever information was passed in.
+ *
+ * For the purposes of this hack, the timestamp is specified in the
+ * CLOCK_MONOTONIC timebase and is split into two EV_MSC events specifying
+ * seconds and microseconds.
+ */
+#define MSC_ANDROID_TIME_SEC 0x6
+#define MSC_ANDROID_TIME_USEC 0x7
+
+namespace android {
+
+enum {
+ // Device id of a special "virtual" keyboard that is always present.
+ VIRTUAL_KEYBOARD_ID = -1,
+ // Device id of the "built-in" keyboard if there is one.
+ BUILT_IN_KEYBOARD_ID = 0,
+};
+
+/*
+ * A raw event as retrieved from the EventHub.
+ */
+struct RawEvent {
+ nsecs_t when;
+ int32_t deviceId;
+ int32_t type;
+ int32_t code;
+ int32_t value;
+};
+
+/* Describes an absolute axis. */
+struct RawAbsoluteAxisInfo {
+ bool valid; // true if the information is valid, false otherwise
+
+ int32_t minValue; // minimum value
+ int32_t maxValue; // maximum value
+ int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8
+ int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
+ int32_t resolution; // resolution in units per mm or radians per mm
+
+ inline void clear() {
+ valid = false;
+ minValue = 0;
+ maxValue = 0;
+ flat = 0;
+ fuzz = 0;
+ resolution = 0;
+ }
+};
+
+/*
+ * Input device classes.
+ */
+enum {
+ /* The input device is a keyboard or has buttons. */
+ INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001,
+
+ /* The input device is an alpha-numeric keyboard (not just a dial pad). */
+ INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002,
+
+ /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */
+ INPUT_DEVICE_CLASS_TOUCH = 0x00000004,
+
+ /* The input device is a cursor device such as a trackball or mouse. */
+ INPUT_DEVICE_CLASS_CURSOR = 0x00000008,
+
+ /* The input device is a multi-touch touchscreen. */
+ INPUT_DEVICE_CLASS_TOUCH_MT = 0x00000010,
+
+ /* The input device is a directional pad (implies keyboard, has DPAD keys). */
+ INPUT_DEVICE_CLASS_DPAD = 0x00000020,
+
+ /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
+ INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040,
+
+ /* The input device has switches. */
+ INPUT_DEVICE_CLASS_SWITCH = 0x00000080,
+
+ /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
+ INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100,
+
+ /* The input device has a vibrator (supports FF_RUMBLE). */
+ INPUT_DEVICE_CLASS_VIBRATOR = 0x00000200,
+
+ /* The input device is virtual (not a real device, not part of UI configuration). */
+ INPUT_DEVICE_CLASS_VIRTUAL = 0x40000000,
+
+ /* The input device is external (not built-in). */
+ INPUT_DEVICE_CLASS_EXTERNAL = 0x80000000,
+};
+
+/*
+ * Gets the class that owns an axis, in cases where multiple classes might claim
+ * the same axis for different purposes.
+ */
+extern uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses);
+
+/*
+ * Grand Central Station for events.
+ *
+ * The event hub aggregates input events received across all known input
+ * devices on the system, including devices that may be emulated by the simulator
+ * environment. In addition, the event hub generates fake input events to indicate
+ * when devices are added or removed.
+ *
+ * The event hub provides a stream of input events (via the getEvent function).
+ * It also supports querying the current actual state of input devices such as identifying
+ * which keys are currently down. Finally, the event hub keeps track of the capabilities of
+ * individual input devices, such as their class and the set of key codes that they support.
+ */
+class EventHubInterface : public virtual RefBase {
+protected:
+ EventHubInterface() { }
+ virtual ~EventHubInterface() { }
+
+public:
+ // Synthetic raw event type codes produced when devices are added or removed.
+ enum {
+ // Sent when a device is added.
+ DEVICE_ADDED = 0x10000000,
+ // Sent when a device is removed.
+ DEVICE_REMOVED = 0x20000000,
+ // Sent when all added/removed devices from the most recent scan have been reported.
+ // This event is always sent at least once.
+ FINISHED_DEVICE_SCAN = 0x30000000,
+
+ FIRST_SYNTHETIC_EVENT = DEVICE_ADDED,
+ };
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
+
+ virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const = 0;
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0;
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
+
+ virtual bool hasInputProperty(int32_t deviceId, int property) const = 0;
+
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const = 0;
+
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
+ AxisInfo* outAxisInfo) const = 0;
+
+ // Sets devices that are excluded from opening.
+ // This can be used to ignore input devices for sensors.
+ virtual void setExcludedDevices(const Vector<String8>& devices) = 0;
+
+ /*
+ * Wait for events to become available and returns them.
+ * After returning, the EventHub holds onto a wake lock until the next call to getEvent.
+ * This ensures that the device will not go to sleep while the event is being processed.
+ * If the device needs to remain awake longer than that, then the caller is responsible
+ * for taking care of it (say, by poking the power manager user activity timer).
+ *
+ * The timeout is advisory only. If the device is asleep, it will not wake just to
+ * service the timeout.
+ *
+ * Returns the number of events obtained, or 0 if the timeout expired.
+ */
+ virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0;
+
+ /*
+ * Query current input state.
+ */
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
+ virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
+ int32_t* outValue) const = 0;
+
+ /*
+ * Examine key input devices for specific framework keycode support
+ */
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+ uint8_t* outFlags) const = 0;
+
+ virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0;
+ virtual bool hasLed(int32_t deviceId, int32_t led) const = 0;
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on) = 0;
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const = 0;
+
+ virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const = 0;
+ virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map) = 0;
+
+ /* Control the vibrator. */
+ virtual void vibrate(int32_t deviceId, nsecs_t duration) = 0;
+ virtual void cancelVibrate(int32_t deviceId) = 0;
+
+ /* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
+ virtual void requestReopenDevices() = 0;
+
+ /* Wakes up getEvents() if it is blocked on a read. */
+ virtual void wake() = 0;
+
+ /* Dump EventHub state to a string. */
+ virtual void dump(String8& dump) = 0;
+
+ /* Called by the heatbeat to ensures that the reader has not deadlocked. */
+ virtual void monitor() = 0;
+};
+
+class EventHub : public EventHubInterface
+{
+public:
+ EventHub();
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const;
+
+ virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const;
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const;
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const;
+
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const;
+
+ virtual bool hasInputProperty(int32_t deviceId, int property) const;
+
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const;
+
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
+ AxisInfo* outAxisInfo) const;
+
+ virtual void setExcludedDevices(const Vector<String8>& devices);
+
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const;
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const;
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const;
+ virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const;
+
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const;
+
+ virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize);
+
+ virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const;
+ virtual bool hasLed(int32_t deviceId, int32_t led) const;
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on);
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const;
+
+ virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const;
+ virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map);
+
+ virtual void vibrate(int32_t deviceId, nsecs_t duration);
+ virtual void cancelVibrate(int32_t deviceId);
+
+ virtual void requestReopenDevices();
+
+ virtual void wake();
+
+ virtual void dump(String8& dump);
+ virtual void monitor();
+
+protected:
+ virtual ~EventHub();
+
+private:
+ struct Device {
+ Device* next;
+
+ int fd; // may be -1 if device is virtual
+ const int32_t id;
+ const String8 path;
+ const InputDeviceIdentifier identifier;
+
+ uint32_t classes;
+
+ uint8_t keyBitmask[(KEY_MAX + 1) / 8];
+ uint8_t absBitmask[(ABS_MAX + 1) / 8];
+ uint8_t relBitmask[(REL_MAX + 1) / 8];
+ uint8_t swBitmask[(SW_MAX + 1) / 8];
+ uint8_t ledBitmask[(LED_MAX + 1) / 8];
+ uint8_t ffBitmask[(FF_MAX + 1) / 8];
+ uint8_t propBitmask[(INPUT_PROP_MAX + 1) / 8];
+
+ String8 configurationFile;
+ PropertyMap* configuration;
+ VirtualKeyMap* virtualKeyMap;
+ KeyMap keyMap;
+
+ sp<KeyCharacterMap> overlayKeyMap;
+ sp<KeyCharacterMap> combinedKeyMap;
+
+ bool ffEffectPlaying;
+ int16_t ffEffectId; // initially -1
+
+ int32_t timestampOverrideSec;
+ int32_t timestampOverrideUsec;
+
+ Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier);
+ ~Device();
+
+ void close();
+
+ inline bool isVirtual() const { return fd < 0; }
+
+ const sp<KeyCharacterMap>& getKeyCharacterMap() const {
+ if (combinedKeyMap != NULL) {
+ return combinedKeyMap;
+ }
+ return keyMap.keyCharacterMap;
+ }
+ };
+
+ status_t openDeviceLocked(const char *devicePath);
+ void createVirtualKeyboardLocked();
+ void addDeviceLocked(Device* device);
+
+ status_t closeDeviceByPathLocked(const char *devicePath);
+ void closeDeviceLocked(Device* device);
+ void closeAllDevicesLocked();
+
+ status_t scanDirLocked(const char *dirname);
+ void scanDevicesLocked();
+ status_t readNotifyLocked();
+
+ Device* getDeviceLocked(int32_t deviceId) const;
+ Device* getDeviceByPathLocked(const char* devicePath) const;
+
+ bool hasKeycodeLocked(Device* device, int keycode) const;
+
+ void loadConfigurationLocked(Device* device);
+ status_t loadVirtualKeyMapLocked(Device* device);
+ status_t loadKeyMapLocked(Device* device);
+
+ bool isExternalDeviceLocked(Device* device);
+
+ // Protect all internal state.
+ mutable Mutex mLock;
+
+ // The actual id of the built-in keyboard, or NO_BUILT_IN_KEYBOARD if none.
+ // EventHub remaps the built-in keyboard to id 0 externally as required by the API.
+ enum {
+ // Must not conflict with any other assigned device ids, including
+ // the virtual keyboard id (-1).
+ NO_BUILT_IN_KEYBOARD = -2,
+ };
+ int32_t mBuiltInKeyboardId;
+
+ int32_t mNextDeviceId;
+
+ KeyedVector<int32_t, Device*> mDevices;
+
+ Device *mOpeningDevices;
+ Device *mClosingDevices;
+
+ bool mNeedToSendFinishedDeviceScan;
+ bool mNeedToReopenDevices;
+ bool mNeedToScanDevices;
+ Vector<String8> mExcludedDevices;
+
+ int mEpollFd;
+ int mINotifyFd;
+ int mWakeReadPipeFd;
+ int mWakeWritePipeFd;
+
+ // Ids used for epoll notifications not associated with devices.
+ static const uint32_t EPOLL_ID_INOTIFY = 0x80000001;
+ static const uint32_t EPOLL_ID_WAKE = 0x80000002;
+
+ // Epoll FD list size hint.
+ static const int EPOLL_SIZE_HINT = 8;
+
+ // Maximum number of signalled FDs to handle at a time.
+ static const int EPOLL_MAX_EVENTS = 16;
+
+ // The array of pending epoll events and the index of the next event to be handled.
+ struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];
+ size_t mPendingEventCount;
+ size_t mPendingEventIndex;
+ bool mPendingINotify;
+};
+
+}; // namespace android
+
+#endif // _RUNTIME_EVENT_HUB_H
diff --git a/widget/gonk/libui/Input.cpp b/widget/gonk/libui/Input.cpp
new file mode 100644
index 000000000..2208191e6
--- /dev/null
+++ b/widget/gonk/libui/Input.cpp
@@ -0,0 +1,635 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Input"
+//#define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+#include <math.h>
+#include <limits.h>
+
+#include "Input.h"
+
+#ifdef HAVE_ANDROID_OS
+#include <binder/Parcel.h>
+
+#include "SkPoint.h"
+#include "SkMatrix.h"
+#include "SkScalar.h"
+#endif
+
+namespace android {
+
+// --- InputEvent ---
+
+void InputEvent::initialize(int32_t deviceId, int32_t source) {
+ mDeviceId = deviceId;
+ mSource = source;
+}
+
+void InputEvent::initialize(const InputEvent& from) {
+ mDeviceId = from.mDeviceId;
+ mSource = from.mSource;
+}
+
+// --- KeyEvent ---
+
+bool KeyEvent::hasDefaultAction(int32_t keyCode) {
+ switch (keyCode) {
+ case AKEYCODE_HOME:
+ case AKEYCODE_BACK:
+ case AKEYCODE_CALL:
+ case AKEYCODE_ENDCALL:
+ case AKEYCODE_VOLUME_UP:
+ case AKEYCODE_VOLUME_DOWN:
+ case AKEYCODE_VOLUME_MUTE:
+ case AKEYCODE_POWER:
+ case AKEYCODE_CAMERA:
+ case AKEYCODE_HEADSETHOOK:
+ case AKEYCODE_MENU:
+ case AKEYCODE_NOTIFICATION:
+ case AKEYCODE_FOCUS:
+ case AKEYCODE_SEARCH:
+ case AKEYCODE_MEDIA_PLAY:
+ case AKEYCODE_MEDIA_PAUSE:
+ case AKEYCODE_MEDIA_PLAY_PAUSE:
+ case AKEYCODE_MEDIA_STOP:
+ case AKEYCODE_MEDIA_NEXT:
+ case AKEYCODE_MEDIA_PREVIOUS:
+ case AKEYCODE_MEDIA_REWIND:
+ case AKEYCODE_MEDIA_RECORD:
+ case AKEYCODE_MEDIA_FAST_FORWARD:
+ case AKEYCODE_MUTE:
+ case AKEYCODE_BRIGHTNESS_DOWN:
+ case AKEYCODE_BRIGHTNESS_UP:
+ return true;
+ }
+
+ return false;
+}
+
+bool KeyEvent::hasDefaultAction() const {
+ return hasDefaultAction(getKeyCode());
+}
+
+bool KeyEvent::isSystemKey(int32_t keyCode) {
+ switch (keyCode) {
+ case AKEYCODE_MENU:
+ case AKEYCODE_SOFT_RIGHT:
+ case AKEYCODE_HOME:
+ case AKEYCODE_BACK:
+ case AKEYCODE_CALL:
+ case AKEYCODE_ENDCALL:
+ case AKEYCODE_VOLUME_UP:
+ case AKEYCODE_VOLUME_DOWN:
+ case AKEYCODE_VOLUME_MUTE:
+ case AKEYCODE_MUTE:
+ case AKEYCODE_POWER:
+ case AKEYCODE_HEADSETHOOK:
+ case AKEYCODE_MEDIA_PLAY:
+ case AKEYCODE_MEDIA_PAUSE:
+ case AKEYCODE_MEDIA_PLAY_PAUSE:
+ case AKEYCODE_MEDIA_STOP:
+ case AKEYCODE_MEDIA_NEXT:
+ case AKEYCODE_MEDIA_PREVIOUS:
+ case AKEYCODE_MEDIA_REWIND:
+ case AKEYCODE_MEDIA_RECORD:
+ case AKEYCODE_MEDIA_FAST_FORWARD:
+ case AKEYCODE_CAMERA:
+ case AKEYCODE_FOCUS:
+ case AKEYCODE_SEARCH:
+ case AKEYCODE_BRIGHTNESS_DOWN:
+ case AKEYCODE_BRIGHTNESS_UP:
+ return true;
+ }
+
+ return false;
+}
+
+bool KeyEvent::isSystemKey() const {
+ return isSystemKey(getKeyCode());
+}
+
+void KeyEvent::initialize(
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t keyCode,
+ int32_t scanCode,
+ int32_t metaState,
+ int32_t repeatCount,
+ nsecs_t downTime,
+ nsecs_t eventTime) {
+ InputEvent::initialize(deviceId, source);
+ mAction = action;
+ mFlags = flags;
+ mKeyCode = keyCode;
+ mScanCode = scanCode;
+ mMetaState = metaState;
+ mRepeatCount = repeatCount;
+ mDownTime = downTime;
+ mEventTime = eventTime;
+}
+
+void KeyEvent::initialize(const KeyEvent& from) {
+ InputEvent::initialize(from);
+ mAction = from.mAction;
+ mFlags = from.mFlags;
+ mKeyCode = from.mKeyCode;
+ mScanCode = from.mScanCode;
+ mMetaState = from.mMetaState;
+ mRepeatCount = from.mRepeatCount;
+ mDownTime = from.mDownTime;
+ mEventTime = from.mEventTime;
+}
+
+
+// --- PointerCoords ---
+
+float PointerCoords::getAxisValue(int32_t axis) const {
+ if (axis < 0 || axis > 63) {
+ return 0;
+ }
+
+ uint64_t axisBit = 1LL << axis;
+ if (!(bits & axisBit)) {
+ return 0;
+ }
+ uint32_t index = __builtin_popcountll(bits & (axisBit - 1LL));
+ return values[index];
+}
+
+status_t PointerCoords::setAxisValue(int32_t axis, float value) {
+ if (axis < 0 || axis > 63) {
+ return NAME_NOT_FOUND;
+ }
+
+ uint64_t axisBit = 1LL << axis;
+ uint32_t index = __builtin_popcountll(bits & (axisBit - 1LL));
+ if (!(bits & axisBit)) {
+ if (value == 0) {
+ return OK; // axes with value 0 do not need to be stored
+ }
+ uint32_t count = __builtin_popcountll(bits);
+ if (count >= MAX_AXES) {
+ tooManyAxes(axis);
+ return NO_MEMORY;
+ }
+ bits |= axisBit;
+ for (uint32_t i = count; i > index; i--) {
+ values[i] = values[i - 1];
+ }
+ }
+ values[index] = value;
+ return OK;
+}
+
+static inline void scaleAxisValue(PointerCoords& c, int axis, float scaleFactor) {
+ float value = c.getAxisValue(axis);
+ if (value != 0) {
+ c.setAxisValue(axis, value * scaleFactor);
+ }
+}
+
+void PointerCoords::scale(float scaleFactor) {
+ // No need to scale pressure or size since they are normalized.
+ // No need to scale orientation since it is meaningless to do so.
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_X, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_Y, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MAJOR, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MINOR, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MAJOR, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MINOR, scaleFactor);
+}
+
+#ifdef HAVE_ANDROID_OS
+status_t PointerCoords::readFromParcel(Parcel* parcel) {
+ bits = parcel->readInt64();
+
+ uint32_t count = __builtin_popcountll(bits);
+ if (count > MAX_AXES) {
+ return BAD_VALUE;
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ values[i] = parcel->readFloat();
+ }
+ return OK;
+}
+
+status_t PointerCoords::writeToParcel(Parcel* parcel) const {
+ parcel->writeInt64(bits);
+
+ uint32_t count = __builtin_popcountll(bits);
+ for (uint32_t i = 0; i < count; i++) {
+ parcel->writeFloat(values[i]);
+ }
+ return OK;
+}
+#endif
+
+void PointerCoords::tooManyAxes(int axis) {
+ ALOGW("Could not set value for axis %d because the PointerCoords structure is full and "
+ "cannot contain more than %d axis values.", axis, int(MAX_AXES));
+}
+
+bool PointerCoords::operator==(const PointerCoords& other) const {
+ if (bits != other.bits) {
+ return false;
+ }
+ uint32_t count = __builtin_popcountll(bits);
+ for (uint32_t i = 0; i < count; i++) {
+ if (values[i] != other.values[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void PointerCoords::copyFrom(const PointerCoords& other) {
+ bits = other.bits;
+ uint32_t count = __builtin_popcountll(bits);
+ for (uint32_t i = 0; i < count; i++) {
+ values[i] = other.values[i];
+ }
+}
+
+
+// --- PointerProperties ---
+
+bool PointerProperties::operator==(const PointerProperties& other) const {
+ return id == other.id
+ && toolType == other.toolType;
+}
+
+void PointerProperties::copyFrom(const PointerProperties& other) {
+ id = other.id;
+ toolType = other.toolType;
+}
+
+
+// --- MotionEvent ---
+
+void MotionEvent::initialize(
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t edgeFlags,
+ int32_t metaState,
+ int32_t buttonState,
+ float xOffset,
+ float yOffset,
+ float xPrecision,
+ float yPrecision,
+ nsecs_t downTime,
+ nsecs_t eventTime,
+ size_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords) {
+ InputEvent::initialize(deviceId, source);
+ mAction = action;
+ mFlags = flags;
+ mEdgeFlags = edgeFlags;
+ mMetaState = metaState;
+ mButtonState = buttonState;
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+ mXPrecision = xPrecision;
+ mYPrecision = yPrecision;
+ mDownTime = downTime;
+ mPointerProperties.clear();
+ mPointerProperties.appendArray(pointerProperties, pointerCount);
+ mSampleEventTimes.clear();
+ mSamplePointerCoords.clear();
+ addSample(eventTime, pointerCoords);
+}
+
+void MotionEvent::copyFrom(const MotionEvent* other, bool keepHistory) {
+ InputEvent::initialize(other->mDeviceId, other->mSource);
+ mAction = other->mAction;
+ mFlags = other->mFlags;
+ mEdgeFlags = other->mEdgeFlags;
+ mMetaState = other->mMetaState;
+ mButtonState = other->mButtonState;
+ mXOffset = other->mXOffset;
+ mYOffset = other->mYOffset;
+ mXPrecision = other->mXPrecision;
+ mYPrecision = other->mYPrecision;
+ mDownTime = other->mDownTime;
+ mPointerProperties = other->mPointerProperties;
+
+ if (keepHistory) {
+ mSampleEventTimes = other->mSampleEventTimes;
+ mSamplePointerCoords = other->mSamplePointerCoords;
+ } else {
+ mSampleEventTimes.clear();
+ mSampleEventTimes.push(other->getEventTime());
+ mSamplePointerCoords.clear();
+ size_t pointerCount = other->getPointerCount();
+ size_t historySize = other->getHistorySize();
+ mSamplePointerCoords.appendArray(other->mSamplePointerCoords.array()
+ + (historySize * pointerCount), pointerCount);
+ }
+}
+
+void MotionEvent::addSample(
+ int64_t eventTime,
+ const PointerCoords* pointerCoords) {
+ mSampleEventTimes.push(eventTime);
+ mSamplePointerCoords.appendArray(pointerCoords, getPointerCount());
+}
+
+const PointerCoords* MotionEvent::getRawPointerCoords(size_t pointerIndex) const {
+ return &mSamplePointerCoords[getHistorySize() * getPointerCount() + pointerIndex];
+}
+
+float MotionEvent::getRawAxisValue(int32_t axis, size_t pointerIndex) const {
+ return getRawPointerCoords(pointerIndex)->getAxisValue(axis);
+}
+
+float MotionEvent::getAxisValue(int32_t axis, size_t pointerIndex) const {
+ float value = getRawPointerCoords(pointerIndex)->getAxisValue(axis);
+ switch (axis) {
+ case AMOTION_EVENT_AXIS_X:
+ return value + mXOffset;
+ case AMOTION_EVENT_AXIS_Y:
+ return value + mYOffset;
+ }
+ return value;
+}
+
+const PointerCoords* MotionEvent::getHistoricalRawPointerCoords(
+ size_t pointerIndex, size_t historicalIndex) const {
+ return &mSamplePointerCoords[historicalIndex * getPointerCount() + pointerIndex];
+}
+
+float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
+ size_t historicalIndex) const {
+ return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
+}
+
+float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
+ size_t historicalIndex) const {
+ float value = getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
+ switch (axis) {
+ case AMOTION_EVENT_AXIS_X:
+ return value + mXOffset;
+ case AMOTION_EVENT_AXIS_Y:
+ return value + mYOffset;
+ }
+ return value;
+}
+
+ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
+ size_t pointerCount = mPointerProperties.size();
+ for (size_t i = 0; i < pointerCount; i++) {
+ if (mPointerProperties.itemAt(i).id == pointerId) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void MotionEvent::offsetLocation(float xOffset, float yOffset) {
+ mXOffset += xOffset;
+ mYOffset += yOffset;
+}
+
+void MotionEvent::scale(float scaleFactor) {
+ mXOffset *= scaleFactor;
+ mYOffset *= scaleFactor;
+ mXPrecision *= scaleFactor;
+ mYPrecision *= scaleFactor;
+
+ size_t numSamples = mSamplePointerCoords.size();
+ for (size_t i = 0; i < numSamples; i++) {
+ mSamplePointerCoords.editItemAt(i).scale(scaleFactor);
+ }
+}
+
+#ifdef HAVE_ANDROID_OS
+static inline float transformAngle(const SkMatrix* matrix, float angleRadians) {
+ // Construct and transform a vector oriented at the specified clockwise angle from vertical.
+ // Coordinate system: down is increasing Y, right is increasing X.
+ SkPoint vector;
+ vector.fX = SkFloatToScalar(sinf(angleRadians));
+ vector.fY = SkFloatToScalar(-cosf(angleRadians));
+ matrix->mapVectors(& vector, 1);
+
+ // Derive the transformed vector's clockwise angle from vertical.
+ float result = atan2f(SkScalarToFloat(vector.fX), SkScalarToFloat(-vector.fY));
+ if (result < - M_PI_2) {
+ result += M_PI;
+ } else if (result > M_PI_2) {
+ result -= M_PI;
+ }
+ return result;
+}
+
+void MotionEvent::transform(const SkMatrix* matrix) {
+ float oldXOffset = mXOffset;
+ float oldYOffset = mYOffset;
+
+ // The tricky part of this implementation is to preserve the value of
+ // rawX and rawY. So we apply the transformation to the first point
+ // then derive an appropriate new X/Y offset that will preserve rawX and rawY.
+ SkPoint point;
+ float rawX = getRawX(0);
+ float rawY = getRawY(0);
+ matrix->mapXY(SkFloatToScalar(rawX + oldXOffset), SkFloatToScalar(rawY + oldYOffset),
+ & point);
+ float newX = SkScalarToFloat(point.fX);
+ float newY = SkScalarToFloat(point.fY);
+ float newXOffset = newX - rawX;
+ float newYOffset = newY - rawY;
+
+ mXOffset = newXOffset;
+ mYOffset = newYOffset;
+
+ // Apply the transformation to all samples.
+ size_t numSamples = mSamplePointerCoords.size();
+ for (size_t i = 0; i < numSamples; i++) {
+ PointerCoords& c = mSamplePointerCoords.editItemAt(i);
+ float x = c.getAxisValue(AMOTION_EVENT_AXIS_X) + oldXOffset;
+ float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y) + oldYOffset;
+ matrix->mapXY(SkFloatToScalar(x), SkFloatToScalar(y), &point);
+ c.setAxisValue(AMOTION_EVENT_AXIS_X, SkScalarToFloat(point.fX) - newXOffset);
+ c.setAxisValue(AMOTION_EVENT_AXIS_Y, SkScalarToFloat(point.fY) - newYOffset);
+
+ float orientation = c.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+ c.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, transformAngle(matrix, orientation));
+ }
+}
+
+status_t MotionEvent::readFromParcel(Parcel* parcel) {
+ size_t pointerCount = parcel->readInt32();
+ size_t sampleCount = parcel->readInt32();
+ if (pointerCount == 0 || pointerCount > MAX_POINTERS || sampleCount == 0) {
+ return BAD_VALUE;
+ }
+
+ mDeviceId = parcel->readInt32();
+ mSource = parcel->readInt32();
+ mAction = parcel->readInt32();
+ mFlags = parcel->readInt32();
+ mEdgeFlags = parcel->readInt32();
+ mMetaState = parcel->readInt32();
+ mButtonState = parcel->readInt32();
+ mXOffset = parcel->readFloat();
+ mYOffset = parcel->readFloat();
+ mXPrecision = parcel->readFloat();
+ mYPrecision = parcel->readFloat();
+ mDownTime = parcel->readInt64();
+
+ mPointerProperties.clear();
+ mPointerProperties.setCapacity(pointerCount);
+ mSampleEventTimes.clear();
+ mSampleEventTimes.setCapacity(sampleCount);
+ mSamplePointerCoords.clear();
+ mSamplePointerCoords.setCapacity(sampleCount * pointerCount);
+
+ for (size_t i = 0; i < pointerCount; i++) {
+ mPointerProperties.push();
+ PointerProperties& properties = mPointerProperties.editTop();
+ properties.id = parcel->readInt32();
+ properties.toolType = parcel->readInt32();
+ }
+
+ while (sampleCount-- > 0) {
+ mSampleEventTimes.push(parcel->readInt64());
+ for (size_t i = 0; i < pointerCount; i++) {
+ mSamplePointerCoords.push();
+ status_t status = mSamplePointerCoords.editTop().readFromParcel(parcel);
+ if (status) {
+ return status;
+ }
+ }
+ }
+ return OK;
+}
+
+status_t MotionEvent::writeToParcel(Parcel* parcel) const {
+ size_t pointerCount = mPointerProperties.size();
+ size_t sampleCount = mSampleEventTimes.size();
+
+ parcel->writeInt32(pointerCount);
+ parcel->writeInt32(sampleCount);
+
+ parcel->writeInt32(mDeviceId);
+ parcel->writeInt32(mSource);
+ parcel->writeInt32(mAction);
+ parcel->writeInt32(mFlags);
+ parcel->writeInt32(mEdgeFlags);
+ parcel->writeInt32(mMetaState);
+ parcel->writeInt32(mButtonState);
+ parcel->writeFloat(mXOffset);
+ parcel->writeFloat(mYOffset);
+ parcel->writeFloat(mXPrecision);
+ parcel->writeFloat(mYPrecision);
+ parcel->writeInt64(mDownTime);
+
+ for (size_t i = 0; i < pointerCount; i++) {
+ const PointerProperties& properties = mPointerProperties.itemAt(i);
+ parcel->writeInt32(properties.id);
+ parcel->writeInt32(properties.toolType);
+ }
+
+ const PointerCoords* pc = mSamplePointerCoords.array();
+ for (size_t h = 0; h < sampleCount; h++) {
+ parcel->writeInt64(mSampleEventTimes.itemAt(h));
+ for (size_t i = 0; i < pointerCount; i++) {
+ status_t status = (pc++)->writeToParcel(parcel);
+ if (status) {
+ return status;
+ }
+ }
+ }
+ return OK;
+}
+#endif
+
+bool MotionEvent::isTouchEvent(int32_t source, int32_t action) {
+ if (source & AINPUT_SOURCE_CLASS_POINTER) {
+ // Specifically excludes HOVER_MOVE and SCROLL.
+ switch (action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_MOVE:
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ return true;
+ }
+ }
+ return false;
+}
+
+
+// --- PooledInputEventFactory ---
+
+PooledInputEventFactory::PooledInputEventFactory(size_t maxPoolSize) :
+ mMaxPoolSize(maxPoolSize) {
+}
+
+PooledInputEventFactory::~PooledInputEventFactory() {
+ for (size_t i = 0; i < mKeyEventPool.size(); i++) {
+ delete mKeyEventPool.itemAt(i);
+ }
+ for (size_t i = 0; i < mMotionEventPool.size(); i++) {
+ delete mMotionEventPool.itemAt(i);
+ }
+}
+
+KeyEvent* PooledInputEventFactory::createKeyEvent() {
+ if (!mKeyEventPool.isEmpty()) {
+ KeyEvent* event = mKeyEventPool.top();
+ mKeyEventPool.pop();
+ return event;
+ }
+ return new KeyEvent();
+}
+
+MotionEvent* PooledInputEventFactory::createMotionEvent() {
+ if (!mMotionEventPool.isEmpty()) {
+ MotionEvent* event = mMotionEventPool.top();
+ mMotionEventPool.pop();
+ return event;
+ }
+ return new MotionEvent();
+}
+
+void PooledInputEventFactory::recycle(InputEvent* event) {
+ switch (event->getType()) {
+ case AINPUT_EVENT_TYPE_KEY:
+ if (mKeyEventPool.size() < mMaxPoolSize) {
+ mKeyEventPool.push(static_cast<KeyEvent*>(event));
+ return;
+ }
+ break;
+ case AINPUT_EVENT_TYPE_MOTION:
+ if (mMotionEventPool.size() < mMaxPoolSize) {
+ mMotionEventPool.push(static_cast<MotionEvent*>(event));
+ return;
+ }
+ break;
+ }
+ delete event;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/Input.h b/widget/gonk/libui/Input.h
new file mode 100644
index 000000000..3d958bfab
--- /dev/null
+++ b/widget/gonk/libui/Input.h
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_INPUT_H
+#define _ANDROIDFW_INPUT_H
+
+/**
+ * Native input event structures.
+ */
+
+#include "android_input.h"
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+#ifdef HAVE_ANDROID_OS
+class SkMatrix;
+#endif
+
+/*
+ * Additional private constants not defined in ndk/ui/input.h.
+ */
+enum {
+ /* Signifies that the key is being predispatched */
+ AKEY_EVENT_FLAG_PREDISPATCH = 0x20000000,
+
+ /* Private control to determine when an app is tracking a key sequence. */
+ AKEY_EVENT_FLAG_START_TRACKING = 0x40000000,
+
+ /* Key event is inconsistent with previously sent key events. */
+ AKEY_EVENT_FLAG_TAINTED = 0x80000000,
+};
+
+enum {
+ /* Motion event is inconsistent with previously sent motion events. */
+ AMOTION_EVENT_FLAG_TAINTED = 0x80000000,
+};
+
+enum {
+ /* Used when a motion event is not associated with any display.
+ * Typically used for non-pointer events. */
+ ADISPLAY_ID_NONE = -1,
+
+ /* The default display id. */
+ ADISPLAY_ID_DEFAULT = 0,
+};
+
+enum {
+ /*
+ * Indicates that an input device has switches.
+ * This input source flag is hidden from the API because switches are only used by the system
+ * and applications have no way to interact with them.
+ */
+ AINPUT_SOURCE_SWITCH = 0x80000000,
+};
+
+/*
+ * SystemUiVisibility constants from View.
+ */
+enum {
+ ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE = 0,
+ ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN = 0x00000001,
+};
+
+/*
+ * Maximum number of pointers supported per motion event.
+ * Smallest number of pointers is 1.
+ * (We want at least 10 but some touch controllers obstensibly configured for 10 pointers
+ * will occasionally emit 11. There is not much harm making this constant bigger.)
+ */
+#define MAX_POINTERS 16
+
+/*
+ * Maximum pointer id value supported in a motion event.
+ * Smallest pointer id is 0.
+ * (This is limited by our use of BitSet32 to track pointer assignments.)
+ */
+#define MAX_POINTER_ID 31
+
+/*
+ * Declare a concrete type for the NDK's input event forward declaration.
+ */
+struct AInputEvent {
+ virtual ~AInputEvent() { }
+};
+
+/*
+ * Declare a concrete type for the NDK's input device forward declaration.
+ */
+struct AInputDevice {
+ virtual ~AInputDevice() { }
+};
+
+
+namespace android {
+
+#ifdef HAVE_ANDROID_OS
+class Parcel;
+#endif
+
+/*
+ * Flags that flow alongside events in the input dispatch system to help with certain
+ * policy decisions such as waking from device sleep.
+ *
+ * These flags are also defined in frameworks/base/core/java/android/view/WindowManagerPolicy.java.
+ */
+enum {
+ /* These flags originate in RawEvents and are generally set in the key map.
+ * NOTE: If you edit these flags, also edit labels in KeycodeLabels.h. */
+
+ POLICY_FLAG_WAKE = 0x00000001,
+ POLICY_FLAG_WAKE_DROPPED = 0x00000002,
+ POLICY_FLAG_SHIFT = 0x00000004,
+ POLICY_FLAG_CAPS_LOCK = 0x00000008,
+ POLICY_FLAG_ALT = 0x00000010,
+ POLICY_FLAG_ALT_GR = 0x00000020,
+ POLICY_FLAG_MENU = 0x00000040,
+ POLICY_FLAG_LAUNCHER = 0x00000080,
+ POLICY_FLAG_VIRTUAL = 0x00000100,
+ POLICY_FLAG_FUNCTION = 0x00000200,
+
+ POLICY_FLAG_RAW_MASK = 0x0000ffff,
+
+ /* These flags are set by the input dispatcher. */
+
+ // Indicates that the input event was injected.
+ POLICY_FLAG_INJECTED = 0x01000000,
+
+ // Indicates that the input event is from a trusted source such as a directly attached
+ // input device or an application with system-wide event injection permission.
+ POLICY_FLAG_TRUSTED = 0x02000000,
+
+ // Indicates that the input event has passed through an input filter.
+ POLICY_FLAG_FILTERED = 0x04000000,
+
+ // Disables automatic key repeating behavior.
+ POLICY_FLAG_DISABLE_KEY_REPEAT = 0x08000000,
+
+ /* These flags are set by the input reader policy as it intercepts each event. */
+
+ // Indicates that the screen was off when the event was received and the event
+ // should wake the device.
+ POLICY_FLAG_WOKE_HERE = 0x10000000,
+
+ // Indicates that the screen was dim when the event was received and the event
+ // should brighten the device.
+ POLICY_FLAG_BRIGHT_HERE = 0x20000000,
+
+ // Indicates that the event should be dispatched to applications.
+ // The input event should still be sent to the InputDispatcher so that it can see all
+ // input events received include those that it will not deliver.
+ POLICY_FLAG_PASS_TO_USER = 0x40000000,
+};
+
+/*
+ * Pointer coordinate data.
+ */
+struct PointerCoords {
+ enum { MAX_AXES = 14 }; // 14 so that sizeof(PointerCoords) == 64
+
+ // Bitfield of axes that are present in this structure.
+ uint64_t bits;
+
+ // Values of axes that are stored in this structure packed in order by axis id
+ // for each axis that is present in the structure according to 'bits'.
+ float values[MAX_AXES];
+
+ inline void clear() {
+ bits = 0;
+ }
+
+ float getAxisValue(int32_t axis) const;
+ status_t setAxisValue(int32_t axis, float value);
+
+ void scale(float scale);
+
+ inline float getX() const {
+ return getAxisValue(AMOTION_EVENT_AXIS_X);
+ }
+
+ inline float getY() const {
+ return getAxisValue(AMOTION_EVENT_AXIS_Y);
+ }
+
+#ifdef HAVE_ANDROID_OS
+ status_t readFromParcel(Parcel* parcel);
+ status_t writeToParcel(Parcel* parcel) const;
+#endif
+
+ bool operator==(const PointerCoords& other) const;
+ inline bool operator!=(const PointerCoords& other) const {
+ return !(*this == other);
+ }
+
+ void copyFrom(const PointerCoords& other);
+
+private:
+ void tooManyAxes(int axis);
+};
+
+/*
+ * Pointer property data.
+ */
+struct PointerProperties {
+ // The id of the pointer.
+ int32_t id;
+
+ // The pointer tool type.
+ int32_t toolType;
+
+ inline void clear() {
+ id = -1;
+ toolType = 0;
+ }
+
+ bool operator==(const PointerProperties& other) const;
+ inline bool operator!=(const PointerProperties& other) const {
+ return !(*this == other);
+ }
+
+ void copyFrom(const PointerProperties& other);
+};
+
+/*
+ * Input events.
+ */
+class InputEvent : public AInputEvent {
+public:
+ virtual ~InputEvent() { }
+
+ virtual int32_t getType() const = 0;
+
+ inline int32_t getDeviceId() const { return mDeviceId; }
+
+ inline int32_t getSource() const { return mSource; }
+
+ inline void setSource(int32_t source) { mSource = source; }
+
+protected:
+ void initialize(int32_t deviceId, int32_t source);
+ void initialize(const InputEvent& from);
+
+ int32_t mDeviceId;
+ int32_t mSource;
+};
+
+/*
+ * Key events.
+ */
+class KeyEvent : public InputEvent {
+public:
+ virtual ~KeyEvent() { }
+
+ virtual int32_t getType() const { return AINPUT_EVENT_TYPE_KEY; }
+
+ inline int32_t getAction() const { return mAction; }
+
+ inline int32_t getFlags() const { return mFlags; }
+
+ inline void setFlags(int32_t flags) { mFlags = flags; }
+
+ inline int32_t getKeyCode() const { return mKeyCode; }
+
+ inline int32_t getScanCode() const { return mScanCode; }
+
+ inline int32_t getMetaState() const { return mMetaState; }
+
+ inline int32_t getRepeatCount() const { return mRepeatCount; }
+
+ inline nsecs_t getDownTime() const { return mDownTime; }
+
+ inline nsecs_t getEventTime() const { return mEventTime; }
+
+ // Return true if this event may have a default action implementation.
+ static bool hasDefaultAction(int32_t keyCode);
+ bool hasDefaultAction() const;
+
+ // Return true if this event represents a system key.
+ static bool isSystemKey(int32_t keyCode);
+ bool isSystemKey() const;
+
+ void initialize(
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t keyCode,
+ int32_t scanCode,
+ int32_t metaState,
+ int32_t repeatCount,
+ nsecs_t downTime,
+ nsecs_t eventTime);
+ void initialize(const KeyEvent& from);
+
+protected:
+ int32_t mAction;
+ int32_t mFlags;
+ int32_t mKeyCode;
+ int32_t mScanCode;
+ int32_t mMetaState;
+ int32_t mRepeatCount;
+ nsecs_t mDownTime;
+ nsecs_t mEventTime;
+};
+
+/*
+ * Motion events.
+ */
+class MotionEvent : public InputEvent {
+public:
+ virtual ~MotionEvent() { }
+
+ virtual int32_t getType() const { return AINPUT_EVENT_TYPE_MOTION; }
+
+ inline int32_t getAction() const { return mAction; }
+
+ inline int32_t getActionMasked() const { return mAction & AMOTION_EVENT_ACTION_MASK; }
+
+ inline int32_t getActionIndex() const {
+ return (mAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ inline void setAction(int32_t action) { mAction = action; }
+
+ inline int32_t getFlags() const { return mFlags; }
+
+ inline void setFlags(int32_t flags) { mFlags = flags; }
+
+ inline int32_t getEdgeFlags() const { return mEdgeFlags; }
+
+ inline void setEdgeFlags(int32_t edgeFlags) { mEdgeFlags = edgeFlags; }
+
+ inline int32_t getMetaState() const { return mMetaState; }
+
+ inline void setMetaState(int32_t metaState) { mMetaState = metaState; }
+
+ inline int32_t getButtonState() const { return mButtonState; }
+
+ inline float getXOffset() const { return mXOffset; }
+
+ inline float getYOffset() const { return mYOffset; }
+
+ inline float getXPrecision() const { return mXPrecision; }
+
+ inline float getYPrecision() const { return mYPrecision; }
+
+ inline nsecs_t getDownTime() const { return mDownTime; }
+
+ inline void setDownTime(nsecs_t downTime) { mDownTime = downTime; }
+
+ inline size_t getPointerCount() const { return mPointerProperties.size(); }
+
+ inline const PointerProperties* getPointerProperties(size_t pointerIndex) const {
+ return &mPointerProperties[pointerIndex];
+ }
+
+ inline int32_t getPointerId(size_t pointerIndex) const {
+ return mPointerProperties[pointerIndex].id;
+ }
+
+ inline int32_t getToolType(size_t pointerIndex) const {
+ return mPointerProperties[pointerIndex].toolType;
+ }
+
+ inline nsecs_t getEventTime() const { return mSampleEventTimes[getHistorySize()]; }
+
+ const PointerCoords* getRawPointerCoords(size_t pointerIndex) const;
+
+ float getRawAxisValue(int32_t axis, size_t pointerIndex) const;
+
+ inline float getRawX(size_t pointerIndex) const {
+ return getRawAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex);
+ }
+
+ inline float getRawY(size_t pointerIndex) const {
+ return getRawAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex);
+ }
+
+ float getAxisValue(int32_t axis, size_t pointerIndex) const;
+
+ inline float getX(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex);
+ }
+
+ inline float getY(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex);
+ }
+
+ inline float getPressure(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerIndex);
+ }
+
+ inline float getSize(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_SIZE, pointerIndex);
+ }
+
+ inline float getTouchMajor(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex);
+ }
+
+ inline float getTouchMinor(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex);
+ }
+
+ inline float getToolMajor(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex);
+ }
+
+ inline float getToolMinor(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex);
+ }
+
+ inline float getOrientation(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex);
+ }
+
+ inline size_t getHistorySize() const { return mSampleEventTimes.size() - 1; }
+
+ inline nsecs_t getHistoricalEventTime(size_t historicalIndex) const {
+ return mSampleEventTimes[historicalIndex];
+ }
+
+ const PointerCoords* getHistoricalRawPointerCoords(
+ size_t pointerIndex, size_t historicalIndex) const;
+
+ float getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
+ size_t historicalIndex) const;
+
+ inline float getHistoricalRawX(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalRawAxisValue(
+ AMOTION_EVENT_AXIS_X, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalRawY(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalRawAxisValue(
+ AMOTION_EVENT_AXIS_Y, pointerIndex, historicalIndex);
+ }
+
+ float getHistoricalAxisValue(int32_t axis, size_t pointerIndex, size_t historicalIndex) const;
+
+ inline float getHistoricalX(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_X, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalY(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_Y, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalPressure(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalSize(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_SIZE, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalTouchMajor(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalTouchMinor(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalToolMajor(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalToolMinor(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalOrientation(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex);
+ }
+
+ ssize_t findPointerIndex(int32_t pointerId) const;
+
+ void initialize(
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t edgeFlags,
+ int32_t metaState,
+ int32_t buttonState,
+ float xOffset,
+ float yOffset,
+ float xPrecision,
+ float yPrecision,
+ nsecs_t downTime,
+ nsecs_t eventTime,
+ size_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords);
+
+ void copyFrom(const MotionEvent* other, bool keepHistory);
+
+ void addSample(
+ nsecs_t eventTime,
+ const PointerCoords* pointerCoords);
+
+ void offsetLocation(float xOffset, float yOffset);
+
+ void scale(float scaleFactor);
+
+#ifdef HAVE_ANDROID_OS
+ void transform(const SkMatrix* matrix);
+
+ status_t readFromParcel(Parcel* parcel);
+ status_t writeToParcel(Parcel* parcel) const;
+#endif
+
+ static bool isTouchEvent(int32_t source, int32_t action);
+ inline bool isTouchEvent() const {
+ return isTouchEvent(mSource, mAction);
+ }
+
+ // Low-level accessors.
+ inline const PointerProperties* getPointerProperties() const {
+ return mPointerProperties.array();
+ }
+ inline const nsecs_t* getSampleEventTimes() const { return mSampleEventTimes.array(); }
+ inline const PointerCoords* getSamplePointerCoords() const {
+ return mSamplePointerCoords.array();
+ }
+
+protected:
+ int32_t mAction;
+ int32_t mFlags;
+ int32_t mEdgeFlags;
+ int32_t mMetaState;
+ int32_t mButtonState;
+ float mXOffset;
+ float mYOffset;
+ float mXPrecision;
+ float mYPrecision;
+ nsecs_t mDownTime;
+ Vector<PointerProperties> mPointerProperties;
+ Vector<nsecs_t> mSampleEventTimes;
+ Vector<PointerCoords> mSamplePointerCoords;
+};
+
+/*
+ * Input event factory.
+ */
+class InputEventFactoryInterface {
+protected:
+ virtual ~InputEventFactoryInterface() { }
+
+public:
+ InputEventFactoryInterface() { }
+
+ virtual KeyEvent* createKeyEvent() = 0;
+ virtual MotionEvent* createMotionEvent() = 0;
+};
+
+/*
+ * A simple input event factory implementation that uses a single preallocated instance
+ * of each type of input event that are reused for each request.
+ */
+class PreallocatedInputEventFactory : public InputEventFactoryInterface {
+public:
+ PreallocatedInputEventFactory() { }
+ virtual ~PreallocatedInputEventFactory() { }
+
+ virtual KeyEvent* createKeyEvent() { return & mKeyEvent; }
+ virtual MotionEvent* createMotionEvent() { return & mMotionEvent; }
+
+private:
+ KeyEvent mKeyEvent;
+ MotionEvent mMotionEvent;
+};
+
+/*
+ * An input event factory implementation that maintains a pool of input events.
+ */
+class PooledInputEventFactory : public InputEventFactoryInterface {
+public:
+ PooledInputEventFactory(size_t maxPoolSize = 20);
+ virtual ~PooledInputEventFactory();
+
+ virtual KeyEvent* createKeyEvent();
+ virtual MotionEvent* createMotionEvent();
+
+ void recycle(InputEvent* event);
+
+private:
+ const size_t mMaxPoolSize;
+
+ Vector<KeyEvent*> mKeyEventPool;
+ Vector<MotionEvent*> mMotionEventPool;
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_INPUT_H
diff --git a/widget/gonk/libui/InputApplication.cpp b/widget/gonk/libui/InputApplication.cpp
new file mode 100644
index 000000000..ce432356b
--- /dev/null
+++ b/widget/gonk/libui/InputApplication.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputApplication"
+
+#include "InputApplication.h"
+
+#include "cutils_log.h"
+
+namespace android {
+
+// --- InputApplicationHandle ---
+
+InputApplicationHandle::InputApplicationHandle() :
+ mInfo(NULL) {
+}
+
+InputApplicationHandle::~InputApplicationHandle() {
+ delete mInfo;
+}
+
+void InputApplicationHandle::releaseInfo() {
+ if (mInfo) {
+ delete mInfo;
+ mInfo = NULL;
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputApplication.h b/widget/gonk/libui/InputApplication.h
new file mode 100644
index 000000000..ba789559c
--- /dev/null
+++ b/widget/gonk/libui/InputApplication.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_APPLICATION_H
+#define _UI_INPUT_APPLICATION_H
+
+#include "Input.h"
+
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * Describes the properties of an application that can receive input.
+ */
+struct InputApplicationInfo {
+ String8 name;
+ nsecs_t dispatchingTimeout;
+};
+
+
+/*
+ * Handle for an application that can receive input.
+ *
+ * Used by the native input dispatcher as a handle for the window manager objects
+ * that describe an application.
+ */
+class InputApplicationHandle : public RefBase {
+public:
+ inline const InputApplicationInfo* getInfo() const {
+ return mInfo;
+ }
+
+ inline String8 getName() const {
+ return mInfo ? mInfo->name : String8("<invalid>");
+ }
+
+ inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const {
+ return mInfo ? mInfo->dispatchingTimeout : defaultValue;
+ }
+
+ /**
+ * Requests that the state of this object be updated to reflect
+ * the most current available information about the application.
+ *
+ * This method should only be called from within the input dispatcher's
+ * critical section.
+ *
+ * Returns true on success, or false if the handle is no longer valid.
+ */
+ virtual bool updateInfo() = 0;
+
+ /**
+ * Releases the storage used by the associated information when it is
+ * no longer needed.
+ */
+ void releaseInfo();
+
+protected:
+ InputApplicationHandle();
+ virtual ~InputApplicationHandle();
+
+ InputApplicationInfo* mInfo;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_APPLICATION_H
diff --git a/widget/gonk/libui/InputDevice.cpp b/widget/gonk/libui/InputDevice.cpp
new file mode 100644
index 000000000..01a437dd4
--- /dev/null
+++ b/widget/gonk/libui/InputDevice.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputDevice"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "InputDevice.h"
+
+namespace android {
+
+static const char* CONFIGURATION_FILE_DIR[] = {
+ "idc/",
+ "keylayout/",
+ "keychars/",
+};
+
+static const char* CONFIGURATION_FILE_EXTENSION[] = {
+ ".idc",
+ ".kl",
+ ".kcm",
+};
+
+static bool isValidNameChar(char ch) {
+ return isascii(ch) && (isdigit(ch) || isalpha(ch) || ch == '-' || ch == '_');
+}
+
+static void appendInputDeviceConfigurationFileRelativePath(String8& path,
+ const String8& name, InputDeviceConfigurationFileType type) {
+ path.append(CONFIGURATION_FILE_DIR[type]);
+ for (size_t i = 0; i < name.length(); i++) {
+ char ch = name[i];
+ if (!isValidNameChar(ch)) {
+ ch = '_';
+ }
+ path.append(&ch, 1);
+ }
+ path.append(CONFIGURATION_FILE_EXTENSION[type]);
+}
+
+String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
+ const InputDeviceIdentifier& deviceIdentifier,
+ InputDeviceConfigurationFileType type) {
+ if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
+ if (deviceIdentifier.version != 0) {
+ // Try vendor product version.
+ String8 versionPath(getInputDeviceConfigurationFilePathByName(
+ String8::format("Vendor_%04x_Product_%04x_Version_%04x",
+ deviceIdentifier.vendor, deviceIdentifier.product,
+ deviceIdentifier.version),
+ type));
+ if (!versionPath.isEmpty()) {
+ return versionPath;
+ }
+ }
+
+ // Try vendor product.
+ String8 productPath(getInputDeviceConfigurationFilePathByName(
+ String8::format("Vendor_%04x_Product_%04x",
+ deviceIdentifier.vendor, deviceIdentifier.product),
+ type));
+ if (!productPath.isEmpty()) {
+ return productPath;
+ }
+ }
+
+ // Try device name.
+ return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
+}
+
+String8 getInputDeviceConfigurationFilePathByName(
+ const String8& name, InputDeviceConfigurationFileType type) {
+ // Search system repository.
+ String8 path;
+ path.setTo(getenv("ANDROID_ROOT"));
+ path.append("/usr/");
+ appendInputDeviceConfigurationFileRelativePath(path, name, type);
+#if DEBUG_PROBE
+ ALOGD("Probing for system provided input device configuration file: path='%s'", path.string());
+#endif
+ if (!access(path.string(), R_OK)) {
+#if DEBUG_PROBE
+ ALOGD("Found");
+#endif
+ return path;
+ }
+
+ // Search user repository.
+ // TODO Should only look here if not in safe mode.
+ path.setTo(getenv("ANDROID_DATA"));
+ path.append("/system/devices/");
+ appendInputDeviceConfigurationFileRelativePath(path, name, type);
+#if DEBUG_PROBE
+ ALOGD("Probing for system user input device configuration file: path='%s'", path.string());
+#endif
+ if (!access(path.string(), R_OK)) {
+#if DEBUG_PROBE
+ ALOGD("Found");
+#endif
+ return path;
+ }
+
+ // Not found.
+#if DEBUG_PROBE
+ ALOGD("Probe failed to find input device configuration file: name='%s', type=%d",
+ name.string(), type);
+#endif
+ return String8();
+}
+
+
+// --- InputDeviceInfo ---
+
+InputDeviceInfo::InputDeviceInfo() {
+ initialize(-1, -1, InputDeviceIdentifier(), String8(), false);
+}
+
+InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
+ mId(other.mId), mGeneration(other.mGeneration), mIdentifier(other.mIdentifier),
+ mAlias(other.mAlias), mIsExternal(other.mIsExternal), mSources(other.mSources),
+ mKeyboardType(other.mKeyboardType),
+ mKeyCharacterMap(other.mKeyCharacterMap),
+ mHasVibrator(other.mHasVibrator),
+ mMotionRanges(other.mMotionRanges) {
+}
+
+InputDeviceInfo::~InputDeviceInfo() {
+}
+
+void InputDeviceInfo::initialize(int32_t id, int32_t generation,
+ const InputDeviceIdentifier& identifier, const String8& alias, bool isExternal) {
+ mId = id;
+ mGeneration = generation;
+ mIdentifier = identifier;
+ mAlias = alias;
+ mIsExternal = isExternal;
+ mSources = 0;
+ mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+ mHasVibrator = false;
+ mMotionRanges.clear();
+}
+
+const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(
+ int32_t axis, uint32_t source) const {
+ size_t numRanges = mMotionRanges.size();
+ for (size_t i = 0; i < numRanges; i++) {
+ const MotionRange& range = mMotionRanges.itemAt(i);
+ if (range.axis == axis && range.source == source) {
+ return &range;
+ }
+ }
+ return NULL;
+}
+
+void InputDeviceInfo::addSource(uint32_t source) {
+ mSources |= source;
+}
+
+void InputDeviceInfo::addMotionRange(int32_t axis, uint32_t source, float min, float max,
+ float flat, float fuzz, float resolution) {
+ MotionRange range = { axis, source, min, max, flat, fuzz, resolution };
+ mMotionRanges.add(range);
+}
+
+void InputDeviceInfo::addMotionRange(const MotionRange& range) {
+ mMotionRanges.add(range);
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputDevice.h b/widget/gonk/libui/InputDevice.h
new file mode 100644
index 000000000..0ab5863c9
--- /dev/null
+++ b/widget/gonk/libui/InputDevice.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_INPUT_DEVICE_H
+#define _ANDROIDFW_INPUT_DEVICE_H
+
+#include "Input.h"
+#include "KeyCharacterMap.h"
+
+namespace android {
+
+/*
+ * Identifies a device.
+ */
+struct InputDeviceIdentifier {
+ inline InputDeviceIdentifier() :
+ bus(0), vendor(0), product(0), version(0) {
+ }
+
+ // Information provided by the kernel.
+ String8 name;
+ String8 location;
+ String8 uniqueId;
+ uint16_t bus;
+ uint16_t vendor;
+ uint16_t product;
+ uint16_t version;
+
+ // A composite input device descriptor string that uniquely identifies the device
+ // even across reboots or reconnections. The value of this field is used by
+ // upper layers of the input system to associate settings with individual devices.
+ // It is hashed from whatever kernel provided information is available.
+ // Ideally, the way this value is computed should not change between Android releases
+ // because that would invalidate persistent settings that rely on it.
+ String8 descriptor;
+};
+
+/*
+ * Describes the characteristics and capabilities of an input device.
+ */
+class InputDeviceInfo {
+public:
+ InputDeviceInfo();
+ InputDeviceInfo(const InputDeviceInfo& other);
+ ~InputDeviceInfo();
+
+ struct MotionRange {
+ int32_t axis;
+ uint32_t source;
+ float min;
+ float max;
+ float flat;
+ float fuzz;
+ float resolution;
+ };
+
+ void initialize(int32_t id, int32_t generation, const InputDeviceIdentifier& identifier,
+ const String8& alias, bool isExternal);
+
+ inline int32_t getId() const { return mId; }
+ inline int32_t getGeneration() const { return mGeneration; }
+ inline const InputDeviceIdentifier& getIdentifier() const { return mIdentifier; }
+ inline const String8& getAlias() const { return mAlias; }
+ inline const String8& getDisplayName() const {
+ return mAlias.isEmpty() ? mIdentifier.name : mAlias;
+ }
+ inline bool isExternal() const { return mIsExternal; }
+ inline uint32_t getSources() const { return mSources; }
+
+ const MotionRange* getMotionRange(int32_t axis, uint32_t source) const;
+
+ void addSource(uint32_t source);
+ void addMotionRange(int32_t axis, uint32_t source,
+ float min, float max, float flat, float fuzz, float resolution);
+ void addMotionRange(const MotionRange& range);
+
+ inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
+ inline int32_t getKeyboardType() const { return mKeyboardType; }
+
+ inline void setKeyCharacterMap(const sp<KeyCharacterMap>& value) {
+ mKeyCharacterMap = value;
+ }
+
+ inline sp<KeyCharacterMap> getKeyCharacterMap() const {
+ return mKeyCharacterMap;
+ }
+
+ inline void setVibrator(bool hasVibrator) { mHasVibrator = hasVibrator; }
+ inline bool hasVibrator() const { return mHasVibrator; }
+
+ inline const Vector<MotionRange>& getMotionRanges() const {
+ return mMotionRanges;
+ }
+
+private:
+ int32_t mId;
+ int32_t mGeneration;
+ InputDeviceIdentifier mIdentifier;
+ String8 mAlias;
+ bool mIsExternal;
+ uint32_t mSources;
+ int32_t mKeyboardType;
+ sp<KeyCharacterMap> mKeyCharacterMap;
+ bool mHasVibrator;
+
+ Vector<MotionRange> mMotionRanges;
+};
+
+/* Types of input device configuration files. */
+enum InputDeviceConfigurationFileType {
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION = 0, /* .idc file */
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT = 1, /* .kl file */
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP = 2, /* .kcm file */
+};
+
+/*
+ * Gets the path of an input device configuration file, if one is available.
+ * Considers both system provided and user installed configuration files.
+ *
+ * The device identifier is used to construct several default configuration file
+ * names to try based on the device name, vendor, product, and version.
+ *
+ * Returns an empty string if not found.
+ */
+extern String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
+ const InputDeviceIdentifier& deviceIdentifier,
+ InputDeviceConfigurationFileType type);
+
+/*
+ * Gets the path of an input device configuration file, if one is available.
+ * Considers both system provided and user installed configuration files.
+ *
+ * The name is case-sensitive and is used to construct the filename to resolve.
+ * All characters except 'a'-'z', 'A'-'Z', '0'-'9', '-', and '_' are replaced by underscores.
+ *
+ * Returns an empty string if not found.
+ */
+extern String8 getInputDeviceConfigurationFilePathByName(
+ const String8& name, InputDeviceConfigurationFileType type);
+
+} // namespace android
+
+#endif // _ANDROIDFW_INPUT_DEVICE_H
diff --git a/widget/gonk/libui/InputDispatcher.cpp b/widget/gonk/libui/InputDispatcher.cpp
new file mode 100644
index 000000000..7adaa1998
--- /dev/null
+++ b/widget/gonk/libui/InputDispatcher.cpp
@@ -0,0 +1,4430 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputDispatcher"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+//#define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+// Log detailed debug messages about each inbound event notification to the dispatcher.
+#define DEBUG_INBOUND_EVENT_DETAILS 0
+
+// Log detailed debug messages about each outbound event processed by the dispatcher.
+#define DEBUG_OUTBOUND_EVENT_DETAILS 0
+
+// Log debug messages about the dispatch cycle.
+#define DEBUG_DISPATCH_CYCLE 0
+
+// Log debug messages about registrations.
+#define DEBUG_REGISTRATION 0
+
+// Log debug messages about input event injection.
+#define DEBUG_INJECTION 0
+
+// Log debug messages about input focus tracking.
+#define DEBUG_FOCUS 0
+
+// Log debug messages about the app switch latency optimization.
+#define DEBUG_APP_SWITCH 0
+
+// Log debug messages about hover events.
+#define DEBUG_HOVER 0
+
+#include "InputDispatcher.h"
+
+#include "Trace.h"
+#include "PowerManager.h"
+
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <time.h>
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+#define INDENT4 " "
+
+namespace android {
+
+// Default input dispatching timeout if there is no focused application or paused window
+// from which to determine an appropriate dispatching timeout.
+const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
+
+// Amount of time to allow for all pending events to be processed when an app switch
+// key is on the way. This is used to preempt input dispatch and drop input events
+// when an application takes too long to respond and the user has pressed an app switch key.
+const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
+// Amount of time to allow for an event to be dispatched (measured since its eventTime)
+// before considering it stale and dropping it.
+const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec
+
+// Amount of time to allow touch events to be streamed out to a connection before requiring
+// that the first event be finished. This value extends the ANR timeout by the specified
+// amount. For example, if streaming is allowed to get ahead by one second relative to the
+// queue of waiting unfinished events, then ANRs will similarly be delayed by one second.
+const nsecs_t STREAM_AHEAD_EVENT_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
+// Log a warning when an event takes longer than this to process, even if an ANR does not occur.
+const nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec
+
+
+static inline nsecs_t now() {
+ return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static inline int32_t getMotionEventActionPointerIndex(int32_t action) {
+ return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+}
+
+static bool isValidKeyAction(int32_t action) {
+ switch (action) {
+ case AKEY_EVENT_ACTION_DOWN:
+ case AKEY_EVENT_ACTION_UP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool validateKeyEvent(int32_t action) {
+ if (! isValidKeyAction(action)) {
+ ALOGE("Key event has invalid action code 0x%x", action);
+ return false;
+ }
+ return true;
+}
+
+static bool isValidMotionAction(int32_t action, size_t pointerCount) {
+ switch (action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_MOVE:
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ case AMOTION_EVENT_ACTION_HOVER_EXIT:
+ case AMOTION_EVENT_ACTION_SCROLL:
+ return true;
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP: {
+ int32_t index = getMotionEventActionPointerIndex(action);
+ return index >= 0 && size_t(index) < pointerCount;
+ }
+ default:
+ return false;
+ }
+}
+
+static bool validateMotionEvent(int32_t action, size_t pointerCount,
+ const PointerProperties* pointerProperties) {
+ if (! isValidMotionAction(action, pointerCount)) {
+ ALOGE("Motion event has invalid action code 0x%x", action);
+ return false;
+ }
+ if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
+ ALOGE("Motion event has invalid pointer count %d; value must be between 1 and %d.",
+ pointerCount, MAX_POINTERS);
+ return false;
+ }
+ BitSet32 pointerIdBits;
+ for (size_t i = 0; i < pointerCount; i++) {
+ int32_t id = pointerProperties[i].id;
+ if (id < 0 || id > MAX_POINTER_ID) {
+ ALOGE("Motion event has invalid pointer id %d; value must be between 0 and %d",
+ id, MAX_POINTER_ID);
+ return false;
+ }
+ if (pointerIdBits.hasBit(id)) {
+ ALOGE("Motion event has duplicate pointer id %d", id);
+ return false;
+ }
+ pointerIdBits.markBit(id);
+ }
+ return true;
+}
+
+static bool isMainDisplay(int32_t displayId) {
+ return displayId == ADISPLAY_ID_DEFAULT || displayId == ADISPLAY_ID_NONE;
+}
+
+static void dumpRegion(String8& dump, const SkRegion& region) {
+ if (region.isEmpty()) {
+ dump.append("<empty>");
+ return;
+ }
+
+ bool first = true;
+ for (SkRegion::Iterator it(region); !it.done(); it.next()) {
+ if (first) {
+ first = false;
+ } else {
+ dump.append("|");
+ }
+ const SkIRect& rect = it.rect();
+ dump.appendFormat("[%d,%d][%d,%d]", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+ }
+}
+
+
+// --- InputDispatcher ---
+
+InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
+ mPolicy(policy),
+ mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
+ mNextUnblockedEvent(NULL),
+ mDispatchEnabled(false), mDispatchFrozen(false), mInputFilterEnabled(false),
+ mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
+ mLooper = new Looper(false);
+
+ mKeyRepeatState.lastKeyEntry = NULL;
+
+ policy->getDispatcherConfiguration(&mConfig);
+}
+
+InputDispatcher::~InputDispatcher() {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ resetKeyRepeatLocked();
+ releasePendingEventLocked();
+ drainInboundQueueLocked();
+ }
+
+ while (mConnectionsByFd.size() != 0) {
+ unregisterInputChannel(mConnectionsByFd.valueAt(0)->inputChannel);
+ }
+}
+
+void InputDispatcher::dispatchOnce() {
+ nsecs_t nextWakeupTime = LONG_LONG_MAX;
+ { // acquire lock
+ AutoMutex _l(mLock);
+ mDispatcherIsAliveCondition.broadcast();
+
+ // Run a dispatch loop if there are no pending commands.
+ // The dispatch loop might enqueue commands to run afterwards.
+ if (!haveCommandsLocked()) {
+ dispatchOnceInnerLocked(&nextWakeupTime);
+ }
+
+ // Run all pending commands if there are any.
+ // If any commands were run then force the next poll to wake up immediately.
+ if (runCommandsLockedInterruptible()) {
+ nextWakeupTime = LONG_LONG_MIN;
+ }
+ } // release lock
+
+ // Wait for callback or timeout or wake. (make sure we round up, not down)
+ nsecs_t currentTime = now();
+ int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
+ mLooper->pollOnce(timeoutMillis);
+}
+
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
+ nsecs_t currentTime = now();
+
+ // Reset the key repeat timer whenever we disallow key events, even if the next event
+ // is not a key. This is to ensure that we abort a key repeat if the device is just coming
+ // out of sleep.
+ if (!mPolicy->isKeyRepeatEnabled()) {
+ resetKeyRepeatLocked();
+ }
+
+ // If dispatching is frozen, do not process timeouts or try to deliver any new events.
+ if (mDispatchFrozen) {
+#if DEBUG_FOCUS
+ ALOGD("Dispatch frozen. Waiting some more.");
+#endif
+ return;
+ }
+
+ // Optimize latency of app switches.
+ // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
+ // been pressed. When it expires, we preempt dispatch and drop all other pending events.
+ bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
+ if (mAppSwitchDueTime < *nextWakeupTime) {
+ *nextWakeupTime = mAppSwitchDueTime;
+ }
+
+ // Ready to start a new event.
+ // If we don't already have a pending event, go grab one.
+ if (! mPendingEvent) {
+ if (mInboundQueue.isEmpty()) {
+ if (isAppSwitchDue) {
+ // The inbound queue is empty so the app switch key we were waiting
+ // for will never arrive. Stop waiting for it.
+ resetPendingAppSwitchLocked(false);
+ isAppSwitchDue = false;
+ }
+
+ // Synthesize a key repeat if appropriate.
+ if (mKeyRepeatState.lastKeyEntry) {
+ if (currentTime >= mKeyRepeatState.nextRepeatTime) {
+ mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
+ } else {
+ if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
+ *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
+ }
+ }
+ }
+
+ // Nothing to do if there is no pending event.
+ if (!mPendingEvent) {
+ return;
+ }
+ } else {
+ // Inbound queue has at least one entry.
+ mPendingEvent = mInboundQueue.dequeueAtHead();
+ traceInboundQueueLengthLocked();
+ }
+
+ // Poke user activity for this event.
+ if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+ pokeUserActivityLocked(mPendingEvent);
+ }
+
+ // Get ready to dispatch the event.
+ resetANRTimeoutsLocked();
+ }
+
+ // Now we have an event to dispatch.
+ // All events are eventually dequeued and processed this way, even if we intend to drop them.
+ ALOG_ASSERT(mPendingEvent != NULL);
+ bool done = false;
+ DropReason dropReason = DROP_REASON_NOT_DROPPED;
+ if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+ dropReason = DROP_REASON_POLICY;
+ } else if (!mDispatchEnabled) {
+ dropReason = DROP_REASON_DISABLED;
+ }
+
+ if (mNextUnblockedEvent == mPendingEvent) {
+ mNextUnblockedEvent = NULL;
+ }
+
+ switch (mPendingEvent->type) {
+ case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+ ConfigurationChangedEntry* typedEntry =
+ static_cast<ConfigurationChangedEntry*>(mPendingEvent);
+ done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+ dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped
+ break;
+ }
+
+ case EventEntry::TYPE_DEVICE_RESET: {
+ DeviceResetEntry* typedEntry =
+ static_cast<DeviceResetEntry*>(mPendingEvent);
+ done = dispatchDeviceResetLocked(currentTime, typedEntry);
+ dropReason = DROP_REASON_NOT_DROPPED; // device resets are never dropped
+ break;
+ }
+
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
+ if (isAppSwitchDue) {
+ if (isAppSwitchKeyEventLocked(typedEntry)) {
+ resetPendingAppSwitchLocked(true);
+ isAppSwitchDue = false;
+ } else if (dropReason == DROP_REASON_NOT_DROPPED) {
+ dropReason = DROP_REASON_APP_SWITCH;
+ }
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
+ done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
+ if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
+ dropReason = DROP_REASON_APP_SWITCH;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
+ done = dispatchMotionLocked(currentTime, typedEntry,
+ &dropReason, nextWakeupTime);
+ break;
+ }
+
+ default:
+ ALOG_ASSERT(false);
+ break;
+ }
+
+ if (done) {
+ if (dropReason != DROP_REASON_NOT_DROPPED) {
+ dropInboundEventLocked(mPendingEvent, dropReason);
+ }
+
+ releasePendingEventLocked();
+ *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
+ }
+}
+
+bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
+ bool needWake = mInboundQueue.isEmpty();
+ mInboundQueue.enqueueAtTail(entry);
+ traceInboundQueueLengthLocked();
+
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY: {
+ // Optimize app switch latency.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the app switch key.
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
+ if (isAppSwitchKeyEventLocked(keyEntry)) {
+ if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
+ mAppSwitchSawKeyDown = true;
+ } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
+ if (mAppSwitchSawKeyDown) {
+#if DEBUG_APP_SWITCH
+ ALOGD("App switch is pending!");
+#endif
+ mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
+ mAppSwitchSawKeyDown = false;
+ needWake = true;
+ }
+ }
+ }
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ // Optimize case where the current application is unresponsive and the user
+ // decides to touch a window in a different application.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the touch into the other window.
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN
+ && (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
+ && mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
+ && mInputTargetWaitApplicationHandle != NULL) {
+ int32_t displayId = motionEntry->displayId;
+ int32_t x = int32_t(motionEntry->pointerCoords[0].
+ getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = int32_t(motionEntry->pointerCoords[0].
+ getAxisValue(AMOTION_EVENT_AXIS_Y));
+ sp<InputWindowHandle> touchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y);
+ if (touchedWindowHandle != NULL
+ && touchedWindowHandle->inputApplicationHandle
+ != mInputTargetWaitApplicationHandle) {
+ // User touched a different application than the one we are waiting on.
+ // Flag the event, and start pruning the input queue.
+ mNextUnblockedEvent = motionEntry;
+ needWake = true;
+ }
+ }
+ break;
+ }
+ }
+
+ return needWake;
+}
+
+sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId,
+ int32_t x, int32_t y) {
+ // Traverse windows from front to back to find touched window.
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ if (windowInfo->displayId == displayId) {
+ int32_t flags = windowInfo->layoutParamsFlags;
+
+ if (windowInfo->visible) {
+ if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
+ bool isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
+ | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
+ // Found window.
+ return windowHandle;
+ }
+ }
+ }
+
+ if (flags & InputWindowInfo::FLAG_SYSTEM_ERROR) {
+ // Error window is on top but not visible, so touch is dropped.
+ return NULL;
+ }
+ }
+ }
+ return NULL;
+}
+
+void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) {
+ const char* reason;
+ switch (dropReason) {
+ case DROP_REASON_POLICY:
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("Dropped event because policy consumed it.");
+#endif
+ reason = "inbound event was dropped because the policy consumed it";
+ break;
+ case DROP_REASON_DISABLED:
+ ALOGI("Dropped event because input dispatch is disabled.");
+ reason = "inbound event was dropped because input dispatch is disabled";
+ break;
+ case DROP_REASON_APP_SWITCH:
+ ALOGI("Dropped event because of pending overdue app switch.");
+ reason = "inbound event was dropped because of pending overdue app switch";
+ break;
+ case DROP_REASON_BLOCKED:
+ ALOGI("Dropped event because the current application is not responding and the user "
+ "has started interacting with a different application.");
+ reason = "inbound event was dropped because the current application is not responding "
+ "and the user has started interacting with a different application";
+ break;
+ case DROP_REASON_STALE:
+ ALOGI("Dropped event because it is stale.");
+ reason = "inbound event was dropped because it is stale";
+ break;
+ default:
+ ALOG_ASSERT(false);
+ return;
+ }
+
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY: {
+ CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ break;
+ }
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ } else {
+ CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ }
+ break;
+ }
+ }
+}
+
+bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) {
+ return keyCode == AKEYCODE_HOME
+ || keyCode == AKEYCODE_ENDCALL
+ || keyCode == AKEYCODE_APP_SWITCH;
+}
+
+bool InputDispatcher::isAppSwitchKeyEventLocked(KeyEntry* keyEntry) {
+ return ! (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
+ && isAppSwitchKeyCode(keyEntry->keyCode)
+ && (keyEntry->policyFlags & POLICY_FLAG_TRUSTED)
+ && (keyEntry->policyFlags & POLICY_FLAG_PASS_TO_USER);
+}
+
+bool InputDispatcher::isAppSwitchPendingLocked() {
+ return mAppSwitchDueTime != LONG_LONG_MAX;
+}
+
+void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
+ mAppSwitchDueTime = LONG_LONG_MAX;
+
+#if DEBUG_APP_SWITCH
+ if (handled) {
+ ALOGD("App switch has arrived.");
+ } else {
+ ALOGD("App switch was abandoned.");
+ }
+#endif
+}
+
+bool InputDispatcher::isStaleEventLocked(nsecs_t currentTime, EventEntry* entry) {
+ return currentTime - entry->eventTime >= STALE_EVENT_TIMEOUT;
+}
+
+bool InputDispatcher::haveCommandsLocked() const {
+ return !mCommandQueue.isEmpty();
+}
+
+bool InputDispatcher::runCommandsLockedInterruptible() {
+ if (mCommandQueue.isEmpty()) {
+ return false;
+ }
+
+ do {
+ CommandEntry* commandEntry = mCommandQueue.dequeueAtHead();
+
+ Command command = commandEntry->command;
+ (this->*command)(commandEntry); // commands are implicitly 'LockedInterruptible'
+
+ commandEntry->connection.clear();
+ delete commandEntry;
+ } while (! mCommandQueue.isEmpty());
+ return true;
+}
+
+InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
+ CommandEntry* commandEntry = new CommandEntry(command);
+ mCommandQueue.enqueueAtTail(commandEntry);
+ return commandEntry;
+}
+
+void InputDispatcher::drainInboundQueueLocked() {
+ while (! mInboundQueue.isEmpty()) {
+ EventEntry* entry = mInboundQueue.dequeueAtHead();
+ releaseInboundEventLocked(entry);
+ }
+ traceInboundQueueLengthLocked();
+}
+
+void InputDispatcher::releasePendingEventLocked() {
+ if (mPendingEvent) {
+ resetANRTimeoutsLocked();
+ releaseInboundEventLocked(mPendingEvent);
+ mPendingEvent = NULL;
+ }
+}
+
+void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState && injectionState->injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("Injected inbound event was dropped.");
+#endif
+ setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+ }
+ if (entry == mNextUnblockedEvent) {
+ mNextUnblockedEvent = NULL;
+ }
+ entry->release();
+}
+
+void InputDispatcher::resetKeyRepeatLocked() {
+ if (mKeyRepeatState.lastKeyEntry) {
+ mKeyRepeatState.lastKeyEntry->release();
+ mKeyRepeatState.lastKeyEntry = NULL;
+ }
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked(nsecs_t currentTime) {
+ KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
+
+ // Reuse the repeated key entry if it is otherwise unreferenced.
+ uint32_t policyFlags = (entry->policyFlags & POLICY_FLAG_RAW_MASK)
+ | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_TRUSTED;
+ if (entry->refCount == 1) {
+ entry->recycle();
+ entry->eventTime = currentTime;
+ entry->policyFlags = policyFlags;
+ entry->repeatCount += 1;
+ } else {
+ KeyEntry* newEntry = new KeyEntry(currentTime,
+ entry->deviceId, entry->source, policyFlags,
+ entry->action, entry->flags, entry->keyCode, entry->scanCode,
+ entry->metaState, entry->repeatCount + 1, entry->downTime);
+
+ mKeyRepeatState.lastKeyEntry = newEntry;
+ entry->release();
+
+ entry = newEntry;
+ }
+ entry->syntheticRepeat = true;
+
+ // Increment reference count since we keep a reference to the event in
+ // mKeyRepeatState.lastKeyEntry in addition to the one we return.
+ entry->refCount += 1;
+
+ mKeyRepeatState.nextRepeatTime = currentTime + mConfig.keyRepeatDelay;
+ return entry;
+}
+
+bool InputDispatcher::dispatchConfigurationChangedLocked(
+ nsecs_t currentTime, ConfigurationChangedEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime);
+#endif
+
+ // Reset key repeating in case a keyboard device was added or removed or something.
+ resetKeyRepeatLocked();
+
+ // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyConfigurationChangedInterruptible);
+ commandEntry->eventTime = entry->eventTime;
+ return true;
+}
+
+bool InputDispatcher::dispatchDeviceResetLocked(
+ nsecs_t currentTime, DeviceResetEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("dispatchDeviceReset - eventTime=%lld, deviceId=%d", entry->eventTime, entry->deviceId);
+#endif
+
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
+ "device was reset");
+ options.deviceId = entry->deviceId;
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ return true;
+}
+
+bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ if (entry->repeatCount == 0
+ && entry->action == AKEY_EVENT_ACTION_DOWN
+ && (entry->policyFlags & POLICY_FLAG_TRUSTED)
+ && (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) {
+ if (mKeyRepeatState.lastKeyEntry
+ && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
+ // We have seen two identical key downs in a row which indicates that the device
+ // driver is automatically generating key repeats itself. We take note of the
+ // repeat here, but we disable our own next key repeat timer since it is clear that
+ // we will not need to synthesize key repeats ourselves.
+ entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
+ resetKeyRepeatLocked();
+ mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+ } else {
+ // Not a repeat. Save key down state in case we do see a repeat later.
+ resetKeyRepeatLocked();
+ mKeyRepeatState.nextRepeatTime = entry->eventTime + mConfig.keyRepeatTimeout;
+ }
+ mKeyRepeatState.lastKeyEntry = entry;
+ entry->refCount += 1;
+ } else if (! entry->syntheticRepeat) {
+ resetKeyRepeatLocked();
+ }
+
+ if (entry->repeatCount == 1) {
+ entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
+ } else {
+ entry->flags &= ~AKEY_EVENT_FLAG_LONG_PRESS;
+ }
+
+ entry->dispatchInProgress = true;
+
+ logOutboundKeyDetailsLocked("dispatchKey - ", entry);
+ }
+
+ // Handle case where the policy asked us to try again later last time.
+ if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
+ if (currentTime < entry->interceptKeyWakeupTime) {
+ if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
+ *nextWakeupTime = entry->interceptKeyWakeupTime;
+ }
+ return false; // wait until next wakeup
+ }
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+ entry->interceptKeyWakeupTime = 0;
+ }
+
+ // Give the policy a chance to intercept the key.
+ if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+ if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
+ if (mFocusedWindowHandle != NULL) {
+ commandEntry->inputWindowHandle = mFocusedWindowHandle;
+ }
+ commandEntry->keyEntry = entry;
+ entry->refCount += 1;
+ return false; // wait for the command to run
+ } else {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ }
+ } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+ if (*dropReason == DROP_REASON_NOT_DROPPED) {
+ *dropReason = DROP_REASON_POLICY;
+ }
+ }
+
+ // Clean up if dropping the event.
+ if (*dropReason != DROP_REASON_NOT_DROPPED) {
+ setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+ ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+ return true;
+ }
+
+ // Identify targets.
+ Vector<InputTarget> inputTargets;
+ int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
+ entry, inputTargets, nextWakeupTime);
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
+
+ addMonitoringTargetsLocked(inputTargets);
+
+ // Dispatch the key.
+ dispatchEventLocked(currentTime, entry, inputTargets);
+ return true;
+}
+
+void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
+ "repeatCount=%d, downTime=%lld",
+ prefix,
+ entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+ entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
+ entry->repeatCount, entry->downTime);
+#endif
+}
+
+bool InputDispatcher::dispatchMotionLocked(
+ nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ entry->dispatchInProgress = true;
+
+ logOutboundMotionDetailsLocked("dispatchMotion - ", entry);
+ }
+
+ // Clean up if dropping the event.
+ if (*dropReason != DROP_REASON_NOT_DROPPED) {
+ setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+ ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+ return true;
+ }
+
+ bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
+
+ // Identify targets.
+ Vector<InputTarget> inputTargets;
+
+ bool conflictingPointerActions = false;
+ int32_t injectionResult;
+ if (isPointerEvent) {
+ // Pointer event. (eg. touchscreen)
+ injectionResult = findTouchedWindowTargetsLocked(currentTime,
+ entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
+ } else {
+ // Non touch event. (eg. trackball)
+ injectionResult = findFocusedWindowTargetsLocked(currentTime,
+ entry, inputTargets, nextWakeupTime);
+ }
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
+
+ // TODO: support sending secondary display events to input monitors
+ if (isMainDisplay(entry->displayId)) {
+ addMonitoringTargetsLocked(inputTargets);
+ }
+
+ // Dispatch the motion.
+ if (conflictingPointerActions) {
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ "conflicting pointer actions");
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ }
+ dispatchEventLocked(currentTime, entry, inputTargets);
+ return true;
+}
+
+
+void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, "
+ "metaState=0x%x, buttonState=0x%x, "
+ "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
+ prefix,
+ entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+ entry->action, entry->flags,
+ entry->metaState, entry->buttonState,
+ entry->edgeFlags, entry->xPrecision, entry->yPrecision,
+ entry->downTime);
+
+ for (uint32_t i = 0; i < entry->pointerCount; i++) {
+ ALOGD(" Pointer %d: id=%d, toolType=%d, "
+ "x=%f, y=%f, pressure=%f, size=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+ "orientation=%f",
+ i, entry->pointerProperties[i].id,
+ entry->pointerProperties[i].toolType,
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+ }
+#endif
+}
+
+void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
+ EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("dispatchEventToCurrentInputTargets");
+#endif
+
+ ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
+
+ pokeUserActivityLocked(eventEntry);
+
+ for (size_t i = 0; i < inputTargets.size(); i++) {
+ const InputTarget& inputTarget = inputTargets.itemAt(i);
+
+ ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
+ } else {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event delivery to target with channel '%s' because it "
+ "is no longer registered with the input dispatcher.",
+ inputTarget.inputChannel->getName().string());
+#endif
+ }
+ }
+}
+
+int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
+ const EventEntry* entry,
+ const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t* nextWakeupTime, const char* reason) {
+ if (applicationHandle == NULL && windowHandle == NULL) {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
+#if DEBUG_FOCUS
+ ALOGD("Waiting for system to become ready for input. Reason: %s", reason);
+#endif
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
+ mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplicationHandle.clear();
+ }
+ } else {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+#if DEBUG_FOCUS
+ ALOGD("Waiting for application to become ready for input: %s. Reason: %s",
+ getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
+ reason);
+#endif
+ nsecs_t timeout;
+ if (windowHandle != NULL) {
+ timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
+ } else if (applicationHandle != NULL) {
+ timeout = applicationHandle->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT);
+ } else {
+ timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+ }
+
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = currentTime + timeout;
+ mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplicationHandle.clear();
+
+ if (windowHandle != NULL) {
+ mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
+ }
+ if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
+ mInputTargetWaitApplicationHandle = applicationHandle;
+ }
+ }
+ }
+
+ if (mInputTargetWaitTimeoutExpired) {
+ return INPUT_EVENT_INJECTION_TIMED_OUT;
+ }
+
+ if (currentTime >= mInputTargetWaitTimeoutTime) {
+ onANRLocked(currentTime, applicationHandle, windowHandle,
+ entry->eventTime, mInputTargetWaitStartTime, reason);
+
+ // Force poll loop to wake up immediately on next iteration once we get the
+ // ANR response back from the policy.
+ *nextWakeupTime = LONG_LONG_MIN;
+ return INPUT_EVENT_INJECTION_PENDING;
+ } else {
+ // Force poll loop to wake up when timeout is due.
+ if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
+ *nextWakeupTime = mInputTargetWaitTimeoutTime;
+ }
+ return INPUT_EVENT_INJECTION_PENDING;
+ }
+}
+
+void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+ const sp<InputChannel>& inputChannel) {
+ if (newTimeout > 0) {
+ // Extend the timeout.
+ mInputTargetWaitTimeoutTime = now() + newTimeout;
+ } else {
+ // Give up.
+ mInputTargetWaitTimeoutExpired = true;
+
+ // Input state will not be realistic. Mark it out of sync.
+ if (inputChannel.get()) {
+ ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ sp<InputWindowHandle> windowHandle = connection->inputWindowHandle;
+
+ if (windowHandle != NULL) {
+ mTouchState.removeWindow(windowHandle);
+ }
+
+ if (connection->status == Connection::STATUS_NORMAL) {
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
+ "application not responding");
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
+ }
+ }
+ }
+ }
+}
+
+nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationLocked(
+ nsecs_t currentTime) {
+ if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+ return currentTime - mInputTargetWaitStartTime;
+ }
+ return 0;
+}
+
+void InputDispatcher::resetANRTimeoutsLocked() {
+#if DEBUG_FOCUS
+ ALOGD("Resetting ANR timeouts.");
+#endif
+
+ // Reset input target wait timeout.
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+ mInputTargetWaitApplicationHandle.clear();
+}
+
+int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
+ const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
+ int32_t injectionResult;
+
+ // If there is no currently focused window and no focused application
+ // then drop the event.
+ if (mFocusedWindowHandle == NULL) {
+ if (mFocusedApplicationHandle != NULL) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, NULL, nextWakeupTime,
+ "Waiting because no window has focus but there is a "
+ "focused application that may eventually add a window "
+ "when it finishes starting up.");
+ goto Unresponsive;
+ }
+
+ ALOGI("Dropping event because there is no focused window or focused application.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Check permissions.
+ if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+
+ // If the currently focused window is paused then keep waiting.
+ if (mFocusedWindowHandle->getInfo()->paused) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime,
+ "Waiting because the focused window is paused.");
+ goto Unresponsive;
+ }
+
+ // If the currently focused window is still working on previous events then keep waiting.
+ if (!isWindowReadyForMoreInputLocked(currentTime, mFocusedWindowHandle, entry)) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime,
+ "Waiting because the focused window has not finished "
+ "processing the input events that were previously delivered to it.");
+ goto Unresponsive;
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ addWindowTargetLocked(mFocusedWindowHandle,
+ InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
+ inputTargets);
+
+ // Done.
+Failed:
+Unresponsive:
+ nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+ updateDispatchStatisticsLocked(currentTime, entry,
+ injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+ ALOGD("findFocusedWindow finished: injectionResult=%d, "
+ "timeSpentWaitingForApplication=%0.1fms",
+ injectionResult, timeSpentWaitingForApplication / 1000000.0);
+#endif
+ return injectionResult;
+}
+
+int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
+ const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
+ bool* outConflictingPointerActions) {
+ enum InjectionPermission {
+ INJECTION_PERMISSION_UNKNOWN,
+ INJECTION_PERMISSION_GRANTED,
+ INJECTION_PERMISSION_DENIED
+ };
+
+ // For security reasons, we defer updating the touch state until we are sure that
+ // event injection will be allowed.
+ //
+ // FIXME In the original code, screenWasOff could never be set to true.
+ // The reason is that the POLICY_FLAG_WOKE_HERE
+ // and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw
+ // EV_KEY, EV_REL and EV_ABS events. As it happens, the touch event was
+ // actually enqueued using the policyFlags that appeared in the final EV_SYN
+ // events upon which no preprocessing took place. So policyFlags was always 0.
+ // In the new native input dispatcher we're a bit more careful about event
+ // preprocessing so the touches we receive can actually have non-zero policyFlags.
+ // Unfortunately we obtain undesirable behavior.
+ //
+ // Here's what happens:
+ //
+ // When the device dims in anticipation of going to sleep, touches
+ // in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause
+ // the device to brighten and reset the user activity timer.
+ // Touches on other windows (such as the launcher window)
+ // are dropped. Then after a moment, the device goes to sleep. Oops.
+ //
+ // Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE
+ // instead of POLICY_FLAG_WOKE_HERE...
+ //
+ bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE;
+
+ int32_t displayId = entry->displayId;
+ int32_t action = entry->action;
+ int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+
+ // Update the touch state as needed based on the properties of the touch event.
+ int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING;
+ InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ sp<InputWindowHandle> newHoverWindowHandle;
+
+ bool isSplit = mTouchState.split;
+ bool switchedDevice = mTouchState.deviceId >= 0 && mTouchState.displayId >= 0
+ && (mTouchState.deviceId != entry->deviceId
+ || mTouchState.source != entry->source
+ || mTouchState.displayId != displayId);
+ bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
+ bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ || maskedAction == AMOTION_EVENT_ACTION_SCROLL
+ || isHoverAction);
+ bool wrongDevice = false;
+ if (newGesture) {
+ bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
+ if (switchedDevice && mTouchState.down && !down) {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event because a pointer for a different device is already down.");
+#endif
+ mTempTouchState.copyFrom(mTouchState);
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ switchedDevice = false;
+ wrongDevice = true;
+ goto Failed;
+ }
+ mTempTouchState.reset();
+ mTempTouchState.down = down;
+ mTempTouchState.deviceId = entry->deviceId;
+ mTempTouchState.source = entry->source;
+ mTempTouchState.displayId = displayId;
+ isSplit = false;
+ } else {
+ mTempTouchState.copyFrom(mTouchState);
+ }
+
+ if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
+ /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
+
+ int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+ int32_t x = int32_t(entry->pointerCoords[pointerIndex].
+ getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = int32_t(entry->pointerCoords[pointerIndex].
+ getAxisValue(AMOTION_EVENT_AXIS_Y));
+ sp<InputWindowHandle> newTouchedWindowHandle;
+ sp<InputWindowHandle> topErrorWindowHandle;
+ bool isTouchModal = false;
+
+ // Traverse windows from front to back to find touched window and outside targets.
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ if (windowInfo->displayId != displayId) {
+ continue; // wrong display
+ }
+
+ int32_t flags = windowInfo->layoutParamsFlags;
+ if (flags & InputWindowInfo::FLAG_SYSTEM_ERROR) {
+ if (topErrorWindowHandle == NULL) {
+ topErrorWindowHandle = windowHandle;
+ }
+ }
+
+ if (windowInfo->visible) {
+ if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
+ isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
+ | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
+ if (! screenWasOff
+ || (flags & InputWindowInfo::FLAG_TOUCHABLE_WHEN_WAKING)) {
+ newTouchedWindowHandle = windowHandle;
+ }
+ break; // found touched window, exit window loop
+ }
+ }
+
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
+ int32_t outsideTargetFlags = InputTarget::FLAG_DISPATCH_AS_OUTSIDE;
+ if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
+ outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ mTempTouchState.addOrUpdateWindow(
+ windowHandle, outsideTargetFlags, BitSet32(0));
+ }
+ }
+ }
+
+ // If there is an error window but it is not taking focus (typically because
+ // it is invisible) then wait for it. Any other focused window may in
+ // fact be in ANR state.
+ if (topErrorWindowHandle != NULL && newTouchedWindowHandle != topErrorWindowHandle) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, NULL, nextWakeupTime,
+ "Waiting because a system error window is about to be displayed.");
+ injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ goto Unresponsive;
+ }
+
+ // Figure out whether splitting will be allowed for this window.
+ if (newTouchedWindowHandle != NULL
+ && newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
+ // New window supports splitting.
+ isSplit = true;
+ } else if (isSplit) {
+ // New window does not support splitting but we have already split events.
+ // Ignore the new window.
+ newTouchedWindowHandle = NULL;
+ }
+
+ // Handle the case where we did not find a window.
+ if (newTouchedWindowHandle == NULL) {
+ // Try to assign the pointer to the first foreground window we find, if there is one.
+ newTouchedWindowHandle = mTempTouchState.getFirstForegroundWindowHandle();
+ if (newTouchedWindowHandle == NULL) {
+ // There is no touched window. If this is an initial down event
+ // then wait for a window to appear that will handle the touch. This is
+ // to ensure that we report an ANR in the case where an application has started
+ // but not yet put up a window and the user is starting to get impatient.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ && mFocusedApplicationHandle != NULL) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, NULL, nextWakeupTime,
+ "Waiting because there is no touchable window that can "
+ "handle the event but there is focused application that may "
+ "eventually add a new window when it finishes starting up.");
+ goto Unresponsive;
+ }
+
+ ALOGI("Dropping event because there is no touched window.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+ }
+
+ // Set target flags.
+ int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS;
+ if (isSplit) {
+ targetFlags |= InputTarget::FLAG_SPLIT;
+ }
+ if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ // Update hover state.
+ if (isHoverAction) {
+ newHoverWindowHandle = newTouchedWindowHandle;
+ } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
+ newHoverWindowHandle = mLastHoverWindowHandle;
+ }
+
+ // Update the temporary touch state.
+ BitSet32 pointerIds;
+ if (isSplit) {
+ uint32_t pointerId = entry->pointerProperties[pointerIndex].id;
+ pointerIds.markBit(pointerId);
+ }
+ mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+ } else {
+ /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
+
+ // If the pointer is not currently down, then ignore the event.
+ if (! mTempTouchState.down) {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event because the pointer is not down or we previously "
+ "dropped the pointer down event.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Check whether touches should slip outside of the current foreground window.
+ if (maskedAction == AMOTION_EVENT_ACTION_MOVE
+ && entry->pointerCount == 1
+ && mTempTouchState.isSlippery()) {
+ int32_t x = int32_t(entry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = int32_t(entry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
+
+ sp<InputWindowHandle> oldTouchedWindowHandle =
+ mTempTouchState.getFirstForegroundWindowHandle();
+ sp<InputWindowHandle> newTouchedWindowHandle =
+ findTouchedWindowAtLocked(displayId, x, y);
+ if (oldTouchedWindowHandle != newTouchedWindowHandle
+ && newTouchedWindowHandle != NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Touch is slipping out of window %s into window %s.",
+ oldTouchedWindowHandle->getName().string(),
+ newTouchedWindowHandle->getName().string());
+#endif
+ // Make a slippery exit from the old window.
+ mTempTouchState.addOrUpdateWindow(oldTouchedWindowHandle,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT, BitSet32(0));
+
+ // Make a slippery entrance into the new window.
+ if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
+ isSplit = true;
+ }
+
+ int32_t targetFlags = InputTarget::FLAG_FOREGROUND
+ | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER;
+ if (isSplit) {
+ targetFlags |= InputTarget::FLAG_SPLIT;
+ }
+ if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ BitSet32 pointerIds;
+ if (isSplit) {
+ pointerIds.markBit(entry->pointerProperties[0].id);
+ }
+ mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+ }
+ }
+ }
+
+ if (newHoverWindowHandle != mLastHoverWindowHandle) {
+ // Let the previous window know that the hover sequence is over.
+ if (mLastHoverWindowHandle != NULL) {
+#if DEBUG_HOVER
+ ALOGD("Sending hover exit event to window %s.",
+ mLastHoverWindowHandle->getName().string());
+#endif
+ mTempTouchState.addOrUpdateWindow(mLastHoverWindowHandle,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0));
+ }
+
+ // Let the new window know that the hover sequence is starting.
+ if (newHoverWindowHandle != NULL) {
+#if DEBUG_HOVER
+ ALOGD("Sending hover enter event to window %s.",
+ newHoverWindowHandle->getName().string());
+#endif
+ mTempTouchState.addOrUpdateWindow(newHoverWindowHandle,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER, BitSet32(0));
+ }
+ }
+
+ // Check permission to inject into all touched foreground windows and ensure there
+ // is at least one touched foreground window.
+ {
+ bool haveForegroundWindow = false;
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ haveForegroundWindow = true;
+ if (! checkInjectionPermission(touchedWindow.windowHandle,
+ entry->injectionState)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+ }
+ }
+ if (! haveForegroundWindow) {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event because there is no touched foreground window to receive it.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Permission granted to injection into all touched foreground windows.
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ }
+
+ // Check whether windows listening for outside touches are owned by the same UID. If it is
+ // set the policy flag that we will not reveal coordinate information to this window.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ sp<InputWindowHandle> foregroundWindowHandle =
+ mTempTouchState.getFirstForegroundWindowHandle();
+ const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+ sp<InputWindowHandle> inputWindowHandle = touchedWindow.windowHandle;
+ if (inputWindowHandle->getInfo()->ownerUid != foregroundWindowUid) {
+ mTempTouchState.addOrUpdateWindow(inputWindowHandle,
+ InputTarget::FLAG_ZERO_COORDS, BitSet32(0));
+ }
+ }
+ }
+ }
+
+ // Ensure all touched foreground windows are ready for new input.
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ // If the touched window is paused then keep waiting.
+ if (touchedWindow.windowHandle->getInfo()->paused) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, touchedWindow.windowHandle, nextWakeupTime,
+ "Waiting because the touched window is paused.");
+ goto Unresponsive;
+ }
+
+ // If the touched window is still working on previous events then keep waiting.
+ if (!isWindowReadyForMoreInputLocked(currentTime, touchedWindow.windowHandle, entry)) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, touchedWindow.windowHandle, nextWakeupTime,
+ "Waiting because the touched window has not finished "
+ "processing the input events that were previously delivered to it.");
+ goto Unresponsive;
+ }
+ }
+ }
+
+ // If this is the first pointer going down and the touched window has a wallpaper
+ // then also add the touched wallpaper windows so they are locked in for the duration
+ // of the touch gesture.
+ // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
+ // engine only supports touch events. We would need to add a mechanism similar
+ // to View.onGenericMotionEvent to enable wallpapers to handle these events.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ sp<InputWindowHandle> foregroundWindowHandle =
+ mTempTouchState.getFirstForegroundWindowHandle();
+ if (foregroundWindowHandle->getInfo()->hasWallpaper) {
+ for (size_t i = 0; i < mWindowHandles.size(); i++) {
+ sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* info = windowHandle->getInfo();
+ if (info->displayId == displayId
+ && windowHandle->getInfo()->layoutParamsType
+ == InputWindowInfo::TYPE_WALLPAPER) {
+ mTempTouchState.addOrUpdateWindow(windowHandle,
+ InputTarget::FLAG_WINDOW_IS_OBSCURED
+ | InputTarget::FLAG_DISPATCH_AS_IS,
+ BitSet32(0));
+ }
+ }
+ }
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
+ addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+ touchedWindow.pointerIds, inputTargets);
+ }
+
+ // Drop the outside or hover touch windows since we will not care about them
+ // in the next iteration.
+ mTempTouchState.filterNonAsIsTouchWindows();
+
+Failed:
+ // Check injection permission once and for all.
+ if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
+ if (checkInjectionPermission(NULL, entry->injectionState)) {
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ } else {
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ }
+ }
+
+ // Update final pieces of touch state if the injector had permission.
+ if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
+ if (!wrongDevice) {
+ if (switchedDevice) {
+#if DEBUG_FOCUS
+ ALOGD("Conflicting pointer actions: Switched to a different device.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+
+ if (isHoverAction) {
+ // Started hovering, therefore no longer down.
+ if (mTouchState.down) {
+#if DEBUG_FOCUS
+ ALOGD("Conflicting pointer actions: Hover received while pointer was down.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+ mTouchState.reset();
+ if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+ mTouchState.deviceId = entry->deviceId;
+ mTouchState.source = entry->source;
+ mTouchState.displayId = displayId;
+ }
+ } else if (maskedAction == AMOTION_EVENT_ACTION_UP
+ || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ // All pointers up or canceled.
+ mTouchState.reset();
+ } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ // First pointer went down.
+ if (mTouchState.down) {
+#if DEBUG_FOCUS
+ ALOGD("Conflicting pointer actions: Down received while already down.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+ mTouchState.copyFrom(mTempTouchState);
+ } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+ // One pointer went up.
+ if (isSplit) {
+ int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+ uint32_t pointerId = entry->pointerProperties[pointerIndex].id;
+
+ for (size_t i = 0; i < mTempTouchState.windows.size(); ) {
+ TouchedWindow& touchedWindow = mTempTouchState.windows.editItemAt(i);
+ if (touchedWindow.targetFlags & InputTarget::FLAG_SPLIT) {
+ touchedWindow.pointerIds.clearBit(pointerId);
+ if (touchedWindow.pointerIds.isEmpty()) {
+ mTempTouchState.windows.removeAt(i);
+ continue;
+ }
+ }
+ i += 1;
+ }
+ }
+ mTouchState.copyFrom(mTempTouchState);
+ } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
+ // Discard temporary touch state since it was only valid for this action.
+ } else {
+ // Save changes to touch state as-is for all other actions.
+ mTouchState.copyFrom(mTempTouchState);
+ }
+
+ // Update hover state.
+ mLastHoverWindowHandle = newHoverWindowHandle;
+ }
+ } else {
+#if DEBUG_FOCUS
+ ALOGD("Not updating touch focus because injection was denied.");
+#endif
+ }
+
+Unresponsive:
+ // Reset temporary touch state to ensure we release unnecessary references to input channels.
+ mTempTouchState.reset();
+
+ nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+ updateDispatchStatisticsLocked(currentTime, entry,
+ injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+ ALOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, "
+ "timeSpentWaitingForApplication=%0.1fms",
+ injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0);
+#endif
+ return injectionResult;
+}
+
+void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {
+ inputTargets.push();
+
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ InputTarget& target = inputTargets.editTop();
+ target.inputChannel = windowInfo->inputChannel;
+ target.flags = targetFlags;
+ target.xOffset = - windowInfo->frameLeft;
+ target.yOffset = - windowInfo->frameTop;
+ target.scaleFactor = windowInfo->scaleFactor;
+ target.pointerIds = pointerIds;
+}
+
+void InputDispatcher::addMonitoringTargetsLocked(Vector<InputTarget>& inputTargets) {
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ inputTargets.push();
+
+ InputTarget& target = inputTargets.editTop();
+ target.inputChannel = mMonitoringChannels[i];
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+ target.xOffset = 0;
+ target.yOffset = 0;
+ target.pointerIds.clear();
+ target.scaleFactor = 1.0f;
+ }
+}
+
+bool InputDispatcher::checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
+ const InjectionState* injectionState) {
+ if (injectionState
+ && (windowHandle == NULL
+ || windowHandle->getInfo()->ownerUid != injectionState->injectorUid)
+ && !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
+ if (windowHandle != NULL) {
+ ALOGW("Permission denied: injecting event from pid %d uid %d to window %s "
+ "owned by uid %d",
+ injectionState->injectorPid, injectionState->injectorUid,
+ windowHandle->getName().string(),
+ windowHandle->getInfo()->ownerUid);
+ } else {
+ ALOGW("Permission denied: injecting event from pid %d uid %d",
+ injectionState->injectorPid, injectionState->injectorUid);
+ }
+ return false;
+ }
+ return true;
+}
+
+bool InputDispatcher::isWindowObscuredAtPointLocked(
+ const sp<InputWindowHandle>& windowHandle, int32_t x, int32_t y) const {
+ int32_t displayId = windowHandle->getInfo()->displayId;
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ sp<InputWindowHandle> otherHandle = mWindowHandles.itemAt(i);
+ if (otherHandle == windowHandle) {
+ break;
+ }
+
+ const InputWindowInfo* otherInfo = otherHandle->getInfo();
+ if (otherInfo->displayId == displayId
+ && otherInfo->visible && !otherInfo->isTrustedOverlay()
+ && otherInfo->frameContainsPoint(x, y)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::isWindowReadyForMoreInputLocked(nsecs_t currentTime,
+ const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry) {
+ ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ if (connection->inputPublisherBlocked) {
+ return false;
+ }
+ if (eventEntry->type == EventEntry::TYPE_KEY) {
+ // If the event is a key event, then we must wait for all previous events to
+ // complete before delivering it because previous events may have the
+ // side-effect of transferring focus to a different window and we want to
+ // ensure that the following keys are sent to the new window.
+ //
+ // Suppose the user touches a button in a window then immediately presses "A".
+ // If the button causes a pop-up window to appear then we want to ensure that
+ // the "A" key is delivered to the new pop-up window. This is because users
+ // often anticipate pending UI changes when typing on a keyboard.
+ // To obtain this behavior, we must serialize key events with respect to all
+ // prior input events.
+ return connection->outboundQueue.isEmpty()
+ && connection->waitQueue.isEmpty();
+ }
+ // Touch events can always be sent to a window immediately because the user intended
+ // to touch whatever was visible at the time. Even if focus changes or a new
+ // window appears moments later, the touch event was meant to be delivered to
+ // whatever window happened to be on screen at the time.
+ //
+ // Generic motion events, such as trackball or joystick events are a little trickier.
+ // Like key events, generic motion events are delivered to the focused window.
+ // Unlike key events, generic motion events don't tend to transfer focus to other
+ // windows and it is not important for them to be serialized. So we prefer to deliver
+ // generic motion events as soon as possible to improve efficiency and reduce lag
+ // through batching.
+ //
+ // The one case where we pause input event delivery is when the wait queue is piling
+ // up with lots of events because the application is not responding.
+ // This condition ensures that ANRs are detected reliably.
+ if (!connection->waitQueue.isEmpty()
+ && currentTime >= connection->waitQueue.head->eventEntry->eventTime
+ + STREAM_AHEAD_EVENT_TIMEOUT) {
+ return false;
+ }
+ }
+ return true;
+}
+
+String8 InputDispatcher::getApplicationWindowLabelLocked(
+ const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle) {
+ if (applicationHandle != NULL) {
+ if (windowHandle != NULL) {
+ String8 label(applicationHandle->getName());
+ label.append(" - ");
+ label.append(windowHandle->getName());
+ return label;
+ } else {
+ return applicationHandle->getName();
+ }
+ } else if (windowHandle != NULL) {
+ return windowHandle->getName();
+ } else {
+ return String8("<unknown application or window>");
+ }
+}
+
+void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) {
+ if (mFocusedWindowHandle != NULL) {
+ const InputWindowInfo* info = mFocusedWindowHandle->getInfo();
+ if (info->inputFeatures & InputWindowInfo::INPUT_FEATURE_DISABLE_USER_ACTIVITY) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("Not poking user activity: disabled by window '%s'.", info->name.string());
+#endif
+ return;
+ }
+ }
+
+ int32_t eventType = USER_ACTIVITY_EVENT_OTHER;
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_MOTION: {
+ const MotionEntry* motionEntry = static_cast<const MotionEntry*>(eventEntry);
+ if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) {
+ return;
+ }
+
+ if (MotionEvent::isTouchEvent(motionEntry->source, motionEntry->action)) {
+ eventType = USER_ACTIVITY_EVENT_TOUCH;
+ }
+ break;
+ }
+ case EventEntry::TYPE_KEY: {
+ const KeyEntry* keyEntry = static_cast<const KeyEntry*>(eventEntry);
+ if (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) {
+ return;
+ }
+ eventType = USER_ACTIVITY_EVENT_BUTTON;
+ break;
+ }
+ }
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doPokeUserActivityLockedInterruptible);
+ commandEntry->eventTime = eventEntry->eventTime;
+ commandEntry->userActivityEventType = eventType;
+}
+
+void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, "
+ "xOffset=%f, yOffset=%f, scaleFactor=%f, "
+ "pointerIds=0x%x",
+ connection->getInputChannelName(), inputTarget->flags,
+ inputTarget->xOffset, inputTarget->yOffset,
+ inputTarget->scaleFactor, inputTarget->pointerIds.value);
+#endif
+
+ // Skip this event if the connection status is not normal.
+ // We don't want to enqueue additional outbound events if the connection is broken.
+ if (connection->status != Connection::STATUS_NORMAL) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Dropping event because the channel status is %s",
+ connection->getInputChannelName(), connection->getStatusLabel());
+#endif
+ return;
+ }
+
+ // Split a motion event if needed.
+ if (inputTarget->flags & InputTarget::FLAG_SPLIT) {
+ ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION);
+
+ MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry);
+ if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) {
+ MotionEntry* splitMotionEntry = splitMotionEvent(
+ originalMotionEntry, inputTarget->pointerIds);
+ if (!splitMotionEntry) {
+ return; // split event was dropped
+ }
+#if DEBUG_FOCUS
+ ALOGD("channel '%s' ~ Split motion event.",
+ connection->getInputChannelName());
+ logOutboundMotionDetailsLocked(" ", splitMotionEntry);
+#endif
+ enqueueDispatchEntriesLocked(currentTime, connection,
+ splitMotionEntry, inputTarget);
+ splitMotionEntry->release();
+ return;
+ }
+ }
+
+ // Not splitting. Enqueue dispatch entries for the event as is.
+ enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
+}
+
+void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
+ bool wasEmpty = connection->outboundQueue.isEmpty();
+
+ // Enqueue dispatch entries for the requested modes.
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_IS);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
+
+ // If the outbound queue was previously empty, start the dispatch cycle going.
+ if (wasEmpty && !connection->outboundQueue.isEmpty()) {
+ startDispatchCycleLocked(currentTime, connection);
+ }
+}
+
+void InputDispatcher::enqueueDispatchEntryLocked(
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
+ int32_t dispatchMode) {
+ int32_t inputTargetFlags = inputTarget->flags;
+ if (!(inputTargetFlags & dispatchMode)) {
+ return;
+ }
+ inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
+
+ // This is a new event.
+ // Enqueue a new dispatch entry onto the outbound queue for this connection.
+ DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref
+ inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
+ inputTarget->scaleFactor);
+
+ // Apply target flags and update the connection's input state.
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+ dispatchEntry->resolvedAction = keyEntry->action;
+ dispatchEntry->resolvedFlags = keyEntry->flags;
+
+ if (!connection->inputState.trackKey(keyEntry,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
+ connection->getInputChannelName());
+#endif
+ delete dispatchEntry;
+ return; // skip the inconsistent event
+ }
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+ if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
+ } else {
+ dispatchEntry->resolvedAction = motionEntry->action;
+ }
+ if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
+ && !connection->inputState.isHovering(
+ motionEntry->deviceId, motionEntry->source, motionEntry->displayId)) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover enter event",
+ connection->getInputChannelName());
+#endif
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ }
+
+ dispatchEntry->resolvedFlags = motionEntry->flags;
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+ dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ if (!connection->inputState.trackMotion(motionEntry,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion event",
+ connection->getInputChannelName());
+#endif
+ delete dispatchEntry;
+ return; // skip the inconsistent event
+ }
+ break;
+ }
+ }
+
+ // Remember that we are waiting for this dispatch to complete.
+ if (dispatchEntry->hasForegroundTarget()) {
+ incrementPendingForegroundDispatchesLocked(eventEntry);
+ }
+
+ // Enqueue the dispatch entry.
+ connection->outboundQueue.enqueueAtTail(dispatchEntry);
+ traceOutboundQueueLengthLocked(connection);
+}
+
+void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ startDispatchCycle",
+ connection->getInputChannelName());
+#endif
+
+ while (connection->status == Connection::STATUS_NORMAL
+ && !connection->outboundQueue.isEmpty()) {
+ DispatchEntry* dispatchEntry = connection->outboundQueue.head;
+ dispatchEntry->deliveryTime = currentTime;
+
+ // Publish the event.
+ status_t status;
+ EventEntry* eventEntry = dispatchEntry->eventEntry;
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+
+ // Publish the key event.
+ status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
+ keyEntry->deviceId, keyEntry->source,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+ keyEntry->keyCode, keyEntry->scanCode,
+ keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
+ keyEntry->eventTime);
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+
+ PointerCoords scaledCoords[MAX_POINTERS];
+ const PointerCoords* usingCoords = motionEntry->pointerCoords;
+
+ // Set the X and Y offset depending on the input source.
+ float xOffset, yOffset, scaleFactor;
+ if ((motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
+ && !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
+ scaleFactor = dispatchEntry->scaleFactor;
+ xOffset = dispatchEntry->xOffset * scaleFactor;
+ yOffset = dispatchEntry->yOffset * scaleFactor;
+ if (scaleFactor != 1.0f) {
+ for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+ scaledCoords[i] = motionEntry->pointerCoords[i];
+ scaledCoords[i].scale(scaleFactor);
+ }
+ usingCoords = scaledCoords;
+ }
+ } else {
+ xOffset = 0.0f;
+ yOffset = 0.0f;
+ scaleFactor = 1.0f;
+
+ // We don't want the dispatch target to know.
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) {
+ for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+ scaledCoords[i].clear();
+ }
+ usingCoords = scaledCoords;
+ }
+ }
+
+ // Publish the motion event.
+ status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,
+ motionEntry->deviceId, motionEntry->source,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+ motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState,
+ xOffset, yOffset,
+ motionEntry->xPrecision, motionEntry->yPrecision,
+ motionEntry->downTime, motionEntry->eventTime,
+ motionEntry->pointerCount, motionEntry->pointerProperties,
+ usingCoords);
+ break;
+ }
+
+ default:
+ ALOG_ASSERT(false);
+ return;
+ }
+
+ // Check the result.
+ if (status) {
+ if (status == WOULD_BLOCK) {
+ if (connection->waitQueue.isEmpty()) {
+ ALOGE("channel '%s' ~ Could not publish event because the pipe is full. "
+ "This is unexpected because the wait queue is empty, so the pipe "
+ "should be empty and we shouldn't have any problems writing an "
+ "event to it, status=%d", connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ } else {
+ // Pipe is full and we are waiting for the app to finish process some events
+ // before sending more events to it.
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Could not publish event because the pipe is full, "
+ "waiting for the application to catch up",
+ connection->getInputChannelName());
+#endif
+ connection->inputPublisherBlocked = true;
+ }
+ } else {
+ ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, "
+ "status=%d", connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ }
+ return;
+ }
+
+ // Re-enqueue the event on the wait queue.
+ connection->outboundQueue.dequeue(dispatchEntry);
+ traceOutboundQueueLengthLocked(connection);
+ connection->waitQueue.enqueueAtTail(dispatchEntry);
+ traceWaitQueueLengthLocked(connection);
+ }
+}
+
+void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, uint32_t seq, bool handled) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s",
+ connection->getInputChannelName(), seq, toString(handled));
+#endif
+
+ connection->inputPublisherBlocked = false;
+
+ if (connection->status == Connection::STATUS_BROKEN
+ || connection->status == Connection::STATUS_ZOMBIE) {
+ return;
+ }
+
+ // Notify other system components and prepare to start the next dispatch cycle.
+ onDispatchCycleFinishedLocked(currentTime, connection, seq, handled);
+}
+
+void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, bool notify) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s",
+ connection->getInputChannelName(), toString(notify));
+#endif
+
+ // Clear the dispatch queues.
+ drainDispatchQueueLocked(&connection->outboundQueue);
+ traceOutboundQueueLengthLocked(connection);
+ drainDispatchQueueLocked(&connection->waitQueue);
+ traceWaitQueueLengthLocked(connection);
+
+ // The connection appears to be unrecoverably broken.
+ // Ignore already broken or zombie connections.
+ if (connection->status == Connection::STATUS_NORMAL) {
+ connection->status = Connection::STATUS_BROKEN;
+
+ if (notify) {
+ // Notify other system components.
+ onDispatchCycleBrokenLocked(currentTime, connection);
+ }
+ }
+}
+
+void InputDispatcher::drainDispatchQueueLocked(Queue<DispatchEntry>* queue) {
+ while (!queue->isEmpty()) {
+ DispatchEntry* dispatchEntry = queue->dequeueAtHead();
+ releaseDispatchEntryLocked(dispatchEntry);
+ }
+}
+
+void InputDispatcher::releaseDispatchEntryLocked(DispatchEntry* dispatchEntry) {
+ if (dispatchEntry->hasForegroundTarget()) {
+ decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
+ }
+ delete dispatchEntry;
+}
+
+int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
+ InputDispatcher* d = static_cast<InputDispatcher*>(data);
+
+ { // acquire lock
+ AutoMutex _l(d->mLock);
+
+ ssize_t connectionIndex = d->mConnectionsByFd.indexOfKey(fd);
+ if (connectionIndex < 0) {
+ ALOGE("Received spurious receive callback for unknown input channel. "
+ "fd=%d, events=0x%x", fd, events);
+ return 0; // remove the callback
+ }
+
+ bool notify;
+ sp<Connection> connection = d->mConnectionsByFd.valueAt(connectionIndex);
+ if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
+ if (!(events & ALOOPER_EVENT_INPUT)) {
+ ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ return 1;
+ }
+
+ nsecs_t currentTime = now();
+ bool gotOne = false;
+ status_t status;
+ for (;;) {
+ uint32_t seq;
+ bool handled;
+ status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);
+ if (status) {
+ break;
+ }
+ d->finishDispatchCycleLocked(currentTime, connection, seq, handled);
+ gotOne = true;
+ }
+ if (gotOne) {
+ d->runCommandsLockedInterruptible();
+ if (status == WOULD_BLOCK) {
+ return 1;
+ }
+ }
+
+ notify = status != DEAD_OBJECT || !connection->monitor;
+ if (notify) {
+ ALOGE("channel '%s' ~ Failed to receive finished signal. status=%d",
+ connection->getInputChannelName(), status);
+ }
+ } else {
+ // Monitor channels are never explicitly unregistered.
+ // We do it automatically when the remote endpoint is closed so don't warn
+ // about them.
+ notify = !connection->monitor;
+ if (notify) {
+ ALOGW("channel '%s' ~ Consumer closed input channel or an error occurred. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ }
+ }
+
+ // Unregister the channel.
+ d->unregisterInputChannelLocked(connection->inputChannel, notify);
+ return 0; // remove the callback
+ } // release lock
+}
+
+void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked(
+ const CancelationOptions& options) {
+ for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByFd.valueAt(i), options);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
+ const sp<InputChannel>& channel, const CancelationOptions& options) {
+ ssize_t index = getConnectionIndexLocked(channel);
+ if (index >= 0) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByFd.valueAt(index), options);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
+ const sp<Connection>& connection, const CancelationOptions& options) {
+ if (connection->status == Connection::STATUS_BROKEN) {
+ return;
+ }
+
+ nsecs_t currentTime = now();
+
+ Vector<EventEntry*> cancelationEvents;
+ connection->inputState.synthesizeCancelationEvents(currentTime,
+ cancelationEvents, options);
+
+ if (!cancelationEvents.isEmpty()) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("channel '%s' ~ Synthesized %d cancelation events to bring channel back in sync "
+ "with reality: %s, mode=%d.",
+ connection->getInputChannelName(), cancelationEvents.size(),
+ options.reason, options.mode);
+#endif
+ for (size_t i = 0; i < cancelationEvents.size(); i++) {
+ EventEntry* cancelationEventEntry = cancelationEvents.itemAt(i);
+ switch (cancelationEventEntry->type) {
+ case EventEntry::TYPE_KEY:
+ logOutboundKeyDetailsLocked("cancel - ",
+ static_cast<KeyEntry*>(cancelationEventEntry));
+ break;
+ case EventEntry::TYPE_MOTION:
+ logOutboundMotionDetailsLocked("cancel - ",
+ static_cast<MotionEntry*>(cancelationEventEntry));
+ break;
+ }
+
+ InputTarget target;
+ sp<InputWindowHandle> windowHandle = getWindowHandleLocked(connection->inputChannel);
+ if (windowHandle != NULL) {
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ target.xOffset = -windowInfo->frameLeft;
+ target.yOffset = -windowInfo->frameTop;
+ target.scaleFactor = windowInfo->scaleFactor;
+ } else {
+ target.xOffset = 0;
+ target.yOffset = 0;
+ target.scaleFactor = 1.0f;
+ }
+ target.inputChannel = connection->inputChannel;
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+
+ enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
+ &target, InputTarget::FLAG_DISPATCH_AS_IS);
+
+ cancelationEventEntry->release();
+ }
+
+ startDispatchCycleLocked(currentTime, connection);
+ }
+}
+
+InputDispatcher::MotionEntry*
+InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) {
+ ALOG_ASSERT(pointerIds.value != 0);
+
+ PointerProperties splitPointerProperties[MAX_POINTERS];
+ PointerCoords splitPointerCoords[MAX_POINTERS];
+
+ uint32_t originalPointerCount = originalMotionEntry->pointerCount;
+ uint32_t splitPointerCount = 0;
+
+ for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
+ originalPointerIndex++) {
+ const PointerProperties& pointerProperties =
+ originalMotionEntry->pointerProperties[originalPointerIndex];
+ uint32_t pointerId = uint32_t(pointerProperties.id);
+ if (pointerIds.hasBit(pointerId)) {
+ splitPointerProperties[splitPointerCount].copyFrom(pointerProperties);
+ splitPointerCoords[splitPointerCount].copyFrom(
+ originalMotionEntry->pointerCoords[originalPointerIndex]);
+ splitPointerCount += 1;
+ }
+ }
+
+ if (splitPointerCount != pointerIds.count()) {
+ // This is bad. We are missing some of the pointers that we expected to deliver.
+ // Most likely this indicates that we received an ACTION_MOVE events that has
+ // different pointer ids than we expected based on the previous ACTION_DOWN
+ // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
+ // in this way.
+ ALOGW("Dropping split motion event because the pointer count is %d but "
+ "we expected there to be %d pointers. This probably means we received "
+ "a broken sequence of pointer ids from the input device.",
+ splitPointerCount, pointerIds.count());
+ return NULL;
+ }
+
+ int32_t action = originalMotionEntry->action;
+ int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+ if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+ || maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+ int32_t originalPointerIndex = getMotionEventActionPointerIndex(action);
+ const PointerProperties& pointerProperties =
+ originalMotionEntry->pointerProperties[originalPointerIndex];
+ uint32_t pointerId = uint32_t(pointerProperties.id);
+ if (pointerIds.hasBit(pointerId)) {
+ if (pointerIds.count() == 1) {
+ // The first/last pointer went down/up.
+ action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+ ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+ } else {
+ // A secondary pointer went down/up.
+ uint32_t splitPointerIndex = 0;
+ while (pointerId != uint32_t(splitPointerProperties[splitPointerIndex].id)) {
+ splitPointerIndex += 1;
+ }
+ action = maskedAction | (splitPointerIndex
+ << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ }
+ } else {
+ // An unrelated pointer changed.
+ action = AMOTION_EVENT_ACTION_MOVE;
+ }
+ }
+
+ MotionEntry* splitMotionEntry = new MotionEntry(
+ originalMotionEntry->eventTime,
+ originalMotionEntry->deviceId,
+ originalMotionEntry->source,
+ originalMotionEntry->policyFlags,
+ action,
+ originalMotionEntry->flags,
+ originalMotionEntry->metaState,
+ originalMotionEntry->buttonState,
+ originalMotionEntry->edgeFlags,
+ originalMotionEntry->xPrecision,
+ originalMotionEntry->yPrecision,
+ originalMotionEntry->downTime,
+ originalMotionEntry->displayId,
+ splitPointerCount, splitPointerProperties, splitPointerCoords);
+
+ if (originalMotionEntry->injectionState) {
+ splitMotionEntry->injectionState = originalMotionEntry->injectionState;
+ splitMotionEntry->injectionState->refCount += 1;
+ }
+
+ return splitMotionEntry;
+}
+
+void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyConfigurationChanged - eventTime=%lld", args->eventTime);
+#endif
+
+ bool needWake;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ ConfigurationChangedEntry* newEntry = new ConfigurationChangedEntry(args->eventTime);
+ needWake = enqueueInboundEventLocked(newEntry);
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyKey - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, action=0x%x, "
+ "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
+ args->eventTime, args->deviceId, args->source, args->policyFlags,
+ args->action, args->flags, args->keyCode, args->scanCode,
+ args->metaState, args->downTime);
+#endif
+ if (!validateKeyEvent(args->action)) {
+ return;
+ }
+
+ uint32_t policyFlags = args->policyFlags;
+ int32_t flags = args->flags;
+ int32_t metaState = args->metaState;
+ if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+ flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+ }
+ if (policyFlags & POLICY_FLAG_ALT) {
+ metaState |= AMETA_ALT_ON | AMETA_ALT_LEFT_ON;
+ }
+ if (policyFlags & POLICY_FLAG_ALT_GR) {
+ metaState |= AMETA_ALT_ON | AMETA_ALT_RIGHT_ON;
+ }
+ if (policyFlags & POLICY_FLAG_SHIFT) {
+ metaState |= AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON;
+ }
+ if (policyFlags & POLICY_FLAG_CAPS_LOCK) {
+ metaState |= AMETA_CAPS_LOCK_ON;
+ }
+ if (policyFlags & POLICY_FLAG_FUNCTION) {
+ metaState |= AMETA_FUNCTION_ON;
+ }
+
+ policyFlags |= POLICY_FLAG_TRUSTED;
+
+ KeyEvent event;
+ event.initialize(args->deviceId, args->source, args->action,
+ flags, args->keyCode, args->scanCode, metaState, 0,
+ args->downTime, args->eventTime);
+
+ mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
+
+ if (policyFlags & POLICY_FLAG_WOKE_HERE) {
+ flags |= AKEY_EVENT_FLAG_WOKE_HERE;
+ }
+
+ bool needWake;
+ { // acquire lock
+ mLock.lock();
+
+ if (shouldSendKeyToInputFilterLocked(args)) {
+ mLock.unlock();
+
+ policyFlags |= POLICY_FLAG_FILTERED;
+ if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+ return; // event was consumed by the filter
+ }
+
+ mLock.lock();
+ }
+
+ int32_t repeatCount = 0;
+ KeyEntry* newEntry = new KeyEntry(args->eventTime,
+ args->deviceId, args->source, policyFlags,
+ args->action, flags, args->keyCode, args->scanCode,
+ metaState, repeatCount, args->downTime);
+
+ needWake = enqueueInboundEventLocked(newEntry);
+ mLock.unlock();
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) {
+ return mInputFilterEnabled;
+}
+
+void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyMotion - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, edgeFlags=0x%x, "
+ "xPrecision=%f, yPrecision=%f, downTime=%lld",
+ args->eventTime, args->deviceId, args->source, args->policyFlags,
+ args->action, args->flags, args->metaState, args->buttonState,
+ args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime);
+ for (uint32_t i = 0; i < args->pointerCount; i++) {
+ ALOGD(" Pointer %d: id=%d, toolType=%d, "
+ "x=%f, y=%f, pressure=%f, size=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+ "orientation=%f",
+ i, args->pointerProperties[i].id,
+ args->pointerProperties[i].toolType,
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+ }
+#endif
+ if (!validateMotionEvent(args->action, args->pointerCount, args->pointerProperties)) {
+ return;
+ }
+
+ uint32_t policyFlags = args->policyFlags;
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags);
+
+ bool needWake;
+ { // acquire lock
+ mLock.lock();
+
+ if (shouldSendMotionToInputFilterLocked(args)) {
+ mLock.unlock();
+
+ MotionEvent event;
+ event.initialize(args->deviceId, args->source, args->action, args->flags,
+ args->edgeFlags, args->metaState, args->buttonState, 0, 0,
+ args->xPrecision, args->yPrecision,
+ args->downTime, args->eventTime,
+ args->pointerCount, args->pointerProperties, args->pointerCoords);
+
+ policyFlags |= POLICY_FLAG_FILTERED;
+ if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+ return; // event was consumed by the filter
+ }
+
+ mLock.lock();
+ }
+
+ // Just enqueue a new motion event.
+ MotionEntry* newEntry = new MotionEntry(args->eventTime,
+ args->deviceId, args->source, policyFlags,
+ args->action, args->flags, args->metaState, args->buttonState,
+ args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
+ args->displayId,
+ args->pointerCount, args->pointerProperties, args->pointerCoords);
+
+ needWake = enqueueInboundEventLocked(newEntry);
+ mLock.unlock();
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) {
+ // TODO: support sending secondary display events to input filter
+ return mInputFilterEnabled && isMainDisplay(args->displayId);
+}
+
+void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifySwitch - eventTime=%lld, policyFlags=0x%x, switchValues=0x%08x, switchMask=0x%08x",
+ args->eventTime, args->policyFlags,
+ args->switchValues, args->switchMask);
+#endif
+
+ uint32_t policyFlags = args->policyFlags;
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ mPolicy->notifySwitch(args->eventTime,
+ args->switchValues, args->switchMask, policyFlags);
+}
+
+void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyDeviceReset - eventTime=%lld, deviceId=%d",
+ args->eventTime, args->deviceId);
+#endif
+
+ bool needWake;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ DeviceResetEntry* newEntry = new DeviceResetEntry(args->eventTime, args->deviceId);
+ needWake = enqueueInboundEventLocked(newEntry);
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
+ "syncMode=%d, timeoutMillis=%d, policyFlags=0x%08x",
+ event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis, policyFlags);
+#endif
+
+ nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
+
+ policyFlags |= POLICY_FLAG_INJECTED;
+ if (hasInjectionPermission(injectorPid, injectorUid)) {
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ }
+
+ EventEntry* firstInjectedEntry;
+ EventEntry* lastInjectedEntry;
+ switch (event->getType()) {
+ case AINPUT_EVENT_TYPE_KEY: {
+ const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
+ int32_t action = keyEvent->getAction();
+ if (! validateKeyEvent(action)) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ int32_t flags = keyEvent->getFlags();
+ if (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY) {
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+ }
+
+ if (!(policyFlags & POLICY_FLAG_FILTERED)) {
+ mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
+ }
+
+ if (policyFlags & POLICY_FLAG_WOKE_HERE) {
+ flags |= AKEY_EVENT_FLAG_WOKE_HERE;
+ }
+
+ mLock.lock();
+ firstInjectedEntry = new KeyEntry(keyEvent->getEventTime(),
+ keyEvent->getDeviceId(), keyEvent->getSource(),
+ policyFlags, action, flags,
+ keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
+ keyEvent->getRepeatCount(), keyEvent->getDownTime());
+ lastInjectedEntry = firstInjectedEntry;
+ break;
+ }
+
+ case AINPUT_EVENT_TYPE_MOTION: {
+ const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
+ int32_t displayId = ADISPLAY_ID_DEFAULT;
+ int32_t action = motionEvent->getAction();
+ size_t pointerCount = motionEvent->getPointerCount();
+ const PointerProperties* pointerProperties = motionEvent->getPointerProperties();
+ if (! validateMotionEvent(action, pointerCount, pointerProperties)) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ if (!(policyFlags & POLICY_FLAG_FILTERED)) {
+ nsecs_t eventTime = motionEvent->getEventTime();
+ mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags);
+ }
+
+ mLock.lock();
+ const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
+ const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
+ firstInjectedEntry = new MotionEntry(*sampleEventTimes,
+ motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
+ action, motionEvent->getFlags(),
+ motionEvent->getMetaState(), motionEvent->getButtonState(),
+ motionEvent->getEdgeFlags(),
+ motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+ motionEvent->getDownTime(), displayId,
+ uint32_t(pointerCount), pointerProperties, samplePointerCoords);
+ lastInjectedEntry = firstInjectedEntry;
+ for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
+ sampleEventTimes += 1;
+ samplePointerCoords += pointerCount;
+ MotionEntry* nextInjectedEntry = new MotionEntry(*sampleEventTimes,
+ motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
+ action, motionEvent->getFlags(),
+ motionEvent->getMetaState(), motionEvent->getButtonState(),
+ motionEvent->getEdgeFlags(),
+ motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+ motionEvent->getDownTime(), displayId,
+ uint32_t(pointerCount), pointerProperties, samplePointerCoords);
+ lastInjectedEntry->next = nextInjectedEntry;
+ lastInjectedEntry = nextInjectedEntry;
+ }
+ break;
+ }
+
+ default:
+ ALOGW("Cannot inject event of type %d", event->getType());
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ InjectionState* injectionState = new InjectionState(injectorPid, injectorUid);
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionState->injectionIsAsync = true;
+ }
+
+ injectionState->refCount += 1;
+ lastInjectedEntry->injectionState = injectionState;
+
+ bool needWake = false;
+ for (EventEntry* entry = firstInjectedEntry; entry != NULL; ) {
+ EventEntry* nextEntry = entry->next;
+ needWake |= enqueueInboundEventLocked(entry);
+ entry = nextEntry;
+ }
+
+ mLock.unlock();
+
+ if (needWake) {
+ mLooper->wake();
+ }
+
+ int32_t injectionResult;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ } else {
+ for (;;) {
+ injectionResult = injectionState->injectionResult;
+ if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
+ break;
+ }
+
+ nsecs_t remainingTimeout = endTime - now();
+ if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Timed out waiting for injection result "
+ "to become available.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+ break;
+ }
+
+ mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
+ }
+
+ if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED
+ && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) {
+ while (injectionState->pendingForegroundDispatches != 0) {
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
+ injectionState->pendingForegroundDispatches);
+#endif
+ nsecs_t remainingTimeout = endTime - now();
+ if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Timed out waiting for pending foreground "
+ "dispatches to finish.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+ break;
+ }
+
+ mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout);
+ }
+ }
+ }
+
+ injectionState->release();
+ } // release lock
+
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Finished with result %d. "
+ "injectorPid=%d, injectorUid=%d",
+ injectionResult, injectorPid, injectorUid);
+#endif
+
+ return injectionResult;
+}
+
+bool InputDispatcher::hasInjectionPermission(int32_t injectorPid, int32_t injectorUid) {
+ return injectorUid == 0
+ || mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid);
+}
+
+void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+#if DEBUG_INJECTION
+ ALOGD("Setting input event injection result to %d. "
+ "injectorPid=%d, injectorUid=%d",
+ injectionResult, injectionState->injectorPid, injectionState->injectorUid);
+#endif
+
+ if (injectionState->injectionIsAsync
+ && !(entry->policyFlags & POLICY_FLAG_FILTERED)) {
+ // Log the outcome since the injector did not wait for the injection result.
+ switch (injectionResult) {
+ case INPUT_EVENT_INJECTION_SUCCEEDED:
+ ALOGV("Asynchronous input event injection succeeded.");
+ break;
+ case INPUT_EVENT_INJECTION_FAILED:
+ ALOGW("Asynchronous input event injection failed.");
+ break;
+ case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
+ ALOGW("Asynchronous input event injection permission denied.");
+ break;
+ case INPUT_EVENT_INJECTION_TIMED_OUT:
+ ALOGW("Asynchronous input event injection timed out.");
+ break;
+ }
+ }
+
+ injectionState->injectionResult = injectionResult;
+ mInjectionResultAvailableCondition.broadcast();
+ }
+}
+
+void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+ injectionState->pendingForegroundDispatches += 1;
+ }
+}
+
+void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+ injectionState->pendingForegroundDispatches -= 1;
+
+ if (injectionState->pendingForegroundDispatches == 0) {
+ mInjectionSyncFinishedCondition.broadcast();
+ }
+ }
+}
+
+sp<InputWindowHandle> InputDispatcher::getWindowHandleLocked(
+ const sp<InputChannel>& inputChannel) const {
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ if (windowHandle->getInputChannel() == inputChannel) {
+ return windowHandle;
+ }
+ }
+ return NULL;
+}
+
+bool InputDispatcher::hasWindowHandleLocked(
+ const sp<InputWindowHandle>& windowHandle) const {
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ if (mWindowHandles.itemAt(i) == windowHandle) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
+#if DEBUG_FOCUS
+ ALOGD("setInputWindows");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ Vector<sp<InputWindowHandle> > oldWindowHandles = mWindowHandles;
+ mWindowHandles = inputWindowHandles;
+
+ sp<InputWindowHandle> newFocusedWindowHandle;
+ bool foundHoveredWindow = false;
+ for (size_t i = 0; i < mWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) {
+ mWindowHandles.removeAt(i--);
+ continue;
+ }
+ if (windowHandle->getInfo()->hasFocus) {
+ newFocusedWindowHandle = windowHandle;
+ }
+ if (windowHandle == mLastHoverWindowHandle) {
+ foundHoveredWindow = true;
+ }
+ }
+
+ if (!foundHoveredWindow) {
+ mLastHoverWindowHandle = NULL;
+ }
+
+ if (mFocusedWindowHandle != newFocusedWindowHandle) {
+ if (mFocusedWindowHandle != NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Focus left window: %s",
+ mFocusedWindowHandle->getName().string());
+#endif
+ sp<InputChannel> focusedInputChannel = mFocusedWindowHandle->getInputChannel();
+ if (focusedInputChannel != NULL) {
+ CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
+ "focus left window");
+ synthesizeCancelationEventsForInputChannelLocked(
+ focusedInputChannel, options);
+ }
+ }
+ if (newFocusedWindowHandle != NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Focus entered window: %s",
+ newFocusedWindowHandle->getName().string());
+#endif
+ }
+ mFocusedWindowHandle = newFocusedWindowHandle;
+ }
+
+ for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+ TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i);
+ if (!hasWindowHandleLocked(touchedWindow.windowHandle)) {
+#if DEBUG_FOCUS
+ ALOGD("Touched window was removed: %s",
+ touchedWindow.windowHandle->getName().string());
+#endif
+ sp<InputChannel> touchedInputChannel =
+ touchedWindow.windowHandle->getInputChannel();
+ if (touchedInputChannel != NULL) {
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ "touched window was removed");
+ synthesizeCancelationEventsForInputChannelLocked(
+ touchedInputChannel, options);
+ }
+ mTouchState.windows.removeAt(i--);
+ }
+ }
+
+ // Release information for windows that are no longer present.
+ // This ensures that unused input channels are released promptly.
+ // Otherwise, they might stick around until the window handle is destroyed
+ // which might not happen until the next GC.
+ for (size_t i = 0; i < oldWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& oldWindowHandle = oldWindowHandles.itemAt(i);
+ if (!hasWindowHandleLocked(oldWindowHandle)) {
+#if DEBUG_FOCUS
+ ALOGD("Window went away: %s", oldWindowHandle->getName().string());
+#endif
+ oldWindowHandle->releaseInfo();
+ }
+ }
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+}
+
+void InputDispatcher::setFocusedApplication(
+ const sp<InputApplicationHandle>& inputApplicationHandle) {
+#if DEBUG_FOCUS
+ ALOGD("setFocusedApplication");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (inputApplicationHandle != NULL && inputApplicationHandle->updateInfo()) {
+ if (mFocusedApplicationHandle != inputApplicationHandle) {
+ if (mFocusedApplicationHandle != NULL) {
+ resetANRTimeoutsLocked();
+ mFocusedApplicationHandle->releaseInfo();
+ }
+ mFocusedApplicationHandle = inputApplicationHandle;
+ }
+ } else if (mFocusedApplicationHandle != NULL) {
+ resetANRTimeoutsLocked();
+ mFocusedApplicationHandle->releaseInfo();
+ mFocusedApplicationHandle.clear();
+ }
+
+#if DEBUG_FOCUS
+ //logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+}
+
+void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
+#if DEBUG_FOCUS
+ ALOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
+#endif
+
+ bool changed;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
+ if (mDispatchFrozen && !frozen) {
+ resetANRTimeoutsLocked();
+ }
+
+ if (mDispatchEnabled && !enabled) {
+ resetAndDropEverythingLocked("dispatcher is being disabled");
+ }
+
+ mDispatchEnabled = enabled;
+ mDispatchFrozen = frozen;
+ changed = true;
+ } else {
+ changed = false;
+ }
+
+#if DEBUG_FOCUS
+ //logDispatchStateLocked();
+#endif
+ } // release lock
+
+ if (changed) {
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+ }
+}
+
+void InputDispatcher::setInputFilterEnabled(bool enabled) {
+#if DEBUG_FOCUS
+ ALOGD("setInputFilterEnabled: enabled=%d", enabled);
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mInputFilterEnabled == enabled) {
+ return;
+ }
+
+ mInputFilterEnabled = enabled;
+ resetAndDropEverythingLocked("input filter is being enabled or disabled");
+ } // release lock
+
+ // Wake up poll loop since there might be work to do to drop everything.
+ mLooper->wake();
+}
+
+bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) {
+#if DEBUG_FOCUS
+ ALOGD("transferTouchFocus: fromChannel=%s, toChannel=%s",
+ fromChannel->getName().string(), toChannel->getName().string());
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ sp<InputWindowHandle> fromWindowHandle = getWindowHandleLocked(fromChannel);
+ sp<InputWindowHandle> toWindowHandle = getWindowHandleLocked(toChannel);
+ if (fromWindowHandle == NULL || toWindowHandle == NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Cannot transfer focus because from or to window not found.");
+#endif
+ return false;
+ }
+ if (fromWindowHandle == toWindowHandle) {
+#if DEBUG_FOCUS
+ ALOGD("Trivial transfer to same window.");
+#endif
+ return true;
+ }
+ if (fromWindowHandle->getInfo()->displayId != toWindowHandle->getInfo()->displayId) {
+#if DEBUG_FOCUS
+ ALOGD("Cannot transfer focus because windows are on different displays.");
+#endif
+ return false;
+ }
+
+ bool found = false;
+ for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTouchState.windows[i];
+ if (touchedWindow.windowHandle == fromWindowHandle) {
+ int32_t oldTargetFlags = touchedWindow.targetFlags;
+ BitSet32 pointerIds = touchedWindow.pointerIds;
+
+ mTouchState.windows.removeAt(i);
+
+ int32_t newTargetFlags = oldTargetFlags
+ & (InputTarget::FLAG_FOREGROUND
+ | InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
+ mTouchState.addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds);
+
+ found = true;
+ break;
+ }
+ }
+
+ if (! found) {
+#if DEBUG_FOCUS
+ ALOGD("Focus transfer failed because from window did not have focus.");
+#endif
+ return false;
+ }
+
+ ssize_t fromConnectionIndex = getConnectionIndexLocked(fromChannel);
+ ssize_t toConnectionIndex = getConnectionIndexLocked(toChannel);
+ if (fromConnectionIndex >= 0 && toConnectionIndex >= 0) {
+ sp<Connection> fromConnection = mConnectionsByFd.valueAt(fromConnectionIndex);
+ sp<Connection> toConnection = mConnectionsByFd.valueAt(toConnectionIndex);
+
+ fromConnection->inputState.copyPointerStateTo(toConnection->inputState);
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ "transferring touch focus from this window to another window");
+ synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
+ }
+
+#if DEBUG_FOCUS
+ logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+ return true;
+}
+
+void InputDispatcher::resetAndDropEverythingLocked(const char* reason) {
+#if DEBUG_FOCUS
+ ALOGD("Resetting and dropping all events (%s).", reason);
+#endif
+
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+
+ resetKeyRepeatLocked();
+ releasePendingEventLocked();
+ drainInboundQueueLocked();
+ resetANRTimeoutsLocked();
+
+ mTouchState.reset();
+ mLastHoverWindowHandle.clear();
+}
+
+void InputDispatcher::logDispatchStateLocked() {
+ String8 dump;
+ dumpDispatchStateLocked(dump);
+
+ char* text = dump.lockBuffer(dump.size());
+ char* start = text;
+ while (*start != '\0') {
+ char* end = strchr(start, '\n');
+ if (*end == '\n') {
+ *(end++) = '\0';
+ }
+ ALOGD("%s", start);
+ start = end;
+ }
+}
+
+void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
+ dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled);
+ dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen);
+
+ if (mFocusedApplicationHandle != NULL) {
+ dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
+ mFocusedApplicationHandle->getName().string(),
+ mFocusedApplicationHandle->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT) / 1000000.0);
+ } else {
+ dump.append(INDENT "FocusedApplication: <null>\n");
+ }
+ dump.appendFormat(INDENT "FocusedWindow: name='%s'\n",
+ mFocusedWindowHandle != NULL ? mFocusedWindowHandle->getName().string() : "<null>");
+
+ dump.appendFormat(INDENT "TouchDown: %s\n", toString(mTouchState.down));
+ dump.appendFormat(INDENT "TouchSplit: %s\n", toString(mTouchState.split));
+ dump.appendFormat(INDENT "TouchDeviceId: %d\n", mTouchState.deviceId);
+ dump.appendFormat(INDENT "TouchSource: 0x%08x\n", mTouchState.source);
+ dump.appendFormat(INDENT "TouchDisplayId: %d\n", mTouchState.displayId);
+ if (!mTouchState.windows.isEmpty()) {
+ dump.append(INDENT "TouchedWindows:\n");
+ for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTouchState.windows[i];
+ dump.appendFormat(INDENT2 "%d: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
+ i, touchedWindow.windowHandle->getName().string(),
+ touchedWindow.pointerIds.value,
+ touchedWindow.targetFlags);
+ }
+ } else {
+ dump.append(INDENT "TouchedWindows: <none>\n");
+ }
+
+ if (!mWindowHandles.isEmpty()) {
+ dump.append(INDENT "Windows:\n");
+ for (size_t i = 0; i < mWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+
+ dump.appendFormat(INDENT2 "%d: name='%s', displayId=%d, "
+ "paused=%s, hasFocus=%s, hasWallpaper=%s, "
+ "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
+ "frame=[%d,%d][%d,%d], scale=%f, "
+ "touchableRegion=",
+ i, windowInfo->name.string(), windowInfo->displayId,
+ toString(windowInfo->paused),
+ toString(windowInfo->hasFocus),
+ toString(windowInfo->hasWallpaper),
+ toString(windowInfo->visible),
+ toString(windowInfo->canReceiveKeys),
+ windowInfo->layoutParamsFlags, windowInfo->layoutParamsType,
+ windowInfo->layer,
+ windowInfo->frameLeft, windowInfo->frameTop,
+ windowInfo->frameRight, windowInfo->frameBottom,
+ windowInfo->scaleFactor);
+ dumpRegion(dump, windowInfo->touchableRegion);
+ dump.appendFormat(", inputFeatures=0x%08x", windowInfo->inputFeatures);
+ dump.appendFormat(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+ windowInfo->ownerPid, windowInfo->ownerUid,
+ windowInfo->dispatchingTimeout / 1000000.0);
+ }
+ } else {
+ dump.append(INDENT "Windows: <none>\n");
+ }
+
+ if (!mMonitoringChannels.isEmpty()) {
+ dump.append(INDENT "MonitoringChannels:\n");
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ const sp<InputChannel>& channel = mMonitoringChannels[i];
+ dump.appendFormat(INDENT2 "%d: '%s'\n", i, channel->getName().string());
+ }
+ } else {
+ dump.append(INDENT "MonitoringChannels: <none>\n");
+ }
+
+ nsecs_t currentTime = now();
+
+ if (!mInboundQueue.isEmpty()) {
+ dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count());
+ for (EventEntry* entry = mInboundQueue.head; entry; entry = entry->next) {
+ dump.append(INDENT2);
+ entry->appendDescription(dump);
+ dump.appendFormat(", age=%0.1fms\n",
+ (currentTime - entry->eventTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT "InboundQueue: <empty>\n");
+ }
+
+ if (!mConnectionsByFd.isEmpty()) {
+ dump.append(INDENT "Connections:\n");
+ for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
+ const sp<Connection>& connection = mConnectionsByFd.valueAt(i);
+ dump.appendFormat(INDENT2 "%d: channelName='%s', windowName='%s', "
+ "status=%s, monitor=%s, inputPublisherBlocked=%s\n",
+ i, connection->getInputChannelName(), connection->getWindowName(),
+ connection->getStatusLabel(), toString(connection->monitor),
+ toString(connection->inputPublisherBlocked));
+
+ if (!connection->outboundQueue.isEmpty()) {
+ dump.appendFormat(INDENT3 "OutboundQueue: length=%u\n",
+ connection->outboundQueue.count());
+ for (DispatchEntry* entry = connection->outboundQueue.head; entry;
+ entry = entry->next) {
+ dump.append(INDENT4);
+ entry->eventEntry->appendDescription(dump);
+ dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, age=%0.1fms\n",
+ entry->targetFlags, entry->resolvedAction,
+ (currentTime - entry->eventEntry->eventTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT3 "OutboundQueue: <empty>\n");
+ }
+
+ if (!connection->waitQueue.isEmpty()) {
+ dump.appendFormat(INDENT3 "WaitQueue: length=%u\n",
+ connection->waitQueue.count());
+ for (DispatchEntry* entry = connection->waitQueue.head; entry;
+ entry = entry->next) {
+ dump.append(INDENT4);
+ entry->eventEntry->appendDescription(dump);
+ dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, "
+ "age=%0.1fms, wait=%0.1fms\n",
+ entry->targetFlags, entry->resolvedAction,
+ (currentTime - entry->eventEntry->eventTime) * 0.000001f,
+ (currentTime - entry->deliveryTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT3 "WaitQueue: <empty>\n");
+ }
+ }
+ } else {
+ dump.append(INDENT "Connections: <none>\n");
+ }
+
+ if (isAppSwitchPendingLocked()) {
+ dump.appendFormat(INDENT "AppSwitch: pending, due in %0.1fms\n",
+ (mAppSwitchDueTime - now()) / 1000000.0);
+ } else {
+ dump.append(INDENT "AppSwitch: not pending\n");
+ }
+
+ dump.append(INDENT "Configuration:\n");
+ dump.appendFormat(INDENT2 "KeyRepeatDelay: %0.1fms\n",
+ mConfig.keyRepeatDelay * 0.000001f);
+ dump.appendFormat(INDENT2 "KeyRepeatTimeout: %0.1fms\n",
+ mConfig.keyRepeatTimeout * 0.000001f);
+}
+
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
+#if DEBUG_REGISTRATION
+ ALOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
+ toString(monitor));
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (getConnectionIndexLocked(inputChannel) >= 0) {
+ ALOGW("Attempted to register already registered input channel '%s'",
+ inputChannel->getName().string());
+ return BAD_VALUE;
+ }
+
+ sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
+
+ int fd = inputChannel->getFd();
+ mConnectionsByFd.add(fd, connection);
+
+ if (monitor) {
+ mMonitoringChannels.push(inputChannel);
+ }
+
+ mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
+ } // release lock
+
+ // Wake the looper because some connections have changed.
+ mLooper->wake();
+ return OK;
+}
+
+status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+#if DEBUG_REGISTRATION
+ ALOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string());
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ status_t status = unregisterInputChannelLocked(inputChannel, false /*notify*/);
+ if (status) {
+ return status;
+ }
+ } // release lock
+
+ // Wake the poll loop because removing the connection may have changed the current
+ // synchronization state.
+ mLooper->wake();
+ return OK;
+}
+
+status_t InputDispatcher::unregisterInputChannelLocked(const sp<InputChannel>& inputChannel,
+ bool notify) {
+ ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+ if (connectionIndex < 0) {
+ ALOGW("Attempted to unregister already unregistered input channel '%s'",
+ inputChannel->getName().string());
+ return BAD_VALUE;
+ }
+
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ mConnectionsByFd.removeItemsAt(connectionIndex);
+
+ if (connection->monitor) {
+ removeMonitorChannelLocked(inputChannel);
+ }
+
+ mLooper->removeFd(inputChannel->getFd());
+
+ nsecs_t currentTime = now();
+ abortBrokenDispatchCycleLocked(currentTime, connection, notify);
+
+ connection->status = Connection::STATUS_ZOMBIE;
+ return OK;
+}
+
+void InputDispatcher::removeMonitorChannelLocked(const sp<InputChannel>& inputChannel) {
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ if (mMonitoringChannels[i] == inputChannel) {
+ mMonitoringChannels.removeAt(i);
+ break;
+ }
+ }
+}
+
+ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
+ ssize_t connectionIndex = mConnectionsByFd.indexOfKey(inputChannel->getFd());
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ if (connection->inputChannel.get() == inputChannel.get()) {
+ return connectionIndex;
+ }
+ }
+
+ return -1;
+}
+
+void InputDispatcher::onDispatchCycleFinishedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
+ commandEntry->connection = connection;
+ commandEntry->eventTime = currentTime;
+ commandEntry->seq = seq;
+ commandEntry->handled = handled;
+}
+
+void InputDispatcher::onDispatchCycleBrokenLocked(
+ nsecs_t currentTime, const sp<Connection>& connection) {
+ ALOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
+ connection->getInputChannelName());
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
+ commandEntry->connection = connection;
+}
+
+void InputDispatcher::onANRLocked(
+ nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) {
+ float dispatchLatency = (currentTime - eventTime) * 0.000001f;
+ float waitDuration = (currentTime - waitStartTime) * 0.000001f;
+ ALOGI("Application is not responding: %s. "
+ "It has been %0.1fms since event, %0.1fms since wait started. Reason: %s",
+ getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
+ dispatchLatency, waitDuration, reason);
+
+ // Capture a record of the InputDispatcher state at the time of the ANR.
+ time_t t = time(NULL);
+ struct tm tm;
+ localtime_r(&t, &tm);
+ char timestr[64];
+ strftime(timestr, sizeof(timestr), "%F %T", &tm);
+ mLastANRState.clear();
+ mLastANRState.append(INDENT "ANR:\n");
+ mLastANRState.appendFormat(INDENT2 "Time: %s\n", timestr);
+ mLastANRState.appendFormat(INDENT2 "Window: %s\n",
+ getApplicationWindowLabelLocked(applicationHandle, windowHandle).string());
+ mLastANRState.appendFormat(INDENT2 "DispatchLatency: %0.1fms\n", dispatchLatency);
+ mLastANRState.appendFormat(INDENT2 "WaitDuration: %0.1fms\n", waitDuration);
+ mLastANRState.appendFormat(INDENT2 "Reason: %s\n", reason);
+ dumpDispatchStateLocked(mLastANRState);
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyANRLockedInterruptible);
+ commandEntry->inputApplicationHandle = applicationHandle;
+ commandEntry->inputWindowHandle = windowHandle;
+}
+
+void InputDispatcher::doNotifyConfigurationChangedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
+
+ mLock.lock();
+}
+
+void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
+ CommandEntry* commandEntry) {
+ sp<Connection> connection = commandEntry->connection;
+
+ if (connection->status != Connection::STATUS_ZOMBIE) {
+ mLock.unlock();
+
+ mPolicy->notifyInputChannelBroken(connection->inputWindowHandle);
+
+ mLock.lock();
+ }
+}
+
+void InputDispatcher::doNotifyANRLockedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ nsecs_t newTimeout = mPolicy->notifyANR(
+ commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle);
+
+ mLock.lock();
+
+ resumeAfterTargetsNotReadyTimeoutLocked(newTimeout,
+ commandEntry->inputWindowHandle != NULL
+ ? commandEntry->inputWindowHandle->getInputChannel() : NULL);
+}
+
+void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
+ CommandEntry* commandEntry) {
+ KeyEntry* entry = commandEntry->keyEntry;
+
+ KeyEvent event;
+ initializeKeyEvent(&event, entry);
+
+ mLock.unlock();
+
+ nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
+ &event, entry->policyFlags);
+
+ mLock.lock();
+
+ if (delay < 0) {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
+ } else if (!delay) {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ } else {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
+ entry->interceptKeyWakeupTime = now() + delay;
+ }
+ entry->release();
+}
+
+void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
+ CommandEntry* commandEntry) {
+ sp<Connection> connection = commandEntry->connection;
+ nsecs_t finishTime = commandEntry->eventTime;
+ uint32_t seq = commandEntry->seq;
+ bool handled = commandEntry->handled;
+
+ // Handle post-event policy actions.
+ DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);
+ if (dispatchEntry) {
+ nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
+ if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
+ String8 msg;
+ msg.appendFormat("Window '%s' spent %0.1fms processing the last input event: ",
+ connection->getWindowName(), eventDuration * 0.000001f);
+ dispatchEntry->eventEntry->appendDescription(msg);
+ ALOGI("%s", msg.string());
+ }
+
+ bool restartEvent;
+ if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
+ restartEvent = afterKeyEventLockedInterruptible(connection,
+ dispatchEntry, keyEntry, handled);
+ } else if (dispatchEntry->eventEntry->type == EventEntry::TYPE_MOTION) {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry);
+ restartEvent = afterMotionEventLockedInterruptible(connection,
+ dispatchEntry, motionEntry, handled);
+ } else {
+ restartEvent = false;
+ }
+
+ // Dequeue the event and start the next cycle.
+ // Note that because the lock might have been released, it is possible that the
+ // contents of the wait queue to have been drained, so we need to double-check
+ // a few things.
+ if (dispatchEntry == connection->findWaitQueueEntry(seq)) {
+ connection->waitQueue.dequeue(dispatchEntry);
+ traceWaitQueueLengthLocked(connection);
+ if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
+ connection->outboundQueue.enqueueAtHead(dispatchEntry);
+ traceOutboundQueueLengthLocked(connection);
+ } else {
+ releaseDispatchEntryLocked(dispatchEntry);
+ }
+ }
+
+ // Start the next dispatch cycle for this connection.
+ startDispatchCycleLocked(now(), connection);
+ }
+}
+
+bool InputDispatcher::afterKeyEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, KeyEntry* keyEntry, bool handled) {
+ if (!(keyEntry->flags & AKEY_EVENT_FLAG_FALLBACK)) {
+ // Get the fallback key state.
+ // Clear it out after dispatching the UP.
+ int32_t originalKeyCode = keyEntry->keyCode;
+ int32_t fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode);
+ if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
+ connection->inputState.removeFallbackKey(originalKeyCode);
+ }
+
+ if (handled || !dispatchEntry->hasForegroundTarget()) {
+ // If the application handles the original key for which we previously
+ // generated a fallback or if the window is not a foreground window,
+ // then cancel the associated fallback key, if any.
+ if (fallbackKeyCode != -1) {
+ // Dispatch the unhandled key to the policy with the cancel flag.
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Asking policy to cancel fallback action. "
+ "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+ keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount,
+ keyEntry->policyFlags);
+#endif
+ KeyEvent event;
+ initializeKeyEvent(&event, keyEntry);
+ event.setFlags(event.getFlags() | AKEY_EVENT_FLAG_CANCELED);
+
+ mLock.unlock();
+
+ mPolicy->dispatchUnhandledKey(connection->inputWindowHandle,
+ &event, keyEntry->policyFlags, &event);
+
+ mLock.lock();
+
+ // Cancel the fallback key.
+ if (fallbackKeyCode != AKEYCODE_UNKNOWN) {
+ CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+ "application handled the original non-fallback key "
+ "or is no longer a foreground target, "
+ "canceling previously dispatched fallback key");
+ options.keyCode = fallbackKeyCode;
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
+ }
+ connection->inputState.removeFallbackKey(originalKeyCode);
+ }
+ } else {
+ // If the application did not handle a non-fallback key, first check
+ // that we are in a good state to perform unhandled key event processing
+ // Then ask the policy what to do with it.
+ bool initialDown = keyEntry->action == AKEY_EVENT_ACTION_DOWN
+ && keyEntry->repeatCount == 0;
+ if (fallbackKeyCode == -1 && !initialDown) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Skipping unhandled key event processing "
+ "since this is not an initial down. "
+ "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+ originalKeyCode, keyEntry->action, keyEntry->repeatCount,
+ keyEntry->policyFlags);
+#endif
+ return false;
+ }
+
+ // Dispatch the unhandled key to the policy.
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Asking policy to perform fallback action. "
+ "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+ keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount,
+ keyEntry->policyFlags);
+#endif
+ KeyEvent event;
+ initializeKeyEvent(&event, keyEntry);
+
+ mLock.unlock();
+
+ bool fallback = mPolicy->dispatchUnhandledKey(connection->inputWindowHandle,
+ &event, keyEntry->policyFlags, &event);
+
+ mLock.lock();
+
+ if (connection->status != Connection::STATUS_NORMAL) {
+ connection->inputState.removeFallbackKey(originalKeyCode);
+ return false;
+ }
+
+ // Latch the fallback keycode for this key on an initial down.
+ // The fallback keycode cannot change at any other point in the lifecycle.
+ if (initialDown) {
+ if (fallback) {
+ fallbackKeyCode = event.getKeyCode();
+ } else {
+ fallbackKeyCode = AKEYCODE_UNKNOWN;
+ }
+ connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode);
+ }
+
+ ALOG_ASSERT(fallbackKeyCode != -1);
+
+ // Cancel the fallback key if the policy decides not to send it anymore.
+ // We will continue to dispatch the key to the policy but we will no
+ // longer dispatch a fallback key to the application.
+ if (fallbackKeyCode != AKEYCODE_UNKNOWN
+ && (!fallback || fallbackKeyCode != event.getKeyCode())) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ if (fallback) {
+ ALOGD("Unhandled key event: Policy requested to send key %d"
+ "as a fallback for %d, but on the DOWN it had requested "
+ "to send %d instead. Fallback canceled.",
+ event.getKeyCode(), originalKeyCode, fallbackKeyCode);
+ } else {
+ ALOGD("Unhandled key event: Policy did not request fallback for %d, "
+ "but on the DOWN it had requested to send %d. "
+ "Fallback canceled.",
+ originalKeyCode, fallbackKeyCode);
+ }
+#endif
+
+ CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+ "canceling fallback, policy no longer desires it");
+ options.keyCode = fallbackKeyCode;
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
+
+ fallback = false;
+ fallbackKeyCode = AKEYCODE_UNKNOWN;
+ if (keyEntry->action != AKEY_EVENT_ACTION_UP) {
+ connection->inputState.setFallbackKey(originalKeyCode,
+ fallbackKeyCode);
+ }
+ }
+
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ {
+ String8 msg;
+ const KeyedVector<int32_t, int32_t>& fallbackKeys =
+ connection->inputState.getFallbackKeys();
+ for (size_t i = 0; i < fallbackKeys.size(); i++) {
+ msg.appendFormat(", %d->%d", fallbackKeys.keyAt(i),
+ fallbackKeys.valueAt(i));
+ }
+ ALOGD("Unhandled key event: %d currently tracked fallback keys%s.",
+ fallbackKeys.size(), msg.string());
+ }
+#endif
+
+ if (fallback) {
+ // Restart the dispatch cycle using the fallback key.
+ keyEntry->eventTime = event.getEventTime();
+ keyEntry->deviceId = event.getDeviceId();
+ keyEntry->source = event.getSource();
+ keyEntry->flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK;
+ keyEntry->keyCode = fallbackKeyCode;
+ keyEntry->scanCode = event.getScanCode();
+ keyEntry->metaState = event.getMetaState();
+ keyEntry->repeatCount = event.getRepeatCount();
+ keyEntry->downTime = event.getDownTime();
+ keyEntry->syntheticRepeat = false;
+
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Dispatching fallback key. "
+ "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
+ originalKeyCode, fallbackKeyCode, keyEntry->metaState);
+#endif
+ return true; // restart the event
+ } else {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: No fallback key.");
+#endif
+ }
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::afterMotionEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, MotionEntry* motionEntry, bool handled) {
+ return false;
+}
+
+void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType);
+
+ mLock.lock();
+}
+
+void InputDispatcher::initializeKeyEvent(KeyEvent* event, const KeyEntry* entry) {
+ event->initialize(entry->deviceId, entry->source, entry->action, entry->flags,
+ entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+ entry->downTime, entry->eventTime);
+}
+
+void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+ int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) {
+ // TODO Write some statistics about how long we spend waiting.
+}
+
+void InputDispatcher::traceInboundQueueLengthLocked() {
+#ifdef HAVE_ANDROID_OS
+ if (ATRACE_ENABLED()) {
+ ATRACE_INT("iq", mInboundQueue.count());
+ }
+#endif
+}
+
+void InputDispatcher::traceOutboundQueueLengthLocked(const sp<Connection>& connection) {
+#ifdef HAVE_ANDROID_OS
+ if (ATRACE_ENABLED()) {
+ char counterName[40];
+ snprintf(counterName, sizeof(counterName), "oq:%s", connection->getWindowName());
+ ATRACE_INT(counterName, connection->outboundQueue.count());
+ }
+#endif
+}
+
+void InputDispatcher::traceWaitQueueLengthLocked(const sp<Connection>& connection) {
+#ifdef HAVE_ANDROID_OS
+ if (ATRACE_ENABLED()) {
+ char counterName[40];
+ snprintf(counterName, sizeof(counterName), "wq:%s", connection->getWindowName());
+ ATRACE_INT(counterName, connection->waitQueue.count());
+ }
+#endif
+}
+
+void InputDispatcher::dump(String8& dump) {
+ AutoMutex _l(mLock);
+
+ dump.append("Input Dispatcher State:\n");
+ dumpDispatchStateLocked(dump);
+
+ if (!mLastANRState.isEmpty()) {
+ dump.append("\nInput Dispatcher State at time of last ANR:\n");
+ dump.append(mLastANRState);
+ }
+}
+
+void InputDispatcher::monitor() {
+ // Acquire and release the lock to ensure that the dispatcher has not deadlocked.
+ mLock.lock();
+ mLooper->wake();
+ mDispatcherIsAliveCondition.wait(mLock);
+ mLock.unlock();
+}
+
+
+// --- InputDispatcher::Queue ---
+
+template <typename T>
+uint32_t InputDispatcher::Queue<T>::count() const {
+ uint32_t result = 0;
+ for (const T* entry = head; entry; entry = entry->next) {
+ result += 1;
+ }
+ return result;
+}
+
+
+// --- InputDispatcher::InjectionState ---
+
+InputDispatcher::InjectionState::InjectionState(int32_t injectorPid, int32_t injectorUid) :
+ refCount(1),
+ injectorPid(injectorPid), injectorUid(injectorUid),
+ injectionResult(INPUT_EVENT_INJECTION_PENDING), injectionIsAsync(false),
+ pendingForegroundDispatches(0) {
+}
+
+InputDispatcher::InjectionState::~InjectionState() {
+}
+
+void InputDispatcher::InjectionState::release() {
+ refCount -= 1;
+ if (refCount == 0) {
+ delete this;
+ } else {
+ ALOG_ASSERT(refCount > 0);
+ }
+}
+
+
+// --- InputDispatcher::EventEntry ---
+
+InputDispatcher::EventEntry::EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags) :
+ refCount(1), type(type), eventTime(eventTime), policyFlags(policyFlags),
+ injectionState(NULL), dispatchInProgress(false) {
+}
+
+InputDispatcher::EventEntry::~EventEntry() {
+ releaseInjectionState();
+}
+
+void InputDispatcher::EventEntry::release() {
+ refCount -= 1;
+ if (refCount == 0) {
+ delete this;
+ } else {
+ ALOG_ASSERT(refCount > 0);
+ }
+}
+
+void InputDispatcher::EventEntry::releaseInjectionState() {
+ if (injectionState) {
+ injectionState->release();
+ injectionState = NULL;
+ }
+}
+
+
+// --- InputDispatcher::ConfigurationChangedEntry ---
+
+InputDispatcher::ConfigurationChangedEntry::ConfigurationChangedEntry(nsecs_t eventTime) :
+ EventEntry(TYPE_CONFIGURATION_CHANGED, eventTime, 0) {
+}
+
+InputDispatcher::ConfigurationChangedEntry::~ConfigurationChangedEntry() {
+}
+
+void InputDispatcher::ConfigurationChangedEntry::appendDescription(String8& msg) const {
+ msg.append("ConfigurationChangedEvent()");
+}
+
+
+// --- InputDispatcher::DeviceResetEntry ---
+
+InputDispatcher::DeviceResetEntry::DeviceResetEntry(nsecs_t eventTime, int32_t deviceId) :
+ EventEntry(TYPE_DEVICE_RESET, eventTime, 0),
+ deviceId(deviceId) {
+}
+
+InputDispatcher::DeviceResetEntry::~DeviceResetEntry() {
+}
+
+void InputDispatcher::DeviceResetEntry::appendDescription(String8& msg) const {
+ msg.appendFormat("DeviceResetEvent(deviceId=%d)", deviceId);
+}
+
+
+// --- InputDispatcher::KeyEntry ---
+
+InputDispatcher::KeyEntry::KeyEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+ int32_t repeatCount, nsecs_t downTime) :
+ EventEntry(TYPE_KEY, eventTime, policyFlags),
+ deviceId(deviceId), source(source), action(action), flags(flags),
+ keyCode(keyCode), scanCode(scanCode), metaState(metaState),
+ repeatCount(repeatCount), downTime(downTime),
+ syntheticRepeat(false), interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN),
+ interceptKeyWakeupTime(0) {
+}
+
+InputDispatcher::KeyEntry::~KeyEntry() {
+}
+
+void InputDispatcher::KeyEntry::appendDescription(String8& msg) const {
+ msg.appendFormat("KeyEvent(action=%d, deviceId=%d, source=0x%08x)",
+ action, deviceId, source);
+}
+
+void InputDispatcher::KeyEntry::recycle() {
+ releaseInjectionState();
+
+ dispatchInProgress = false;
+ syntheticRepeat = false;
+ interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+ interceptKeyWakeupTime = 0;
+}
+
+
+// --- InputDispatcher::MotionEntry ---
+
+InputDispatcher::MotionEntry::MotionEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags, float xPrecision, float yPrecision,
+ nsecs_t downTime, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) :
+ EventEntry(TYPE_MOTION, eventTime, policyFlags),
+ eventTime(eventTime),
+ deviceId(deviceId), source(source), action(action), flags(flags),
+ metaState(metaState), buttonState(buttonState), edgeFlags(edgeFlags),
+ xPrecision(xPrecision), yPrecision(yPrecision),
+ downTime(downTime), displayId(displayId), pointerCount(pointerCount) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ this->pointerProperties[i].copyFrom(pointerProperties[i]);
+ this->pointerCoords[i].copyFrom(pointerCoords[i]);
+ }
+}
+
+InputDispatcher::MotionEntry::~MotionEntry() {
+}
+
+void InputDispatcher::MotionEntry::appendDescription(String8& msg) const {
+ msg.appendFormat("MotionEvent(action=%d, deviceId=%d, source=0x%08x, displayId=%d)",
+ action, deviceId, source, displayId);
+}
+
+
+// --- InputDispatcher::DispatchEntry ---
+
+volatile int32_t InputDispatcher::DispatchEntry::sNextSeqAtomic;
+
+InputDispatcher::DispatchEntry::DispatchEntry(EventEntry* eventEntry,
+ int32_t targetFlags, float xOffset, float yOffset, float scaleFactor) :
+ seq(nextSeq()),
+ eventEntry(eventEntry), targetFlags(targetFlags),
+ xOffset(xOffset), yOffset(yOffset), scaleFactor(scaleFactor),
+ deliveryTime(0), resolvedAction(0), resolvedFlags(0) {
+ eventEntry->refCount += 1;
+}
+
+InputDispatcher::DispatchEntry::~DispatchEntry() {
+ eventEntry->release();
+}
+
+uint32_t InputDispatcher::DispatchEntry::nextSeq() {
+ // Sequence number 0 is reserved and will never be returned.
+ uint32_t seq;
+ do {
+ seq = android_atomic_inc(&sNextSeqAtomic);
+ } while (!seq);
+ return seq;
+}
+
+
+// --- InputDispatcher::InputState ---
+
+InputDispatcher::InputState::InputState() {
+}
+
+InputDispatcher::InputState::~InputState() {
+}
+
+bool InputDispatcher::InputState::isNeutral() const {
+ return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
+}
+
+bool InputDispatcher::InputState::isHovering(int32_t deviceId, uint32_t source,
+ int32_t displayId) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.deviceId == deviceId
+ && memento.source == source
+ && memento.displayId == displayId
+ && memento.hovering) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::InputState::trackKey(const KeyEntry* entry,
+ int32_t action, int32_t flags) {
+ switch (action) {
+ case AKEY_EVENT_ACTION_UP: {
+ if (entry->flags & AKEY_EVENT_FLAG_FALLBACK) {
+ for (size_t i = 0; i < mFallbackKeys.size(); ) {
+ if (mFallbackKeys.valueAt(i) == entry->keyCode) {
+ mFallbackKeys.removeItemsAt(i);
+ } else {
+ i += 1;
+ }
+ }
+ }
+ ssize_t index = findKeyMemento(entry);
+ if (index >= 0) {
+ mKeyMementos.removeAt(index);
+ return true;
+ }
+ /* FIXME: We can't just drop the key up event because that prevents creating
+ * popup windows that are automatically shown when a key is held and then
+ * dismissed when the key is released. The problem is that the popup will
+ * not have received the original key down, so the key up will be considered
+ * to be inconsistent with its observed state. We could perhaps handle this
+ * by synthesizing a key down but that will cause other problems.
+ *
+ * So for now, allow inconsistent key up events to be dispatched.
+ *
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, "
+ "keyCode=%d, scanCode=%d",
+ entry->deviceId, entry->source, entry->keyCode, entry->scanCode);
+#endif
+ return false;
+ */
+ return true;
+ }
+
+ case AKEY_EVENT_ACTION_DOWN: {
+ ssize_t index = findKeyMemento(entry);
+ if (index >= 0) {
+ mKeyMementos.removeAt(index);
+ }
+ addKeyMemento(entry, flags);
+ return true;
+ }
+
+ default:
+ return true;
+ }
+}
+
+bool InputDispatcher::InputState::trackMotion(const MotionEntry* entry,
+ int32_t action, int32_t flags) {
+ int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+ switch (actionMasked) {
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent motion up or cancel event: deviceId=%d, source=%08x, "
+ "actionMasked=%d",
+ entry->deviceId, entry->source, actionMasked);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_DOWN: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ }
+ addMotionMemento(entry, flags, false /*hovering*/);
+ return true;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_MOVE: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ MotionMemento& memento = mMotionMementos.editItemAt(index);
+ memento.setPointers(entry);
+ return true;
+ }
+ if (actionMasked == AMOTION_EVENT_ACTION_MOVE
+ && (entry->source & (AINPUT_SOURCE_CLASS_JOYSTICK
+ | AINPUT_SOURCE_CLASS_NAVIGATION))) {
+ // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP.
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent motion pointer up/down or move event: "
+ "deviceId=%d, source=%08x, actionMasked=%d",
+ entry->deviceId, entry->source, actionMasked);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+ ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent motion hover exit event: deviceId=%d, source=%08x",
+ entry->deviceId, entry->source);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE: {
+ ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ }
+ addMotionMemento(entry, flags, true /*hovering*/);
+ return true;
+ }
+
+ default:
+ return true;
+ }
+}
+
+ssize_t InputDispatcher::InputState::findKeyMemento(const KeyEntry* entry) const {
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
+ const KeyMemento& memento = mKeyMementos.itemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source
+ && memento.keyCode == entry->keyCode
+ && memento.scanCode == entry->scanCode) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+ssize_t InputDispatcher::InputState::findMotionMemento(const MotionEntry* entry,
+ bool hovering) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source
+ && memento.displayId == entry->displayId
+ && memento.hovering == hovering) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t flags) {
+ mKeyMementos.push();
+ KeyMemento& memento = mKeyMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.keyCode = entry->keyCode;
+ memento.scanCode = entry->scanCode;
+ memento.metaState = entry->metaState;
+ memento.flags = flags;
+ memento.downTime = entry->downTime;
+ memento.policyFlags = entry->policyFlags;
+}
+
+void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry,
+ int32_t flags, bool hovering) {
+ mMotionMementos.push();
+ MotionMemento& memento = mMotionMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.flags = flags;
+ memento.xPrecision = entry->xPrecision;
+ memento.yPrecision = entry->yPrecision;
+ memento.downTime = entry->downTime;
+ memento.displayId = entry->displayId;
+ memento.setPointers(entry);
+ memento.hovering = hovering;
+ memento.policyFlags = entry->policyFlags;
+}
+
+void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
+ pointerCount = entry->pointerCount;
+ for (uint32_t i = 0; i < entry->pointerCount; i++) {
+ pointerProperties[i].copyFrom(entry->pointerProperties[i]);
+ pointerCoords[i].copyFrom(entry->pointerCoords[i]);
+ }
+}
+
+void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime,
+ Vector<EventEntry*>& outEvents, const CancelationOptions& options) {
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
+ const KeyMemento& memento = mKeyMementos.itemAt(i);
+ if (shouldCancelKey(memento, options)) {
+ outEvents.push(new KeyEntry(currentTime,
+ memento.deviceId, memento.source, memento.policyFlags,
+ AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
+ memento.keyCode, memento.scanCode, memento.metaState, 0, memento.downTime));
+ }
+ }
+
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (shouldCancelMotion(memento, options)) {
+ outEvents.push(new MotionEntry(currentTime,
+ memento.deviceId, memento.source, memento.policyFlags,
+ memento.hovering
+ ? AMOTION_EVENT_ACTION_HOVER_EXIT
+ : AMOTION_EVENT_ACTION_CANCEL,
+ memento.flags, 0, 0, 0,
+ memento.xPrecision, memento.yPrecision, memento.downTime,
+ memento.displayId,
+ memento.pointerCount, memento.pointerProperties, memento.pointerCoords));
+ }
+ }
+}
+
+void InputDispatcher::InputState::clear() {
+ mKeyMementos.clear();
+ mMotionMementos.clear();
+ mFallbackKeys.clear();
+}
+
+void InputDispatcher::InputState::copyPointerStateTo(InputState& other) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
+ for (size_t j = 0; j < other.mMotionMementos.size(); ) {
+ const MotionMemento& otherMemento = other.mMotionMementos.itemAt(j);
+ if (memento.deviceId == otherMemento.deviceId
+ && memento.source == otherMemento.source
+ && memento.displayId == otherMemento.displayId) {
+ other.mMotionMementos.removeAt(j);
+ } else {
+ j += 1;
+ }
+ }
+ other.mMotionMementos.push(memento);
+ }
+ }
+}
+
+int32_t InputDispatcher::InputState::getFallbackKey(int32_t originalKeyCode) {
+ ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
+ return index >= 0 ? mFallbackKeys.valueAt(index) : -1;
+}
+
+void InputDispatcher::InputState::setFallbackKey(int32_t originalKeyCode,
+ int32_t fallbackKeyCode) {
+ ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
+ if (index >= 0) {
+ mFallbackKeys.replaceValueAt(index, fallbackKeyCode);
+ } else {
+ mFallbackKeys.add(originalKeyCode, fallbackKeyCode);
+ }
+}
+
+void InputDispatcher::InputState::removeFallbackKey(int32_t originalKeyCode) {
+ mFallbackKeys.removeItem(originalKeyCode);
+}
+
+bool InputDispatcher::InputState::shouldCancelKey(const KeyMemento& memento,
+ const CancelationOptions& options) {
+ if (options.keyCode != -1 && memento.keyCode != options.keyCode) {
+ return false;
+ }
+
+ if (options.deviceId != -1 && memento.deviceId != options.deviceId) {
+ return false;
+ }
+
+ switch (options.mode) {
+ case CancelationOptions::CANCEL_ALL_EVENTS:
+ case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+ return true;
+ case CancelationOptions::CANCEL_FALLBACK_EVENTS:
+ return memento.flags & AKEY_EVENT_FLAG_FALLBACK;
+ default:
+ return false;
+ }
+}
+
+bool InputDispatcher::InputState::shouldCancelMotion(const MotionMemento& memento,
+ const CancelationOptions& options) {
+ if (options.deviceId != -1 && memento.deviceId != options.deviceId) {
+ return false;
+ }
+
+ switch (options.mode) {
+ case CancelationOptions::CANCEL_ALL_EVENTS:
+ return true;
+ case CancelationOptions::CANCEL_POINTER_EVENTS:
+ return memento.source & AINPUT_SOURCE_CLASS_POINTER;
+ case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+ return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
+ default:
+ return false;
+ }
+}
+
+
+// --- InputDispatcher::Connection ---
+
+InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) :
+ status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle),
+ monitor(monitor),
+ inputPublisher(inputChannel), inputPublisherBlocked(false) {
+}
+
+InputDispatcher::Connection::~Connection() {
+}
+
+const char* InputDispatcher::Connection::getWindowName() const {
+ if (inputWindowHandle != NULL) {
+ return inputWindowHandle->getName().string();
+ }
+ if (monitor) {
+ return "monitor";
+ }
+ return "?";
+}
+
+const char* InputDispatcher::Connection::getStatusLabel() const {
+ switch (status) {
+ case STATUS_NORMAL:
+ return "NORMAL";
+
+ case STATUS_BROKEN:
+ return "BROKEN";
+
+ case STATUS_ZOMBIE:
+ return "ZOMBIE";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+InputDispatcher::DispatchEntry* InputDispatcher::Connection::findWaitQueueEntry(uint32_t seq) {
+ for (DispatchEntry* entry = waitQueue.head; entry != NULL; entry = entry->next) {
+ if (entry->seq == seq) {
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+
+// --- InputDispatcher::CommandEntry ---
+
+InputDispatcher::CommandEntry::CommandEntry(Command command) :
+ command(command), eventTime(0), keyEntry(NULL), userActivityEventType(0),
+ seq(0), handled(false) {
+}
+
+InputDispatcher::CommandEntry::~CommandEntry() {
+}
+
+
+// --- InputDispatcher::TouchState ---
+
+InputDispatcher::TouchState::TouchState() :
+ down(false), split(false), deviceId(-1), source(0), displayId(-1) {
+}
+
+InputDispatcher::TouchState::~TouchState() {
+}
+
+void InputDispatcher::TouchState::reset() {
+ down = false;
+ split = false;
+ deviceId = -1;
+ source = 0;
+ displayId = -1;
+ windows.clear();
+}
+
+void InputDispatcher::TouchState::copyFrom(const TouchState& other) {
+ down = other.down;
+ split = other.split;
+ deviceId = other.deviceId;
+ source = other.source;
+ displayId = other.displayId;
+ windows = other.windows;
+}
+
+void InputDispatcher::TouchState::addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds) {
+ if (targetFlags & InputTarget::FLAG_SPLIT) {
+ split = true;
+ }
+
+ for (size_t i = 0; i < windows.size(); i++) {
+ TouchedWindow& touchedWindow = windows.editItemAt(i);
+ if (touchedWindow.windowHandle == windowHandle) {
+ touchedWindow.targetFlags |= targetFlags;
+ if (targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+ touchedWindow.targetFlags &= ~InputTarget::FLAG_DISPATCH_AS_IS;
+ }
+ touchedWindow.pointerIds.value |= pointerIds.value;
+ return;
+ }
+ }
+
+ windows.push();
+
+ TouchedWindow& touchedWindow = windows.editTop();
+ touchedWindow.windowHandle = windowHandle;
+ touchedWindow.targetFlags = targetFlags;
+ touchedWindow.pointerIds = pointerIds;
+}
+
+void InputDispatcher::TouchState::removeWindow(const sp<InputWindowHandle>& windowHandle) {
+ for (size_t i = 0; i < windows.size(); i++) {
+ if (windows.itemAt(i).windowHandle == windowHandle) {
+ windows.removeAt(i);
+ return;
+ }
+ }
+}
+
+void InputDispatcher::TouchState::filterNonAsIsTouchWindows() {
+ for (size_t i = 0 ; i < windows.size(); ) {
+ TouchedWindow& window = windows.editItemAt(i);
+ if (window.targetFlags & (InputTarget::FLAG_DISPATCH_AS_IS
+ | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER)) {
+ window.targetFlags &= ~InputTarget::FLAG_DISPATCH_MASK;
+ window.targetFlags |= InputTarget::FLAG_DISPATCH_AS_IS;
+ i += 1;
+ } else {
+ windows.removeAt(i);
+ }
+ }
+}
+
+sp<InputWindowHandle> InputDispatcher::TouchState::getFirstForegroundWindowHandle() const {
+ for (size_t i = 0; i < windows.size(); i++) {
+ const TouchedWindow& window = windows.itemAt(i);
+ if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ return window.windowHandle;
+ }
+ }
+ return NULL;
+}
+
+bool InputDispatcher::TouchState::isSlippery() const {
+ // Must have exactly one foreground window.
+ bool haveSlipperyForegroundWindow = false;
+ for (size_t i = 0; i < windows.size(); i++) {
+ const TouchedWindow& window = windows.itemAt(i);
+ if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ if (haveSlipperyForegroundWindow
+ || !(window.windowHandle->getInfo()->layoutParamsFlags
+ & InputWindowInfo::FLAG_SLIPPERY)) {
+ return false;
+ }
+ haveSlipperyForegroundWindow = true;
+ }
+ }
+ return haveSlipperyForegroundWindow;
+}
+
+
+// --- InputDispatcherThread ---
+
+InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
+ Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
+}
+
+InputDispatcherThread::~InputDispatcherThread() {
+}
+
+bool InputDispatcherThread::threadLoop() {
+ mDispatcher->dispatchOnce();
+ return true;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputDispatcher.h b/widget/gonk/libui/InputDispatcher.h
new file mode 100644
index 000000000..5453421f6
--- /dev/null
+++ b/widget/gonk/libui/InputDispatcher.h
@@ -0,0 +1,1117 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_DISPATCHER_H
+#define _UI_INPUT_DISPATCHER_H
+
+#include "Input.h"
+#include "InputTransport.h"
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Looper.h>
+#include <utils/BitSet.h>
+#include <cutils/atomic.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "InputWindow.h"
+#include "InputApplication.h"
+#include "InputListener.h"
+
+
+namespace android {
+
+/*
+ * Constants used to report the outcome of input event injection.
+ */
+enum {
+ /* (INTERNAL USE ONLY) Specifies that injection is pending and its outcome is unknown. */
+ INPUT_EVENT_INJECTION_PENDING = -1,
+
+ /* Injection succeeded. */
+ INPUT_EVENT_INJECTION_SUCCEEDED = 0,
+
+ /* Injection failed because the injector did not have permission to inject
+ * into the application with input focus. */
+ INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1,
+
+ /* Injection failed because there were no available input targets. */
+ INPUT_EVENT_INJECTION_FAILED = 2,
+
+ /* Injection failed due to a timeout. */
+ INPUT_EVENT_INJECTION_TIMED_OUT = 3
+};
+
+/*
+ * Constants used to determine the input event injection synchronization mode.
+ */
+enum {
+ /* Injection is asynchronous and is assumed always to be successful. */
+ INPUT_EVENT_INJECTION_SYNC_NONE = 0,
+
+ /* Waits for previous events to be dispatched so that the input dispatcher can determine
+ * whether input event injection willbe permitted based on the current input focus.
+ * Does not wait for the input event to finish processing. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1,
+
+ /* Waits for the input event to be completely processed. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED = 2,
+};
+
+
+/*
+ * An input target specifies how an input event is to be dispatched to a particular window
+ * including the window's input channel, control flags, a timeout, and an X / Y offset to
+ * be added to input event coordinates to compensate for the absolute position of the
+ * window area.
+ */
+struct InputTarget {
+ enum {
+ /* This flag indicates that the event is being delivered to a foreground application. */
+ FLAG_FOREGROUND = 1 << 0,
+
+ /* This flag indicates that the target of a MotionEvent is partly or wholly
+ * obscured by another visible window above it. The motion event should be
+ * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+ FLAG_WINDOW_IS_OBSCURED = 1 << 1,
+
+ /* This flag indicates that a motion event is being split across multiple windows. */
+ FLAG_SPLIT = 1 << 2,
+
+ /* This flag indicates that the pointer coordinates dispatched to the application
+ * will be zeroed out to avoid revealing information to an application. This is
+ * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
+ * the same UID from watching all touches. */
+ FLAG_ZERO_COORDS = 1 << 3,
+
+ /* This flag indicates that the event should be sent as is.
+ * Should always be set unless the event is to be transmuted. */
+ FLAG_DISPATCH_AS_IS = 1 << 8,
+
+ /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
+ * of the area of this target and so should instead be delivered as an
+ * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
+ FLAG_DISPATCH_AS_OUTSIDE = 1 << 9,
+
+ /* This flag indicates that a hover sequence is starting in the given window.
+ * The event is transmuted into ACTION_HOVER_ENTER. */
+ FLAG_DISPATCH_AS_HOVER_ENTER = 1 << 10,
+
+ /* This flag indicates that a hover event happened outside of a window which handled
+ * previous hover events, signifying the end of the current hover sequence for that
+ * window.
+ * The event is transmuted into ACTION_HOVER_ENTER. */
+ FLAG_DISPATCH_AS_HOVER_EXIT = 1 << 11,
+
+ /* This flag indicates that the event should be canceled.
+ * It is used to transmute ACTION_MOVE into ACTION_CANCEL when a touch slips
+ * outside of a window. */
+ FLAG_DISPATCH_AS_SLIPPERY_EXIT = 1 << 12,
+
+ /* This flag indicates that the event should be dispatched as an initial down.
+ * It is used to transmute ACTION_MOVE into ACTION_DOWN when a touch slips
+ * into a new window. */
+ FLAG_DISPATCH_AS_SLIPPERY_ENTER = 1 << 13,
+
+ /* Mask for all dispatch modes. */
+ FLAG_DISPATCH_MASK = FLAG_DISPATCH_AS_IS
+ | FLAG_DISPATCH_AS_OUTSIDE
+ | FLAG_DISPATCH_AS_HOVER_ENTER
+ | FLAG_DISPATCH_AS_HOVER_EXIT
+ | FLAG_DISPATCH_AS_SLIPPERY_EXIT
+ | FLAG_DISPATCH_AS_SLIPPERY_ENTER,
+ };
+
+ // The input channel to be targeted.
+ sp<InputChannel> inputChannel;
+
+ // Flags for the input target.
+ int32_t flags;
+
+ // The x and y offset to add to a MotionEvent as it is delivered.
+ // (ignored for KeyEvents)
+ float xOffset, yOffset;
+
+ // Scaling factor to apply to MotionEvent as it is delivered.
+ // (ignored for KeyEvents)
+ float scaleFactor;
+
+ // The subset of pointer ids to include in motion events dispatched to this input target
+ // if FLAG_SPLIT is set.
+ BitSet32 pointerIds;
+};
+
+
+/*
+ * Input dispatcher configuration.
+ *
+ * Specifies various options that modify the behavior of the input dispatcher.
+ * The values provided here are merely defaults. The actual values will come from ViewConfiguration
+ * and are passed into the dispatcher during initialization.
+ */
+struct InputDispatcherConfiguration {
+ // The key repeat initial timeout.
+ nsecs_t keyRepeatTimeout;
+
+ // The key repeat inter-key delay.
+ nsecs_t keyRepeatDelay;
+
+ InputDispatcherConfiguration() :
+ keyRepeatTimeout(500 * 1000000LL),
+ keyRepeatDelay(50 * 1000000LL) { }
+};
+
+
+/*
+ * Input dispatcher policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class InputDispatcherPolicyInterface : public virtual RefBase {
+protected:
+ InputDispatcherPolicyInterface() { }
+ virtual ~InputDispatcherPolicyInterface() { }
+
+public:
+ /* Notifies the system that a configuration change has occurred. */
+ virtual void notifyConfigurationChanged(nsecs_t when) = 0;
+
+ /* Notifies the system that an application is not responding.
+ * Returns a new timeout to continue waiting, or 0 to abort dispatch. */
+ virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+ const sp<InputWindowHandle>& inputWindowHandle) = 0;
+
+ /* Notifies the system that an input channel is unrecoverably broken. */
+ virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) = 0;
+
+ /* Gets the input dispatcher configuration. */
+ virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) = 0;
+
+ /* Returns true if automatic key repeating is enabled. */
+ virtual bool isKeyRepeatEnabled() = 0;
+
+ /* Filters an input event.
+ * Return true to dispatch the event unmodified, false to consume the event.
+ * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED
+ * to injectInputEvent.
+ */
+ virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0;
+
+ /* Intercepts a key event immediately before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0;
+
+ /* Intercepts a touch, trackball or other motion event before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0;
+
+ /* Allows the policy a chance to intercept a key before dispatching. */
+ virtual nsecs_t interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+
+ /* Allows the policy a chance to perform default processing for an unhandled key.
+ * Returns an alternate keycode to redispatch as a fallback, or 0 to give up. */
+ virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0;
+
+ /* Notifies the policy about switch events.
+ */
+ virtual void notifySwitch(nsecs_t when,
+ uint32_t switchValues, uint32_t switchMask, uint32_t policyFlags) = 0;
+
+ /* Poke user activity for an event dispatched to a window. */
+ virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0;
+
+ /* Checks whether a given application pid/uid has permission to inject input events
+ * into other applications.
+ *
+ * This method is special in that its implementation promises to be non-reentrant and
+ * is safe to call while holding other locks. (Most other methods make no such guarantees!)
+ */
+ virtual bool checkInjectEventsPermissionNonReentrant(
+ int32_t injectorPid, int32_t injectorUid) = 0;
+};
+
+
+/* Notifies the system about input events generated by the input reader.
+ * The dispatcher is expected to be mostly asynchronous. */
+class InputDispatcherInterface : public virtual RefBase, public InputListenerInterface {
+protected:
+ InputDispatcherInterface() { }
+ virtual ~InputDispatcherInterface() { }
+
+public:
+ /* Dumps the state of the input dispatcher.
+ *
+ * This method may be called on any thread (usually by the input manager). */
+ virtual void dump(String8& dump) = 0;
+
+ /* Called by the heatbeat to ensures that the dispatcher has not deadlocked. */
+ virtual void monitor() = 0;
+
+ /* Runs a single iteration of the dispatch loop.
+ * Nominally processes one queued event, a timeout, or a response from an input consumer.
+ *
+ * This method should only be called on the input dispatcher thread.
+ */
+ virtual void dispatchOnce() = 0;
+
+ /* Injects an input event and optionally waits for sync.
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
+ * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual int32_t injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags) = 0;
+
+ /* Sets the list of input windows.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) = 0;
+
+ /* Sets the focused application.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setFocusedApplication(
+ const sp<InputApplicationHandle>& inputApplicationHandle) = 0;
+
+ /* Sets the input dispatching mode.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setInputDispatchMode(bool enabled, bool frozen) = 0;
+
+ /* Sets whether input event filtering is enabled.
+ * When enabled, incoming input events are sent to the policy's filterInputEvent
+ * method instead of being dispatched. The filter is expected to use
+ * injectInputEvent to inject the events it would like to have dispatched.
+ * It should include POLICY_FLAG_FILTERED in the policy flags during injection.
+ */
+ virtual void setInputFilterEnabled(bool enabled) = 0;
+
+ /* Transfers touch focus from the window associated with one channel to the
+ * window associated with the other channel.
+ *
+ * Returns true on success. False if the window did not actually have touch focus.
+ */
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) = 0;
+
+ /* Registers or unregister input channels that may be used as targets for input events.
+ * If monitor is true, the channel will receive a copy of all input events.
+ *
+ * These methods may be called on any thread (usually by the input manager).
+ */
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) = 0;
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
+};
+
+/* Dispatches events to input targets. Some functions of the input dispatcher, such as
+ * identifying input targets, are controlled by a separate policy object.
+ *
+ * IMPORTANT INVARIANT:
+ * Because the policy can potentially block or cause re-entrance into the input dispatcher,
+ * the input dispatcher never calls into the policy while holding its internal locks.
+ * The implementation is also carefully designed to recover from scenarios such as an
+ * input channel becoming unregistered while identifying input targets or processing timeouts.
+ *
+ * Methods marked 'Locked' must be called with the lock acquired.
+ *
+ * Methods marked 'LockedInterruptible' must be called with the lock acquired but
+ * may during the course of their execution release the lock, call into the policy, and
+ * then reacquire the lock. The caller is responsible for recovering gracefully.
+ *
+ * A 'LockedInterruptible' method may called a 'Locked' method, but NOT vice-versa.
+ */
+class InputDispatcher : public InputDispatcherInterface {
+protected:
+ virtual ~InputDispatcher();
+
+public:
+ explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
+
+ virtual void dump(String8& dump);
+ virtual void monitor();
+
+ virtual void dispatchOnce();
+
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
+ virtual void notifyKey(const NotifyKeyArgs* args);
+ virtual void notifyMotion(const NotifyMotionArgs* args);
+ virtual void notifySwitch(const NotifySwitchArgs* args);
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
+
+ virtual int32_t injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags);
+
+ virtual void setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles);
+ virtual void setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle);
+ virtual void setInputDispatchMode(bool enabled, bool frozen);
+ virtual void setInputFilterEnabled(bool enabled);
+
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel);
+
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
+
+private:
+ template <typename T>
+ struct Link {
+ T* next;
+ T* prev;
+
+ protected:
+ inline Link() : next(NULL), prev(NULL) { }
+ };
+
+ struct InjectionState {
+ mutable int32_t refCount;
+
+ int32_t injectorPid;
+ int32_t injectorUid;
+ int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING
+ bool injectionIsAsync; // set to true if injection is not waiting for the result
+ int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
+
+ InjectionState(int32_t injectorPid, int32_t injectorUid);
+ void release();
+
+ private:
+ ~InjectionState();
+ };
+
+ struct EventEntry : Link<EventEntry> {
+ enum {
+ TYPE_CONFIGURATION_CHANGED,
+ TYPE_DEVICE_RESET,
+ TYPE_KEY,
+ TYPE_MOTION
+ };
+
+ mutable int32_t refCount;
+ int32_t type;
+ nsecs_t eventTime;
+ uint32_t policyFlags;
+ InjectionState* injectionState;
+
+ bool dispatchInProgress; // initially false, set to true while dispatching
+
+ inline bool isInjected() const { return injectionState != NULL; }
+
+ void release();
+
+ virtual void appendDescription(String8& msg) const = 0;
+
+ protected:
+ EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags);
+ virtual ~EventEntry();
+ void releaseInjectionState();
+ };
+
+ struct ConfigurationChangedEntry : EventEntry {
+ ConfigurationChangedEntry(nsecs_t eventTime);
+ virtual void appendDescription(String8& msg) const;
+
+ protected:
+ virtual ~ConfigurationChangedEntry();
+ };
+
+ struct DeviceResetEntry : EventEntry {
+ int32_t deviceId;
+
+ DeviceResetEntry(nsecs_t eventTime, int32_t deviceId);
+ virtual void appendDescription(String8& msg) const;
+
+ protected:
+ virtual ~DeviceResetEntry();
+ };
+
+ struct KeyEntry : EventEntry {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ int32_t repeatCount;
+ nsecs_t downTime;
+
+ bool syntheticRepeat; // set to true for synthetic key repeats
+
+ enum InterceptKeyResult {
+ INTERCEPT_KEY_RESULT_UNKNOWN,
+ INTERCEPT_KEY_RESULT_SKIP,
+ INTERCEPT_KEY_RESULT_CONTINUE,
+ INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,
+ };
+ InterceptKeyResult interceptKeyResult; // set based on the interception result
+ nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
+
+ KeyEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+ int32_t repeatCount, nsecs_t downTime);
+ virtual void appendDescription(String8& msg) const;
+ void recycle();
+
+ protected:
+ virtual ~KeyEntry();
+ };
+
+ struct MotionEntry : EventEntry {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t buttonState;
+ int32_t edgeFlags;
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+ int32_t displayId;
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+
+ MotionEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags,
+ int32_t action, int32_t flags,
+ int32_t metaState, int32_t buttonState, int32_t edgeFlags,
+ float xPrecision, float yPrecision,
+ nsecs_t downTime, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
+ virtual void appendDescription(String8& msg) const;
+
+ protected:
+ virtual ~MotionEntry();
+ };
+
+ // Tracks the progress of dispatching a particular event to a particular connection.
+ struct DispatchEntry : Link<DispatchEntry> {
+ const uint32_t seq; // unique sequence number, never 0
+
+ EventEntry* eventEntry; // the event to dispatch
+ int32_t targetFlags;
+ float xOffset;
+ float yOffset;
+ float scaleFactor;
+ nsecs_t deliveryTime; // time when the event was actually delivered
+
+ // Set to the resolved action and flags when the event is enqueued.
+ int32_t resolvedAction;
+ int32_t resolvedFlags;
+
+ DispatchEntry(EventEntry* eventEntry,
+ int32_t targetFlags, float xOffset, float yOffset, float scaleFactor);
+ ~DispatchEntry();
+
+ inline bool hasForegroundTarget() const {
+ return targetFlags & InputTarget::FLAG_FOREGROUND;
+ }
+
+ inline bool isSplit() const {
+ return targetFlags & InputTarget::FLAG_SPLIT;
+ }
+
+ private:
+ static volatile int32_t sNextSeqAtomic;
+
+ static uint32_t nextSeq();
+ };
+
+ // A command entry captures state and behavior for an action to be performed in the
+ // dispatch loop after the initial processing has taken place. It is essentially
+ // a kind of continuation used to postpone sensitive policy interactions to a point
+ // in the dispatch loop where it is safe to release the lock (generally after finishing
+ // the critical parts of the dispatch cycle).
+ //
+ // The special thing about commands is that they can voluntarily release and reacquire
+ // the dispatcher lock at will. Initially when the command starts running, the
+ // dispatcher lock is held. However, if the command needs to call into the policy to
+ // do some work, it can release the lock, do the work, then reacquire the lock again
+ // before returning.
+ //
+ // This mechanism is a bit clunky but it helps to preserve the invariant that the dispatch
+ // never calls into the policy while holding its lock.
+ //
+ // Commands are implicitly 'LockedInterruptible'.
+ struct CommandEntry;
+ typedef void (InputDispatcher::*Command)(CommandEntry* commandEntry);
+
+ class Connection;
+ struct CommandEntry : Link<CommandEntry> {
+ CommandEntry(Command command);
+ ~CommandEntry();
+
+ Command command;
+
+ // parameters for the command (usage varies by command)
+ sp<Connection> connection;
+ nsecs_t eventTime;
+ KeyEntry* keyEntry;
+ sp<InputApplicationHandle> inputApplicationHandle;
+ sp<InputWindowHandle> inputWindowHandle;
+ int32_t userActivityEventType;
+ uint32_t seq;
+ bool handled;
+ };
+
+ // Generic queue implementation.
+ template <typename T>
+ struct Queue {
+ T* head;
+ T* tail;
+
+ inline Queue() : head(NULL), tail(NULL) {
+ }
+
+ inline bool isEmpty() const {
+ return !head;
+ }
+
+ inline void enqueueAtTail(T* entry) {
+ entry->prev = tail;
+ if (tail) {
+ tail->next = entry;
+ } else {
+ head = entry;
+ }
+ entry->next = NULL;
+ tail = entry;
+ }
+
+ inline void enqueueAtHead(T* entry) {
+ entry->next = head;
+ if (head) {
+ head->prev = entry;
+ } else {
+ tail = entry;
+ }
+ entry->prev = NULL;
+ head = entry;
+ }
+
+ inline void dequeue(T* entry) {
+ if (entry->prev) {
+ entry->prev->next = entry->next;
+ } else {
+ head = entry->next;
+ }
+ if (entry->next) {
+ entry->next->prev = entry->prev;
+ } else {
+ tail = entry->prev;
+ }
+ }
+
+ inline T* dequeueAtHead() {
+ T* entry = head;
+ head = entry->next;
+ if (head) {
+ head->prev = NULL;
+ } else {
+ tail = NULL;
+ }
+ return entry;
+ }
+
+ uint32_t count() const;
+ };
+
+ /* Specifies which events are to be canceled and why. */
+ struct CancelationOptions {
+ enum Mode {
+ CANCEL_ALL_EVENTS = 0,
+ CANCEL_POINTER_EVENTS = 1,
+ CANCEL_NON_POINTER_EVENTS = 2,
+ CANCEL_FALLBACK_EVENTS = 3,
+ };
+
+ // The criterion to use to determine which events should be canceled.
+ Mode mode;
+
+ // Descriptive reason for the cancelation.
+ const char* reason;
+
+ // The specific keycode of the key event to cancel, or -1 to cancel any key event.
+ int32_t keyCode;
+
+ // The specific device id of events to cancel, or -1 to cancel events from any device.
+ int32_t deviceId;
+
+ CancelationOptions(Mode mode, const char* reason) :
+ mode(mode), reason(reason), keyCode(-1), deviceId(-1) { }
+ };
+
+ /* Tracks dispatched key and motion event state so that cancelation events can be
+ * synthesized when events are dropped. */
+ class InputState {
+ public:
+ InputState();
+ ~InputState();
+
+ // Returns true if there is no state to be canceled.
+ bool isNeutral() const;
+
+ // Returns true if the specified source is known to have received a hover enter
+ // motion event.
+ bool isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const;
+
+ // Records tracking information for a key event that has just been published.
+ // Returns true if the event should be delivered, false if it is inconsistent
+ // and should be skipped.
+ bool trackKey(const KeyEntry* entry, int32_t action, int32_t flags);
+
+ // Records tracking information for a motion event that has just been published.
+ // Returns true if the event should be delivered, false if it is inconsistent
+ // and should be skipped.
+ bool trackMotion(const MotionEntry* entry, int32_t action, int32_t flags);
+
+ // Synthesizes cancelation events for the current state and resets the tracked state.
+ void synthesizeCancelationEvents(nsecs_t currentTime,
+ Vector<EventEntry*>& outEvents, const CancelationOptions& options);
+
+ // Clears the current state.
+ void clear();
+
+ // Copies pointer-related parts of the input state to another instance.
+ void copyPointerStateTo(InputState& other) const;
+
+ // Gets the fallback key associated with a keycode.
+ // Returns -1 if none.
+ // Returns AKEYCODE_UNKNOWN if we are only dispatching the unhandled key to the policy.
+ int32_t getFallbackKey(int32_t originalKeyCode);
+
+ // Sets the fallback key for a particular keycode.
+ void setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode);
+
+ // Removes the fallback key for a particular keycode.
+ void removeFallbackKey(int32_t originalKeyCode);
+
+ inline const KeyedVector<int32_t, int32_t>& getFallbackKeys() const {
+ return mFallbackKeys;
+ }
+
+ private:
+ struct KeyMemento {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ int32_t flags;
+ nsecs_t downTime;
+ uint32_t policyFlags;
+ };
+
+ struct MotionMemento {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t flags;
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+ int32_t displayId;
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ bool hovering;
+ uint32_t policyFlags;
+
+ void setPointers(const MotionEntry* entry);
+ };
+
+ Vector<KeyMemento> mKeyMementos;
+ Vector<MotionMemento> mMotionMementos;
+ KeyedVector<int32_t, int32_t> mFallbackKeys;
+
+ ssize_t findKeyMemento(const KeyEntry* entry) const;
+ ssize_t findMotionMemento(const MotionEntry* entry, bool hovering) const;
+
+ void addKeyMemento(const KeyEntry* entry, int32_t flags);
+ void addMotionMemento(const MotionEntry* entry, int32_t flags, bool hovering);
+
+ static bool shouldCancelKey(const KeyMemento& memento,
+ const CancelationOptions& options);
+ static bool shouldCancelMotion(const MotionMemento& memento,
+ const CancelationOptions& options);
+ };
+
+ /* Manages the dispatch state associated with a single input channel. */
+ class Connection : public RefBase {
+ protected:
+ virtual ~Connection();
+
+ public:
+ enum Status {
+ // Everything is peachy.
+ STATUS_NORMAL,
+ // An unrecoverable communication error has occurred.
+ STATUS_BROKEN,
+ // The input channel has been unregistered.
+ STATUS_ZOMBIE
+ };
+
+ Status status;
+ sp<InputChannel> inputChannel; // never null
+ sp<InputWindowHandle> inputWindowHandle; // may be null
+ bool monitor;
+ InputPublisher inputPublisher;
+ InputState inputState;
+
+ // True if the socket is full and no further events can be published until
+ // the application consumes some of the input.
+ bool inputPublisherBlocked;
+
+ // Queue of events that need to be published to the connection.
+ Queue<DispatchEntry> outboundQueue;
+
+ // Queue of events that have been published to the connection but that have not
+ // yet received a "finished" response from the application.
+ Queue<DispatchEntry> waitQueue;
+
+ explicit Connection(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+
+ inline const char* getInputChannelName() const { return inputChannel->getName().string(); }
+
+ const char* getWindowName() const;
+ const char* getStatusLabel() const;
+
+ DispatchEntry* findWaitQueueEntry(uint32_t seq);
+ };
+
+ enum DropReason {
+ DROP_REASON_NOT_DROPPED = 0,
+ DROP_REASON_POLICY = 1,
+ DROP_REASON_APP_SWITCH = 2,
+ DROP_REASON_DISABLED = 3,
+ DROP_REASON_BLOCKED = 4,
+ DROP_REASON_STALE = 5,
+ };
+
+ sp<InputDispatcherPolicyInterface> mPolicy;
+ InputDispatcherConfiguration mConfig;
+
+ Mutex mLock;
+
+ Condition mDispatcherIsAliveCondition;
+
+ sp<Looper> mLooper;
+
+ EventEntry* mPendingEvent;
+ Queue<EventEntry> mInboundQueue;
+ Queue<CommandEntry> mCommandQueue;
+
+ void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime);
+
+ // Enqueues an inbound event. Returns true if mLooper->wake() should be called.
+ bool enqueueInboundEventLocked(EventEntry* entry);
+
+ // Cleans up input state when dropping an inbound event.
+ void dropInboundEventLocked(EventEntry* entry, DropReason dropReason);
+
+ // App switch latency optimization.
+ bool mAppSwitchSawKeyDown;
+ nsecs_t mAppSwitchDueTime;
+
+ static bool isAppSwitchKeyCode(int32_t keyCode);
+ bool isAppSwitchKeyEventLocked(KeyEntry* keyEntry);
+ bool isAppSwitchPendingLocked();
+ void resetPendingAppSwitchLocked(bool handled);
+
+ // Stale event latency optimization.
+ static bool isStaleEventLocked(nsecs_t currentTime, EventEntry* entry);
+
+ // Blocked event latency optimization. Drops old events when the user intends
+ // to transfer focus to a new application.
+ EventEntry* mNextUnblockedEvent;
+
+ sp<InputWindowHandle> findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y);
+
+ // All registered connections mapped by channel file descriptor.
+ KeyedVector<int, sp<Connection> > mConnectionsByFd;
+
+ ssize_t getConnectionIndexLocked(const sp<InputChannel>& inputChannel);
+
+ // Input channels that will receive a copy of all input events.
+ Vector<sp<InputChannel> > mMonitoringChannels;
+
+ // Event injection and synchronization.
+ Condition mInjectionResultAvailableCondition;
+ bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid);
+ void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
+
+ Condition mInjectionSyncFinishedCondition;
+ void incrementPendingForegroundDispatchesLocked(EventEntry* entry);
+ void decrementPendingForegroundDispatchesLocked(EventEntry* entry);
+
+ // Key repeat tracking.
+ struct KeyRepeatState {
+ KeyEntry* lastKeyEntry; // or null if no repeat
+ nsecs_t nextRepeatTime;
+ } mKeyRepeatState;
+
+ void resetKeyRepeatLocked();
+ KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime);
+
+ // Deferred command processing.
+ bool haveCommandsLocked() const;
+ bool runCommandsLockedInterruptible();
+ CommandEntry* postCommandLocked(Command command);
+
+ // Input filter processing.
+ bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args);
+ bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args);
+
+ // Inbound event processing.
+ void drainInboundQueueLocked();
+ void releasePendingEventLocked();
+ void releaseInboundEventLocked(EventEntry* entry);
+
+ // Dispatch state.
+ bool mDispatchEnabled;
+ bool mDispatchFrozen;
+ bool mInputFilterEnabled;
+
+ Vector<sp<InputWindowHandle> > mWindowHandles;
+
+ sp<InputWindowHandle> getWindowHandleLocked(const sp<InputChannel>& inputChannel) const;
+ bool hasWindowHandleLocked(const sp<InputWindowHandle>& windowHandle) const;
+
+ // Focus tracking for keys, trackball, etc.
+ sp<InputWindowHandle> mFocusedWindowHandle;
+
+ // Focus tracking for touch.
+ struct TouchedWindow {
+ sp<InputWindowHandle> windowHandle;
+ int32_t targetFlags;
+ BitSet32 pointerIds; // zero unless target flag FLAG_SPLIT is set
+ };
+ struct TouchState {
+ bool down;
+ bool split;
+ int32_t deviceId; // id of the device that is currently down, others are rejected
+ uint32_t source; // source of the device that is current down, others are rejected
+ int32_t displayId; // id to the display that currently has a touch, others are rejected
+ Vector<TouchedWindow> windows;
+
+ TouchState();
+ ~TouchState();
+ void reset();
+ void copyFrom(const TouchState& other);
+ void addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds);
+ void removeWindow(const sp<InputWindowHandle>& windowHandle);
+ void filterNonAsIsTouchWindows();
+ sp<InputWindowHandle> getFirstForegroundWindowHandle() const;
+ bool isSlippery() const;
+ };
+
+ TouchState mTouchState;
+ TouchState mTempTouchState;
+
+ // Focused application.
+ sp<InputApplicationHandle> mFocusedApplicationHandle;
+
+ // Dispatcher state at time of last ANR.
+ String8 mLastANRState;
+
+ // Dispatch inbound events.
+ bool dispatchConfigurationChangedLocked(
+ nsecs_t currentTime, ConfigurationChangedEntry* entry);
+ bool dispatchDeviceResetLocked(
+ nsecs_t currentTime, DeviceResetEntry* entry);
+ bool dispatchKeyLocked(
+ nsecs_t currentTime, KeyEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime);
+ bool dispatchMotionLocked(
+ nsecs_t currentTime, MotionEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime);
+ void dispatchEventLocked(nsecs_t currentTime, EventEntry* entry,
+ const Vector<InputTarget>& inputTargets);
+
+ void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry);
+ void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry);
+
+ // Keeping track of ANR timeouts.
+ enum InputTargetWaitCause {
+ INPUT_TARGET_WAIT_CAUSE_NONE,
+ INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY,
+ INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,
+ };
+
+ InputTargetWaitCause mInputTargetWaitCause;
+ nsecs_t mInputTargetWaitStartTime;
+ nsecs_t mInputTargetWaitTimeoutTime;
+ bool mInputTargetWaitTimeoutExpired;
+ sp<InputApplicationHandle> mInputTargetWaitApplicationHandle;
+
+ // Contains the last window which received a hover event.
+ sp<InputWindowHandle> mLastHoverWindowHandle;
+
+ // Finding targets for input events.
+ int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry,
+ const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t* nextWakeupTime, const char* reason);
+ void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+ const sp<InputChannel>& inputChannel);
+ nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime);
+ void resetANRTimeoutsLocked();
+
+ int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry,
+ Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime);
+ int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry,
+ Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
+ bool* outConflictingPointerActions);
+
+ void addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets);
+ void addMonitoringTargetsLocked(Vector<InputTarget>& inputTargets);
+
+ void pokeUserActivityLocked(const EventEntry* eventEntry);
+ bool checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
+ const InjectionState* injectionState);
+ bool isWindowObscuredAtPointLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t x, int32_t y) const;
+ bool isWindowReadyForMoreInputLocked(nsecs_t currentTime,
+ const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry);
+ String8 getApplicationWindowLabelLocked(const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle);
+
+ // Manage the dispatch cycle for a single connection.
+ // These methods are deliberately not Interruptible because doing all of the work
+ // with the mutex held makes it easier to ensure that connection invariants are maintained.
+ // If needed, the methods post commands to run later once the critical bits are done.
+ void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget);
+ void enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget);
+ void enqueueDispatchEntryLocked(const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget, int32_t dispatchMode);
+ void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+ void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ uint32_t seq, bool handled);
+ void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ bool notify);
+ void drainDispatchQueueLocked(Queue<DispatchEntry>* queue);
+ void releaseDispatchEntryLocked(DispatchEntry* dispatchEntry);
+ static int handleReceiveCallback(int fd, int events, void* data);
+
+ void synthesizeCancelationEventsForAllConnectionsLocked(
+ const CancelationOptions& options);
+ void synthesizeCancelationEventsForInputChannelLocked(const sp<InputChannel>& channel,
+ const CancelationOptions& options);
+ void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection,
+ const CancelationOptions& options);
+
+ // Splitting motion events across windows.
+ MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds);
+
+ // Reset and drop everything the dispatcher is doing.
+ void resetAndDropEverythingLocked(const char* reason);
+
+ // Dump state.
+ void dumpDispatchStateLocked(String8& dump);
+ void logDispatchStateLocked();
+
+ // Registration.
+ void removeMonitorChannelLocked(const sp<InputChannel>& inputChannel);
+ status_t unregisterInputChannelLocked(const sp<InputChannel>& inputChannel, bool notify);
+
+ // Add or remove a connection to the mActiveConnections vector.
+ void activateConnectionLocked(Connection* connection);
+ void deactivateConnectionLocked(Connection* connection);
+
+ // Interesting events that we might like to log or tell the framework about.
+ void onDispatchCycleFinishedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled);
+ void onDispatchCycleBrokenLocked(
+ nsecs_t currentTime, const sp<Connection>& connection);
+ void onANRLocked(
+ nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t eventTime, nsecs_t waitStartTime, const char* reason);
+
+ // Outbound policy interactions.
+ void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry);
+ void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
+ void doNotifyANRLockedInterruptible(CommandEntry* commandEntry);
+ void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry);
+ void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry);
+ bool afterKeyEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, KeyEntry* keyEntry, bool handled);
+ bool afterMotionEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, MotionEntry* motionEntry, bool handled);
+ void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry);
+ void initializeKeyEvent(KeyEvent* event, const KeyEntry* entry);
+
+ // Statistics gathering.
+ void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+ int32_t injectionResult, nsecs_t timeSpentWaitingForApplication);
+ void traceInboundQueueLengthLocked();
+ void traceOutboundQueueLengthLocked(const sp<Connection>& connection);
+ void traceWaitQueueLengthLocked(const sp<Connection>& connection);
+};
+
+/* Enqueues and dispatches input events, endlessly. */
+class InputDispatcherThread : public Thread {
+public:
+ explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher);
+ ~InputDispatcherThread();
+
+private:
+ virtual bool threadLoop();
+
+ sp<InputDispatcherInterface> mDispatcher;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_DISPATCHER_H
diff --git a/widget/gonk/libui/InputListener.cpp b/widget/gonk/libui/InputListener.cpp
new file mode 100644
index 000000000..3b673f0ad
--- /dev/null
+++ b/widget/gonk/libui/InputListener.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputListener"
+
+//#define LOG_NDEBUG 0
+
+#include "InputListener.h"
+
+#include "cutils_log.h"
+
+namespace android {
+
+// --- NotifyConfigurationChangedArgs ---
+
+NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(nsecs_t eventTime) :
+ eventTime(eventTime) {
+}
+
+NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(
+ const NotifyConfigurationChangedArgs& other) :
+ eventTime(other.eventTime) {
+}
+
+void NotifyConfigurationChangedArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyConfigurationChanged(this);
+}
+
+
+// --- NotifyKeyArgs ---
+
+NotifyKeyArgs::NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, nsecs_t downTime) :
+ eventTime(eventTime), deviceId(deviceId), source(source), policyFlags(policyFlags),
+ action(action), flags(flags), keyCode(keyCode), scanCode(scanCode),
+ metaState(metaState), downTime(downTime) {
+}
+
+NotifyKeyArgs::NotifyKeyArgs(const NotifyKeyArgs& other) :
+ eventTime(other.eventTime), deviceId(other.deviceId), source(other.source),
+ policyFlags(other.policyFlags),
+ action(other.action), flags(other.flags),
+ keyCode(other.keyCode), scanCode(other.scanCode),
+ metaState(other.metaState), downTime(other.downTime) {
+}
+
+void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyKey(this);
+}
+
+
+// --- NotifyMotionArgs ---
+
+NotifyMotionArgs::NotifyMotionArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime) :
+ eventTime(eventTime), deviceId(deviceId), source(source), policyFlags(policyFlags),
+ action(action), flags(flags), metaState(metaState), buttonState(buttonState),
+ edgeFlags(edgeFlags), displayId(displayId), pointerCount(pointerCount),
+ xPrecision(xPrecision), yPrecision(yPrecision), downTime(downTime) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ this->pointerProperties[i].copyFrom(pointerProperties[i]);
+ this->pointerCoords[i].copyFrom(pointerCoords[i]);
+ }
+}
+
+NotifyMotionArgs::NotifyMotionArgs(const NotifyMotionArgs& other) :
+ eventTime(other.eventTime), deviceId(other.deviceId), source(other.source),
+ policyFlags(other.policyFlags),
+ action(other.action), flags(other.flags),
+ metaState(other.metaState), buttonState(other.buttonState),
+ edgeFlags(other.edgeFlags), displayId(other.displayId),
+ pointerCount(other.pointerCount),
+ xPrecision(other.xPrecision), yPrecision(other.yPrecision), downTime(other.downTime) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].copyFrom(other.pointerProperties[i]);
+ pointerCoords[i].copyFrom(other.pointerCoords[i]);
+ }
+}
+
+void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyMotion(this);
+}
+
+
+// --- NotifySwitchArgs ---
+
+NotifySwitchArgs::NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags,
+ uint32_t switchValues, uint32_t switchMask) :
+ eventTime(eventTime), policyFlags(policyFlags),
+ switchValues(switchValues), switchMask(switchMask) {
+}
+
+NotifySwitchArgs::NotifySwitchArgs(const NotifySwitchArgs& other) :
+ eventTime(other.eventTime), policyFlags(other.policyFlags),
+ switchValues(other.switchValues), switchMask(other.switchMask) {
+}
+
+void NotifySwitchArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifySwitch(this);
+}
+
+
+// --- NotifyDeviceResetArgs ---
+
+NotifyDeviceResetArgs::NotifyDeviceResetArgs(nsecs_t eventTime, int32_t deviceId) :
+ eventTime(eventTime), deviceId(deviceId) {
+}
+
+NotifyDeviceResetArgs::NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) :
+ eventTime(other.eventTime), deviceId(other.deviceId) {
+}
+
+void NotifyDeviceResetArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyDeviceReset(this);
+}
+
+
+// --- QueuedInputListener ---
+
+QueuedInputListener::QueuedInputListener(const sp<InputListenerInterface>& innerListener) :
+ mInnerListener(innerListener) {
+}
+
+QueuedInputListener::~QueuedInputListener() {
+ size_t count = mArgsQueue.size();
+ for (size_t i = 0; i < count; i++) {
+ delete mArgsQueue[i];
+ }
+}
+
+void QueuedInputListener::notifyConfigurationChanged(
+ const NotifyConfigurationChangedArgs* args) {
+ mArgsQueue.push(new NotifyConfigurationChangedArgs(*args));
+}
+
+void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
+ mArgsQueue.push(new NotifyKeyArgs(*args));
+}
+
+void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
+ mArgsQueue.push(new NotifyMotionArgs(*args));
+}
+
+void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) {
+ mArgsQueue.push(new NotifySwitchArgs(*args));
+}
+
+void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+ mArgsQueue.push(new NotifyDeviceResetArgs(*args));
+}
+
+void QueuedInputListener::flush() {
+ size_t count = mArgsQueue.size();
+ for (size_t i = 0; i < count; i++) {
+ NotifyArgs* args = mArgsQueue[i];
+ args->notify(mInnerListener);
+ delete args;
+ }
+ mArgsQueue.clear();
+}
+
+
+} // namespace android
diff --git a/widget/gonk/libui/InputListener.h b/widget/gonk/libui/InputListener.h
new file mode 100644
index 000000000..de799322f
--- /dev/null
+++ b/widget/gonk/libui/InputListener.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_LISTENER_H
+#define _UI_INPUT_LISTENER_H
+
+#include "Input.h"
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+class InputListenerInterface;
+
+
+/* Superclass of all input event argument objects */
+struct NotifyArgs {
+ virtual ~NotifyArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const = 0;
+};
+
+
+/* Describes a configuration change event. */
+struct NotifyConfigurationChangedArgs : public NotifyArgs {
+ nsecs_t eventTime;
+
+ inline NotifyConfigurationChangedArgs() { }
+
+ NotifyConfigurationChangedArgs(nsecs_t eventTime);
+
+ NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other);
+
+ virtual ~NotifyConfigurationChangedArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a key event. */
+struct NotifyKeyArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ uint32_t policyFlags;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ nsecs_t downTime;
+
+ inline NotifyKeyArgs() { }
+
+ NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, nsecs_t downTime);
+
+ NotifyKeyArgs(const NotifyKeyArgs& other);
+
+ virtual ~NotifyKeyArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a motion event. */
+struct NotifyMotionArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ uint32_t policyFlags;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t buttonState;
+ int32_t edgeFlags;
+ int32_t displayId;
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+
+ inline NotifyMotionArgs() { }
+
+ NotifyMotionArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime);
+
+ NotifyMotionArgs(const NotifyMotionArgs& other);
+
+ virtual ~NotifyMotionArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a switch event. */
+struct NotifySwitchArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ uint32_t policyFlags;
+ uint32_t switchValues;
+ uint32_t switchMask;
+
+ inline NotifySwitchArgs() { }
+
+ NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags,
+ uint32_t switchValues, uint32_t switchMask);
+
+ NotifySwitchArgs(const NotifySwitchArgs& other);
+
+ virtual ~NotifySwitchArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a device reset event, such as when a device is added,
+ * reconfigured, or removed. */
+struct NotifyDeviceResetArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+
+ inline NotifyDeviceResetArgs() { }
+
+ NotifyDeviceResetArgs(nsecs_t eventTime, int32_t deviceId);
+
+ NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other);
+
+ virtual ~NotifyDeviceResetArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/*
+ * The interface used by the InputReader to notify the InputListener about input events.
+ */
+class InputListenerInterface : public virtual RefBase {
+protected:
+ InputListenerInterface() { }
+ virtual ~InputListenerInterface() { }
+
+public:
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0;
+ virtual void notifyKey(const NotifyKeyArgs* args) = 0;
+ virtual void notifyMotion(const NotifyMotionArgs* args) = 0;
+ virtual void notifySwitch(const NotifySwitchArgs* args) = 0;
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0;
+};
+
+
+/*
+ * An implementation of the listener interface that queues up and defers dispatch
+ * of decoded events until flushed.
+ */
+class QueuedInputListener : public InputListenerInterface {
+protected:
+ virtual ~QueuedInputListener();
+
+public:
+ QueuedInputListener(const sp<InputListenerInterface>& innerListener);
+
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
+ virtual void notifyKey(const NotifyKeyArgs* args);
+ virtual void notifyMotion(const NotifyMotionArgs* args);
+ virtual void notifySwitch(const NotifySwitchArgs* args);
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
+
+ void flush();
+
+private:
+ sp<InputListenerInterface> mInnerListener;
+ Vector<NotifyArgs*> mArgsQueue;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_LISTENER_H
diff --git a/widget/gonk/libui/InputManager.cpp b/widget/gonk/libui/InputManager.cpp
new file mode 100644
index 000000000..91af056bf
--- /dev/null
+++ b/widget/gonk/libui/InputManager.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputManager"
+
+//#define LOG_NDEBUG 0
+
+#include "InputManager.h"
+
+#include "cutils_log.h"
+
+namespace android {
+
+InputManager::InputManager(
+ const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& readerPolicy,
+ const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
+ mDispatcher = new InputDispatcher(dispatcherPolicy);
+ mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
+ initialize();
+}
+
+InputManager::InputManager(
+ const sp<InputReaderInterface>& reader,
+ const sp<InputDispatcherInterface>& dispatcher) :
+ mReader(reader),
+ mDispatcher(dispatcher) {
+ initialize();
+}
+
+InputManager::~InputManager() {
+ stop();
+}
+
+void InputManager::initialize() {
+ mReaderThread = new InputReaderThread(mReader);
+ mDispatcherThread = new InputDispatcherThread(mDispatcher);
+}
+
+status_t InputManager::start() {
+ status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ ALOGE("Could not start InputDispatcher thread due to error %d.", result);
+ return result;
+ }
+
+ result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ ALOGE("Could not start InputReader thread due to error %d.", result);
+
+ mDispatcherThread->requestExit();
+ return result;
+ }
+
+ return OK;
+}
+
+status_t InputManager::stop() {
+ status_t result = mReaderThread->requestExitAndWait();
+ if (result) {
+ ALOGW("Could not stop InputReader thread due to error %d.", result);
+ }
+
+ result = mDispatcherThread->requestExitAndWait();
+ if (result) {
+ ALOGW("Could not stop InputDispatcher thread due to error %d.", result);
+ }
+
+ return OK;
+}
+
+sp<InputReaderInterface> InputManager::getReader() {
+ return mReader;
+}
+
+sp<InputDispatcherInterface> InputManager::getDispatcher() {
+ return mDispatcher;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputManager.h b/widget/gonk/libui/InputManager.h
new file mode 100644
index 000000000..15a5176ec
--- /dev/null
+++ b/widget/gonk/libui/InputManager.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_MANAGER_H
+#define _UI_INPUT_MANAGER_H
+
+/**
+ * Native input manager.
+ */
+
+#include "EventHub.h"
+#include "InputReader.h"
+#include "InputDispatcher.h"
+
+#include "Input.h"
+#include "InputTransport.h"
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * The input manager is the core of the system event processing.
+ *
+ * The input manager uses two threads.
+ *
+ * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events,
+ * applies policy, and posts messages to a queue managed by the DispatcherThread.
+ * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the
+ * queue and asynchronously dispatches them to applications.
+ *
+ * By design, the InputReaderThread class and InputDispatcherThread class do not share any
+ * internal state. Moreover, all communication is done one way from the InputReaderThread
+ * into the InputDispatcherThread and never the reverse. Both classes may interact with the
+ * InputDispatchPolicy, however.
+ *
+ * The InputManager class never makes any calls into Java itself. Instead, the
+ * InputDispatchPolicy is responsible for performing all external interactions with the
+ * system, including calling DVM services.
+ */
+class InputManagerInterface : public virtual RefBase {
+protected:
+ InputManagerInterface() { }
+ virtual ~InputManagerInterface() { }
+
+public:
+ /* Starts the input manager threads. */
+ virtual status_t start() = 0;
+
+ /* Stops the input manager threads and waits for them to exit. */
+ virtual status_t stop() = 0;
+
+ /* Gets the input reader. */
+ virtual sp<InputReaderInterface> getReader() = 0;
+
+ /* Gets the input dispatcher. */
+ virtual sp<InputDispatcherInterface> getDispatcher() = 0;
+};
+
+class InputManager : public InputManagerInterface {
+protected:
+ virtual ~InputManager();
+
+public:
+ InputManager(
+ const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& readerPolicy,
+ const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);
+
+ // (used for testing purposes)
+ InputManager(
+ const sp<InputReaderInterface>& reader,
+ const sp<InputDispatcherInterface>& dispatcher);
+
+ virtual status_t start();
+ virtual status_t stop();
+
+ virtual sp<InputReaderInterface> getReader();
+ virtual sp<InputDispatcherInterface> getDispatcher();
+
+private:
+ sp<InputReaderInterface> mReader;
+ sp<InputReaderThread> mReaderThread;
+
+ sp<InputDispatcherInterface> mDispatcher;
+ sp<InputDispatcherThread> mDispatcherThread;
+
+ void initialize();
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_MANAGER_H
diff --git a/widget/gonk/libui/InputReader.cpp b/widget/gonk/libui/InputReader.cpp
new file mode 100644
index 000000000..3699569aa
--- /dev/null
+++ b/widget/gonk/libui/InputReader.cpp
@@ -0,0 +1,6510 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputReader"
+
+//#define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+// Log debug messages for each raw event received from the EventHub.
+#define DEBUG_RAW_EVENTS 0
+
+// Log debug messages about touch screen filtering hacks.
+#define DEBUG_HACKS 0
+
+// Log debug messages about virtual key processing.
+#define DEBUG_VIRTUAL_KEYS 0
+
+// Log debug messages about pointers.
+#define DEBUG_POINTERS 0
+
+// Log debug messages about pointer assignment calculations.
+#define DEBUG_POINTER_ASSIGNMENT 0
+
+// Log debug messages about gesture detection.
+#define DEBUG_GESTURES 0
+
+// Log debug messages about the vibrator.
+#define DEBUG_VIBRATOR 0
+
+#include "InputReader.h"
+
+#include "Keyboard.h"
+#include "VirtualKeyMap.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+#define INDENT4 " "
+#define INDENT5 " "
+
+namespace android {
+
+// --- Constants ---
+
+// Maximum number of slots supported when using the slot-based Multitouch Protocol B.
+static const size_t MAX_SLOTS = 32;
+
+// --- Static Functions ---
+
+template<typename T>
+inline static T abs(const T& value) {
+ return value < 0 ? - value : value;
+}
+
+template<typename T>
+inline static T min(const T& a, const T& b) {
+ return a < b ? a : b;
+}
+
+template<typename T>
+inline static void swap(T& a, T& b) {
+ T temp = a;
+ a = b;
+ b = temp;
+}
+
+inline static float avg(float x, float y) {
+ return (x + y) / 2;
+}
+
+inline static float distance(float x1, float y1, float x2, float y2) {
+ return hypotf(x1 - x2, y1 - y2);
+}
+
+inline static int32_t signExtendNybble(int32_t value) {
+ return value >= 8 ? value - 16 : value;
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static int32_t rotateValueUsingRotationMap(int32_t value, int32_t orientation,
+ const int32_t map[][4], size_t mapSize) {
+ if (orientation != DISPLAY_ORIENTATION_0) {
+ for (size_t i = 0; i < mapSize; i++) {
+ if (value == map[i][0]) {
+ return map[i][orientation];
+ }
+ }
+ }
+ return value;
+}
+
+static const int32_t keyCodeRotationMap[][4] = {
+ // key codes enumerated counter-clockwise with the original (unrotated) key first
+ // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation
+ { AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT },
+ { AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN },
+ { AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT },
+ { AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP },
+};
+static const size_t keyCodeRotationMapSize =
+ sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
+
+static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) {
+ return rotateValueUsingRotationMap(keyCode, orientation,
+ keyCodeRotationMap, keyCodeRotationMapSize);
+}
+
+static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) {
+ float temp;
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = *deltaX;
+ *deltaX = *deltaY;
+ *deltaY = -temp;
+ break;
+
+ case DISPLAY_ORIENTATION_180:
+ *deltaX = -*deltaX;
+ *deltaY = -*deltaY;
+ break;
+
+ case DISPLAY_ORIENTATION_270:
+ temp = *deltaX;
+ *deltaX = -*deltaY;
+ *deltaY = temp;
+ break;
+ }
+}
+
+static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) {
+ return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0;
+}
+
+// Returns true if the pointer should be reported as being down given the specified
+// button states. This determines whether the event is reported as a touch event.
+static bool isPointerDown(int32_t buttonState) {
+ return buttonState &
+ (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY
+ | AMOTION_EVENT_BUTTON_TERTIARY);
+}
+
+static float calculateCommonVector(float a, float b) {
+ if (a > 0 && b > 0) {
+ return a < b ? a : b;
+ } else if (a < 0 && b < 0) {
+ return a > b ? a : b;
+ } else {
+ return 0;
+ }
+}
+
+static void synthesizeButtonKey(InputReaderContext* context, int32_t action,
+ nsecs_t when, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState,
+ int32_t buttonState, int32_t keyCode) {
+ if (
+ (action == AKEY_EVENT_ACTION_DOWN
+ && !(lastButtonState & buttonState)
+ && (currentButtonState & buttonState))
+ || (action == AKEY_EVENT_ACTION_UP
+ && (lastButtonState & buttonState)
+ && !(currentButtonState & buttonState))) {
+ NotifyKeyArgs args(when, deviceId, source, policyFlags,
+ action, 0, keyCode, 0, context->getGlobalMetaState(), when);
+ context->getListener()->notifyKey(&args);
+ }
+}
+
+static void synthesizeButtonKeys(InputReaderContext* context, int32_t action,
+ nsecs_t when, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState) {
+ synthesizeButtonKey(context, action, when, deviceId, source, policyFlags,
+ lastButtonState, currentButtonState,
+ AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK);
+ synthesizeButtonKey(context, action, when, deviceId, source, policyFlags,
+ lastButtonState, currentButtonState,
+ AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD);
+}
+
+
+// --- InputReaderConfiguration ---
+
+bool InputReaderConfiguration::getDisplayInfo(bool external, DisplayViewport* outViewport) const {
+ const DisplayViewport& viewport = external ? mExternalDisplay : mInternalDisplay;
+ if (viewport.displayId >= 0) {
+ *outViewport = viewport;
+ return true;
+ }
+ return false;
+}
+
+void InputReaderConfiguration::setDisplayInfo(bool external, const DisplayViewport& viewport) {
+ DisplayViewport& v = external ? mExternalDisplay : mInternalDisplay;
+ v = viewport;
+}
+
+
+// --- InputReader ---
+
+InputReader::InputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputListenerInterface>& listener) :
+ mContext(this), mEventHub(eventHub), mPolicy(policy),
+ mGlobalMetaState(0), mGeneration(1),
+ mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
+ mConfigurationChangesToRefresh(0) {
+ mQueuedListener = new QueuedInputListener(listener);
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ refreshConfigurationLocked(0);
+ updateGlobalMetaStateLocked();
+ } // release lock
+}
+
+InputReader::~InputReader() {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ delete mDevices.valueAt(i);
+ }
+}
+
+void InputReader::loopOnce() {
+ int32_t oldGeneration;
+ int32_t timeoutMillis;
+ bool inputDevicesChanged = false;
+ Vector<InputDeviceInfo> inputDevices;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ oldGeneration = mGeneration;
+ timeoutMillis = -1;
+
+ uint32_t changes = mConfigurationChangesToRefresh;
+ if (changes) {
+ mConfigurationChangesToRefresh = 0;
+ timeoutMillis = 0;
+ refreshConfigurationLocked(changes);
+ } else if (mNextTimeout != LLONG_MAX) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
+ }
+ } // release lock
+
+ size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+ mReaderIsAliveCondition.broadcast();
+
+ if (count) {
+ processEventsLocked(mEventBuffer, count);
+ }
+
+ if (mNextTimeout != LLONG_MAX) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (now >= mNextTimeout) {
+#if DEBUG_RAW_EVENTS
+ ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
+#endif
+ mNextTimeout = LLONG_MAX;
+ timeoutExpiredLocked(now);
+ }
+ }
+
+ if (oldGeneration != mGeneration) {
+ inputDevicesChanged = true;
+ getInputDevicesLocked(inputDevices);
+ }
+ } // release lock
+
+ // Send out a message that the describes the changed input devices.
+ if (inputDevicesChanged) {
+ mPolicy->notifyInputDevicesChanged(inputDevices);
+ }
+
+ // Flush queued events out to the listener.
+ // This must happen outside of the lock because the listener could potentially call
+ // back into the InputReader's methods, such as getScanCodeState, or become blocked
+ // on another thread similarly waiting to acquire the InputReader lock thereby
+ // resulting in a deadlock. This situation is actually quite plausible because the
+ // listener is actually the input dispatcher, which calls into the window manager,
+ // which occasionally calls into the input reader.
+ mQueuedListener->flush();
+}
+
+void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
+ for (const RawEvent* rawEvent = rawEvents; count;) {
+ int32_t type = rawEvent->type;
+ size_t batchSize = 1;
+ if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
+ int32_t deviceId = rawEvent->deviceId;
+ while (batchSize < count) {
+ if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
+ || rawEvent[batchSize].deviceId != deviceId) {
+ break;
+ }
+ batchSize += 1;
+ }
+#if DEBUG_RAW_EVENTS
+ ALOGD("BatchSize: %d Count: %d", batchSize, count);
+#endif
+ processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
+ } else {
+ switch (rawEvent->type) {
+ case EventHubInterface::DEVICE_ADDED:
+ addDeviceLocked(rawEvent->when, rawEvent->deviceId);
+ break;
+ case EventHubInterface::DEVICE_REMOVED:
+ removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
+ break;
+ case EventHubInterface::FINISHED_DEVICE_SCAN:
+ handleConfigurationChangedLocked(rawEvent->when);
+ break;
+ default:
+ ALOG_ASSERT(false); // can't happen
+ break;
+ }
+ }
+ count -= batchSize;
+ rawEvent += batchSize;
+ }
+}
+
+void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+ return;
+ }
+
+ InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
+ uint32_t classes = mEventHub->getDeviceClasses(deviceId);
+
+ InputDevice* device = createDeviceLocked(deviceId, identifier, classes);
+ device->configure(when, &mConfig, 0);
+ device->reset(when);
+
+ if (device->isIgnored()) {
+ ALOGI("Device added: id=%d, name='%s' (ignored non-input device)", deviceId,
+ identifier.name.string());
+ } else {
+ ALOGI("Device added: id=%d, name='%s', sources=0x%08x", deviceId,
+ identifier.name.string(), device->getSources());
+ }
+
+ mDevices.add(deviceId, device);
+ bumpGenerationLocked();
+}
+
+void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) {
+ InputDevice* device = NULL;
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex < 0) {
+ ALOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
+ return;
+ }
+
+ device = mDevices.valueAt(deviceIndex);
+ mDevices.removeItemsAt(deviceIndex, 1);
+ bumpGenerationLocked();
+
+ if (device->isIgnored()) {
+ ALOGI("Device removed: id=%d, name='%s' (ignored non-input device)",
+ device->getId(), device->getName().string());
+ } else {
+ ALOGI("Device removed: id=%d, name='%s', sources=0x%08x",
+ device->getId(), device->getName().string(), device->getSources());
+ }
+
+ device->reset(when);
+ delete device;
+}
+
+InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
+ const InputDeviceIdentifier& identifier, uint32_t classes) {
+ InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
+ identifier, classes);
+
+ // External devices.
+ if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
+ device->setExternal(true);
+ }
+
+ // Switch-like devices.
+ if (classes & INPUT_DEVICE_CLASS_SWITCH) {
+ device->addMapper(new SwitchInputMapper(device));
+ }
+
+ // Vibrator-like devices.
+ if (classes & INPUT_DEVICE_CLASS_VIBRATOR) {
+ device->addMapper(new VibratorInputMapper(device));
+ }
+
+ // Keyboard-like devices.
+ uint32_t keyboardSource = 0;
+ int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
+ if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+ keyboardSource |= AINPUT_SOURCE_KEYBOARD;
+ }
+ if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
+ keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
+ }
+ if (classes & INPUT_DEVICE_CLASS_DPAD) {
+ keyboardSource |= AINPUT_SOURCE_DPAD;
+ }
+ if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
+ keyboardSource |= AINPUT_SOURCE_GAMEPAD;
+ }
+
+ if (keyboardSource != 0) {
+ device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
+ }
+
+ // Cursor-like devices.
+ if (classes & INPUT_DEVICE_CLASS_CURSOR) {
+ device->addMapper(new CursorInputMapper(device));
+ }
+
+ // Touchscreens and touchpad devices.
+ if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
+ device->addMapper(new MultiTouchInputMapper(device));
+ } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
+ device->addMapper(new SingleTouchInputMapper(device));
+ }
+
+ // Joystick-like devices.
+ if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
+ device->addMapper(new JoystickInputMapper(device));
+ }
+
+ return device;
+}
+
+void InputReader::processEventsForDeviceLocked(int32_t deviceId,
+ const RawEvent* rawEvents, size_t count) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex < 0) {
+ ALOGW("Discarding event for unknown deviceId %d.", deviceId);
+ return;
+ }
+
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (device->isIgnored()) {
+ //ALOGD("Discarding event for ignored deviceId %d.", deviceId);
+ return;
+ }
+
+ device->process(rawEvents, count);
+}
+
+void InputReader::timeoutExpiredLocked(nsecs_t when) {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (!device->isIgnored()) {
+ device->timeoutExpired(when);
+ }
+ }
+}
+
+void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
+ // Reset global meta state because it depends on the list of all configured devices.
+ updateGlobalMetaStateLocked();
+
+ // Enqueue configuration changed.
+ NotifyConfigurationChangedArgs args(when);
+ mQueuedListener->notifyConfigurationChanged(&args);
+}
+
+void InputReader::refreshConfigurationLocked(uint32_t changes) {
+ mPolicy->getReaderConfiguration(&mConfig);
+ mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
+
+ if (changes) {
+ ALOGI("Reconfiguring input devices. changes=0x%08x", changes);
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
+ mEventHub->requestReopenDevices();
+ } else {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ device->configure(now, &mConfig, changes);
+ }
+ }
+ }
+}
+
+void InputReader::updateGlobalMetaStateLocked() {
+ mGlobalMetaState = 0;
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ mGlobalMetaState |= device->getMetaState();
+ }
+}
+
+int32_t InputReader::getGlobalMetaStateLocked() {
+ return mGlobalMetaState;
+}
+
+void InputReader::disableVirtualKeysUntilLocked(nsecs_t time) {
+ mDisableVirtualKeysTimeout = time;
+}
+
+bool InputReader::shouldDropVirtualKeyLocked(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) {
+ if (now < mDisableVirtualKeysTimeout) {
+ ALOGI("Dropping virtual key from device %s because virtual keys are "
+ "temporarily disabled for the next %0.3fms. keyCode=%d, scanCode=%d",
+ device->getName().string(),
+ (mDisableVirtualKeysTimeout - now) * 0.000001,
+ keyCode, scanCode);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void InputReader::fadePointerLocked() {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ device->fadePointer();
+ }
+}
+
+void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) {
+ if (when < mNextTimeout) {
+ mNextTimeout = when;
+ mEventHub->wake();
+ }
+}
+
+int32_t InputReader::bumpGenerationLocked() {
+ return ++mGeneration;
+}
+
+void InputReader::getInputDevices(Vector<InputDeviceInfo>& outInputDevices) {
+ AutoMutex _l(mLock);
+ getInputDevicesLocked(outInputDevices);
+}
+
+void InputReader::getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices) {
+ outInputDevices.clear();
+
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (!device->isIgnored()) {
+ outInputDevices.push();
+ device->getDeviceInfo(&outInputDevices.editTop());
+ }
+ }
+}
+
+int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode) {
+ AutoMutex _l(mLock);
+
+ return getStateLocked(deviceId, sourceMask, keyCode, &InputDevice::getKeyCodeState);
+}
+
+int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode) {
+ AutoMutex _l(mLock);
+
+ return getStateLocked(deviceId, sourceMask, scanCode, &InputDevice::getScanCodeState);
+}
+
+int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
+ AutoMutex _l(mLock);
+
+ return getStateLocked(deviceId, sourceMask, switchCode, &InputDevice::getSwitchState);
+}
+
+int32_t InputReader::getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
+ GetStateFunc getStateFunc) {
+ int32_t result = AKEY_STATE_UNKNOWN;
+ if (deviceId >= 0) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result = (device->*getStateFunc)(sourceMask, code);
+ }
+ }
+ } else {
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ // If any device reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that
+ // value. Otherwise, return AKEY_STATE_UP as long as one device reports it.
+ int32_t currentResult = (device->*getStateFunc)(sourceMask, code);
+ if (currentResult >= AKEY_STATE_DOWN) {
+ return currentResult;
+ } else if (currentResult == AKEY_STATE_UP) {
+ result = currentResult;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+ AutoMutex _l(mLock);
+
+ memset(outFlags, 0, numCodes);
+ return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+}
+
+bool InputReader::markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+ bool result = false;
+ if (deviceId >= 0) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result = device->markSupportedKeyCodes(sourceMask,
+ numCodes, keyCodes, outFlags);
+ }
+ }
+ } else {
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result |= device->markSupportedKeyCodes(sourceMask,
+ numCodes, keyCodes, outFlags);
+ }
+ }
+ }
+ return result;
+}
+
+void InputReader::requestRefreshConfiguration(uint32_t changes) {
+ AutoMutex _l(mLock);
+
+ if (changes) {
+ bool needWake = !mConfigurationChangesToRefresh;
+ mConfigurationChangesToRefresh |= changes;
+
+ if (needWake) {
+ mEventHub->wake();
+ }
+ }
+}
+
+void InputReader::vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->cancelVibrate(token);
+ }
+}
+
+void InputReader::dump(String8& dump) {
+ AutoMutex _l(mLock);
+
+ mEventHub->dump(dump);
+ dump.append("\n");
+
+ dump.append("Input Reader State:\n");
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ mDevices.valueAt(i)->dump(dump);
+ }
+
+ dump.append(INDENT "Configuration:\n");
+ dump.append(INDENT2 "ExcludedDeviceNames: [");
+ for (size_t i = 0; i < mConfig.excludedDeviceNames.size(); i++) {
+ if (i != 0) {
+ dump.append(", ");
+ }
+ dump.append(mConfig.excludedDeviceNames.itemAt(i).string());
+ }
+ dump.append("]\n");
+ dump.appendFormat(INDENT2 "VirtualKeyQuietTime: %0.1fms\n",
+ mConfig.virtualKeyQuietTime * 0.000001f);
+
+ dump.appendFormat(INDENT2 "PointerVelocityControlParameters: "
+ "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n",
+ mConfig.pointerVelocityControlParameters.scale,
+ mConfig.pointerVelocityControlParameters.lowThreshold,
+ mConfig.pointerVelocityControlParameters.highThreshold,
+ mConfig.pointerVelocityControlParameters.acceleration);
+
+ dump.appendFormat(INDENT2 "WheelVelocityControlParameters: "
+ "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n",
+ mConfig.wheelVelocityControlParameters.scale,
+ mConfig.wheelVelocityControlParameters.lowThreshold,
+ mConfig.wheelVelocityControlParameters.highThreshold,
+ mConfig.wheelVelocityControlParameters.acceleration);
+
+ dump.appendFormat(INDENT2 "PointerGesture:\n");
+ dump.appendFormat(INDENT3 "Enabled: %s\n",
+ toString(mConfig.pointerGesturesEnabled));
+ dump.appendFormat(INDENT3 "QuietInterval: %0.1fms\n",
+ mConfig.pointerGestureQuietInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "DragMinSwitchSpeed: %0.1fpx/s\n",
+ mConfig.pointerGestureDragMinSwitchSpeed);
+ dump.appendFormat(INDENT3 "TapInterval: %0.1fms\n",
+ mConfig.pointerGestureTapInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "TapDragInterval: %0.1fms\n",
+ mConfig.pointerGestureTapDragInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "TapSlop: %0.1fpx\n",
+ mConfig.pointerGestureTapSlop);
+ dump.appendFormat(INDENT3 "MultitouchSettleInterval: %0.1fms\n",
+ mConfig.pointerGestureMultitouchSettleInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "MultitouchMinDistance: %0.1fpx\n",
+ mConfig.pointerGestureMultitouchMinDistance);
+ dump.appendFormat(INDENT3 "SwipeTransitionAngleCosine: %0.1f\n",
+ mConfig.pointerGestureSwipeTransitionAngleCosine);
+ dump.appendFormat(INDENT3 "SwipeMaxWidthRatio: %0.1f\n",
+ mConfig.pointerGestureSwipeMaxWidthRatio);
+ dump.appendFormat(INDENT3 "MovementSpeedRatio: %0.1f\n",
+ mConfig.pointerGestureMovementSpeedRatio);
+ dump.appendFormat(INDENT3 "ZoomSpeedRatio: %0.1f\n",
+ mConfig.pointerGestureZoomSpeedRatio);
+}
+
+void InputReader::monitor() {
+ // Acquire and release the lock to ensure that the reader has not deadlocked.
+ mLock.lock();
+ mEventHub->wake();
+ mReaderIsAliveCondition.wait(mLock);
+ mLock.unlock();
+
+ // Check the EventHub
+ mEventHub->monitor();
+}
+
+
+// --- InputReader::ContextImpl ---
+
+InputReader::ContextImpl::ContextImpl(InputReader* reader) :
+ mReader(reader) {
+}
+
+void InputReader::ContextImpl::updateGlobalMetaState() {
+ // lock is already held by the input loop
+ mReader->updateGlobalMetaStateLocked();
+}
+
+int32_t InputReader::ContextImpl::getGlobalMetaState() {
+ // lock is already held by the input loop
+ return mReader->getGlobalMetaStateLocked();
+}
+
+void InputReader::ContextImpl::disableVirtualKeysUntil(nsecs_t time) {
+ // lock is already held by the input loop
+ mReader->disableVirtualKeysUntilLocked(time);
+}
+
+bool InputReader::ContextImpl::shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) {
+ // lock is already held by the input loop
+ return mReader->shouldDropVirtualKeyLocked(now, device, keyCode, scanCode);
+}
+
+void InputReader::ContextImpl::fadePointer() {
+ // lock is already held by the input loop
+ mReader->fadePointerLocked();
+}
+
+void InputReader::ContextImpl::requestTimeoutAtTime(nsecs_t when) {
+ // lock is already held by the input loop
+ mReader->requestTimeoutAtTimeLocked(when);
+}
+
+int32_t InputReader::ContextImpl::bumpGeneration() {
+ // lock is already held by the input loop
+ return mReader->bumpGenerationLocked();
+}
+
+InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() {
+ return mReader->mPolicy.get();
+}
+
+InputListenerInterface* InputReader::ContextImpl::getListener() {
+ return mReader->mQueuedListener.get();
+}
+
+EventHubInterface* InputReader::ContextImpl::getEventHub() {
+ return mReader->mEventHub.get();
+}
+
+
+// --- InputReaderThread ---
+
+InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
+ Thread(/*canCallJava*/ true), mReader(reader) {
+}
+
+InputReaderThread::~InputReaderThread() {
+}
+
+bool InputReaderThread::threadLoop() {
+ mReader->loopOnce();
+ return true;
+}
+
+
+// --- InputDevice ---
+
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
+ const InputDeviceIdentifier& identifier, uint32_t classes) :
+ mContext(context), mId(id), mGeneration(generation),
+ mIdentifier(identifier), mClasses(classes),
+ mSources(0), mIsExternal(false), mDropUntilNextSync(false) {
+}
+
+InputDevice::~InputDevice() {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ delete mMappers[i];
+ }
+ mMappers.clear();
+}
+
+void InputDevice::dump(String8& dump) {
+ InputDeviceInfo deviceInfo;
+ getDeviceInfo(& deviceInfo);
+
+ dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(),
+ deviceInfo.getDisplayName().string());
+ dump.appendFormat(INDENT2 "Generation: %d\n", mGeneration);
+ dump.appendFormat(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
+ dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+ dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+
+ const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
+ if (!ranges.isEmpty()) {
+ dump.append(INDENT2 "Motion Ranges:\n");
+ for (size_t i = 0; i < ranges.size(); i++) {
+ const InputDeviceInfo::MotionRange& range = ranges.itemAt(i);
+ const char* label = getAxisLabel(range.axis);
+ char name[32];
+ if (label) {
+ strncpy(name, label, sizeof(name));
+ name[sizeof(name) - 1] = '\0';
+ } else {
+ snprintf(name, sizeof(name), "%d", range.axis);
+ }
+ dump.appendFormat(INDENT3 "%s: source=0x%08x, "
+ "min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f, resolution=%0.3f\n",
+ name, range.source, range.min, range.max, range.flat, range.fuzz,
+ range.resolution);
+ }
+ }
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->dump(dump);
+ }
+}
+
+void InputDevice::addMapper(InputMapper* mapper) {
+ mMappers.add(mapper);
+}
+
+void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) {
+ mSources = 0;
+
+ if (!isIgnored()) {
+ if (!changes) { // first time only
+ mContext->getEventHub()->getConfiguration(mId, &mConfiguration);
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
+ if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) {
+ sp<KeyCharacterMap> keyboardLayout =
+ mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier.descriptor);
+ if (mContext->getEventHub()->setKeyboardLayoutOverlay(mId, keyboardLayout)) {
+ bumpGeneration();
+ }
+ }
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) {
+ if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) {
+ String8 alias = mContext->getPolicy()->getDeviceAlias(mIdentifier);
+ if (mAlias != alias) {
+ mAlias = alias;
+ bumpGeneration();
+ }
+ }
+ }
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->configure(when, config, changes);
+ mSources |= mapper->getSources();
+ }
+ }
+}
+
+void InputDevice::reset(nsecs_t when) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->reset(when);
+ }
+
+ mContext->updateGlobalMetaState();
+
+ notifyReset(when);
+}
+
+void InputDevice::process(const RawEvent* rawEvents, size_t count) {
+ // Process all of the events in order for each mapper.
+ // We cannot simply ask each mapper to process them in bulk because mappers may
+ // have side-effects that must be interleaved. For example, joystick movement events and
+ // gamepad button presses are handled by different mappers but they should be dispatched
+ // in the order received.
+ size_t numMappers = mMappers.size();
+ for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
+#if DEBUG_RAW_EVENTS
+ ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%lld",
+ rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value,
+ rawEvent->when);
+#endif
+
+ if (mDropUntilNextSync) {
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ mDropUntilNextSync = false;
+#if DEBUG_RAW_EVENTS
+ ALOGD("Recovered from input event buffer overrun.");
+#endif
+ } else {
+#if DEBUG_RAW_EVENTS
+ ALOGD("Dropped input event while waiting for next input sync.");
+#endif
+ }
+ } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
+ ALOGI("Detected input event buffer overrun for device %s.", getName().string());
+ mDropUntilNextSync = true;
+ reset(rawEvent->when);
+ } else {
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->process(rawEvent);
+ }
+ }
+ }
+}
+
+void InputDevice::timeoutExpired(nsecs_t when) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->timeoutExpired(when);
+ }
+}
+
+void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
+ outDeviceInfo->initialize(mId, mGeneration, mIdentifier, mAlias, mIsExternal);
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->populateDeviceInfo(outDeviceInfo);
+ }
+}
+
+int32_t InputDevice::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return getState(sourceMask, keyCode, & InputMapper::getKeyCodeState);
+}
+
+int32_t InputDevice::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return getState(sourceMask, scanCode, & InputMapper::getScanCodeState);
+}
+
+int32_t InputDevice::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return getState(sourceMask, switchCode, & InputMapper::getSwitchState);
+}
+
+int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) {
+ int32_t result = AKEY_STATE_UNKNOWN;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+ // If any mapper reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that
+ // value. Otherwise, return AKEY_STATE_UP as long as one mapper reports it.
+ int32_t currentResult = (mapper->*getStateFunc)(sourceMask, code);
+ if (currentResult >= AKEY_STATE_DOWN) {
+ return currentResult;
+ } else if (currentResult == AKEY_STATE_UP) {
+ result = currentResult;
+ }
+ }
+ }
+ return result;
+}
+
+bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ bool result = false;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+ result |= mapper->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
+ }
+ }
+ return result;
+}
+
+void InputDevice::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputDevice::cancelVibrate(int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->cancelVibrate(token);
+ }
+}
+
+int32_t InputDevice::getMetaState() {
+ int32_t result = 0;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ result |= mapper->getMetaState();
+ }
+ return result;
+}
+
+void InputDevice::fadePointer() {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->fadePointer();
+ }
+}
+
+void InputDevice::bumpGeneration() {
+ mGeneration = mContext->bumpGeneration();
+}
+
+void InputDevice::notifyReset(nsecs_t when) {
+ NotifyDeviceResetArgs args(when, mId);
+ mContext->getListener()->notifyDeviceReset(&args);
+}
+
+
+// --- CursorButtonAccumulator ---
+
+CursorButtonAccumulator::CursorButtonAccumulator() {
+ clearButtons();
+}
+
+void CursorButtonAccumulator::reset(InputDevice* device) {
+ mBtnLeft = device->isKeyPressed(BTN_LEFT);
+ mBtnRight = device->isKeyPressed(BTN_RIGHT);
+ mBtnMiddle = device->isKeyPressed(BTN_MIDDLE);
+ mBtnBack = device->isKeyPressed(BTN_BACK);
+ mBtnSide = device->isKeyPressed(BTN_SIDE);
+ mBtnForward = device->isKeyPressed(BTN_FORWARD);
+ mBtnExtra = device->isKeyPressed(BTN_EXTRA);
+ mBtnTask = device->isKeyPressed(BTN_TASK);
+}
+
+void CursorButtonAccumulator::clearButtons() {
+ mBtnLeft = 0;
+ mBtnRight = 0;
+ mBtnMiddle = 0;
+ mBtnBack = 0;
+ mBtnSide = 0;
+ mBtnForward = 0;
+ mBtnExtra = 0;
+ mBtnTask = 0;
+}
+
+void CursorButtonAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_KEY) {
+ switch (rawEvent->code) {
+ case BTN_LEFT:
+ mBtnLeft = rawEvent->value;
+ break;
+ case BTN_RIGHT:
+ mBtnRight = rawEvent->value;
+ break;
+ case BTN_MIDDLE:
+ mBtnMiddle = rawEvent->value;
+ break;
+ case BTN_BACK:
+ mBtnBack = rawEvent->value;
+ break;
+ case BTN_SIDE:
+ mBtnSide = rawEvent->value;
+ break;
+ case BTN_FORWARD:
+ mBtnForward = rawEvent->value;
+ break;
+ case BTN_EXTRA:
+ mBtnExtra = rawEvent->value;
+ break;
+ case BTN_TASK:
+ mBtnTask = rawEvent->value;
+ break;
+ }
+ }
+}
+
+uint32_t CursorButtonAccumulator::getButtonState() const {
+ uint32_t result = 0;
+ if (mBtnLeft) {
+ result |= AMOTION_EVENT_BUTTON_PRIMARY;
+ }
+ if (mBtnRight) {
+ result |= AMOTION_EVENT_BUTTON_SECONDARY;
+ }
+ if (mBtnMiddle) {
+ result |= AMOTION_EVENT_BUTTON_TERTIARY;
+ }
+ if (mBtnBack || mBtnSide) {
+ result |= AMOTION_EVENT_BUTTON_BACK;
+ }
+ if (mBtnForward || mBtnExtra) {
+ result |= AMOTION_EVENT_BUTTON_FORWARD;
+ }
+ return result;
+}
+
+
+// --- CursorMotionAccumulator ---
+
+CursorMotionAccumulator::CursorMotionAccumulator() {
+ clearRelativeAxes();
+}
+
+void CursorMotionAccumulator::reset(InputDevice* device) {
+ clearRelativeAxes();
+}
+
+void CursorMotionAccumulator::clearRelativeAxes() {
+ mRelX = 0;
+ mRelY = 0;
+}
+
+void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_REL) {
+ switch (rawEvent->code) {
+ case REL_X:
+ mRelX = rawEvent->value;
+ break;
+ case REL_Y:
+ mRelY = rawEvent->value;
+ break;
+ }
+ }
+}
+
+void CursorMotionAccumulator::finishSync() {
+ clearRelativeAxes();
+}
+
+
+// --- CursorScrollAccumulator ---
+
+CursorScrollAccumulator::CursorScrollAccumulator() :
+ mHaveRelWheel(false), mHaveRelHWheel(false) {
+ clearRelativeAxes();
+}
+
+void CursorScrollAccumulator::configure(InputDevice* device) {
+ mHaveRelWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_WHEEL);
+ mHaveRelHWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_HWHEEL);
+}
+
+void CursorScrollAccumulator::reset(InputDevice* device) {
+ clearRelativeAxes();
+}
+
+void CursorScrollAccumulator::clearRelativeAxes() {
+ mRelWheel = 0;
+ mRelHWheel = 0;
+}
+
+void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_REL) {
+ switch (rawEvent->code) {
+ case REL_WHEEL:
+ mRelWheel = rawEvent->value;
+ break;
+ case REL_HWHEEL:
+ mRelHWheel = rawEvent->value;
+ break;
+ }
+ }
+}
+
+void CursorScrollAccumulator::finishSync() {
+ clearRelativeAxes();
+}
+
+
+// --- TouchButtonAccumulator ---
+
+TouchButtonAccumulator::TouchButtonAccumulator() :
+ mHaveBtnTouch(false), mHaveStylus(false) {
+ clearButtons();
+}
+
+void TouchButtonAccumulator::configure(InputDevice* device) {
+ mHaveBtnTouch = device->hasKey(BTN_TOUCH);
+ mHaveStylus = device->hasKey(BTN_TOOL_PEN)
+ || device->hasKey(BTN_TOOL_RUBBER)
+ || device->hasKey(BTN_TOOL_BRUSH)
+ || device->hasKey(BTN_TOOL_PENCIL)
+ || device->hasKey(BTN_TOOL_AIRBRUSH);
+}
+
+void TouchButtonAccumulator::reset(InputDevice* device) {
+ mBtnTouch = device->isKeyPressed(BTN_TOUCH);
+ mBtnStylus = device->isKeyPressed(BTN_STYLUS);
+ mBtnStylus2 = device->isKeyPressed(BTN_STYLUS);
+ mBtnToolFinger = device->isKeyPressed(BTN_TOOL_FINGER);
+ mBtnToolPen = device->isKeyPressed(BTN_TOOL_PEN);
+ mBtnToolRubber = device->isKeyPressed(BTN_TOOL_RUBBER);
+ mBtnToolBrush = device->isKeyPressed(BTN_TOOL_BRUSH);
+ mBtnToolPencil = device->isKeyPressed(BTN_TOOL_PENCIL);
+ mBtnToolAirbrush = device->isKeyPressed(BTN_TOOL_AIRBRUSH);
+ mBtnToolMouse = device->isKeyPressed(BTN_TOOL_MOUSE);
+ mBtnToolLens = device->isKeyPressed(BTN_TOOL_LENS);
+ mBtnToolDoubleTap = device->isKeyPressed(BTN_TOOL_DOUBLETAP);
+ mBtnToolTripleTap = device->isKeyPressed(BTN_TOOL_TRIPLETAP);
+ mBtnToolQuadTap = device->isKeyPressed(BTN_TOOL_QUADTAP);
+}
+
+void TouchButtonAccumulator::clearButtons() {
+ mBtnTouch = 0;
+ mBtnStylus = 0;
+ mBtnStylus2 = 0;
+ mBtnToolFinger = 0;
+ mBtnToolPen = 0;
+ mBtnToolRubber = 0;
+ mBtnToolBrush = 0;
+ mBtnToolPencil = 0;
+ mBtnToolAirbrush = 0;
+ mBtnToolMouse = 0;
+ mBtnToolLens = 0;
+ mBtnToolDoubleTap = 0;
+ mBtnToolTripleTap = 0;
+ mBtnToolQuadTap = 0;
+}
+
+void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_KEY) {
+ switch (rawEvent->code) {
+ case BTN_TOUCH:
+ mBtnTouch = rawEvent->value;
+ break;
+ case BTN_STYLUS:
+ mBtnStylus = rawEvent->value;
+ break;
+ case BTN_STYLUS2:
+ mBtnStylus2 = rawEvent->value;
+ break;
+ case BTN_TOOL_FINGER:
+ mBtnToolFinger = rawEvent->value;
+ break;
+ case BTN_TOOL_PEN:
+ mBtnToolPen = rawEvent->value;
+ break;
+ case BTN_TOOL_RUBBER:
+ mBtnToolRubber = rawEvent->value;
+ break;
+ case BTN_TOOL_BRUSH:
+ mBtnToolBrush = rawEvent->value;
+ break;
+ case BTN_TOOL_PENCIL:
+ mBtnToolPencil = rawEvent->value;
+ break;
+ case BTN_TOOL_AIRBRUSH:
+ mBtnToolAirbrush = rawEvent->value;
+ break;
+ case BTN_TOOL_MOUSE:
+ mBtnToolMouse = rawEvent->value;
+ break;
+ case BTN_TOOL_LENS:
+ mBtnToolLens = rawEvent->value;
+ break;
+ case BTN_TOOL_DOUBLETAP:
+ mBtnToolDoubleTap = rawEvent->value;
+ break;
+ case BTN_TOOL_TRIPLETAP:
+ mBtnToolTripleTap = rawEvent->value;
+ break;
+ case BTN_TOOL_QUADTAP:
+ mBtnToolQuadTap = rawEvent->value;
+ break;
+ }
+ }
+}
+
+uint32_t TouchButtonAccumulator::getButtonState() const {
+ uint32_t result = 0;
+ if (mBtnStylus) {
+ result |= AMOTION_EVENT_BUTTON_SECONDARY;
+ }
+ if (mBtnStylus2) {
+ result |= AMOTION_EVENT_BUTTON_TERTIARY;
+ }
+ return result;
+}
+
+int32_t TouchButtonAccumulator::getToolType() const {
+ if (mBtnToolMouse || mBtnToolLens) {
+ return AMOTION_EVENT_TOOL_TYPE_MOUSE;
+ }
+ if (mBtnToolRubber) {
+ return AMOTION_EVENT_TOOL_TYPE_ERASER;
+ }
+ if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) {
+ return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+ }
+ if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap) {
+ return AMOTION_EVENT_TOOL_TYPE_FINGER;
+ }
+ return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+bool TouchButtonAccumulator::isToolActive() const {
+ return mBtnTouch || mBtnToolFinger || mBtnToolPen || mBtnToolRubber
+ || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush
+ || mBtnToolMouse || mBtnToolLens
+ || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap;
+}
+
+bool TouchButtonAccumulator::isHovering() const {
+ return mHaveBtnTouch && !mBtnTouch;
+}
+
+bool TouchButtonAccumulator::hasStylus() const {
+ return mHaveStylus;
+}
+
+
+// --- RawPointerAxes ---
+
+RawPointerAxes::RawPointerAxes() {
+ clear();
+}
+
+void RawPointerAxes::clear() {
+ x.clear();
+ y.clear();
+ pressure.clear();
+ touchMajor.clear();
+ touchMinor.clear();
+ toolMajor.clear();
+ toolMinor.clear();
+ orientation.clear();
+ distance.clear();
+ tiltX.clear();
+ tiltY.clear();
+ trackingId.clear();
+ slot.clear();
+}
+
+
+// --- RawPointerData ---
+
+RawPointerData::RawPointerData() {
+ clear();
+}
+
+void RawPointerData::clear() {
+ pointerCount = 0;
+ clearIdBits();
+}
+
+void RawPointerData::copyFrom(const RawPointerData& other) {
+ pointerCount = other.pointerCount;
+ hoveringIdBits = other.hoveringIdBits;
+ touchingIdBits = other.touchingIdBits;
+
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointers[i] = other.pointers[i];
+
+ int id = pointers[i].id;
+ idToIndex[id] = other.idToIndex[id];
+ }
+}
+
+void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const {
+ float x = 0, y = 0;
+ uint32_t count = touchingIdBits.count();
+ if (count) {
+ for (BitSet32 idBits(touchingIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const Pointer& pointer = pointerForId(id);
+ x += pointer.x;
+ y += pointer.y;
+ }
+ x /= count;
+ y /= count;
+ }
+ *outX = x;
+ *outY = y;
+}
+
+
+// --- CookedPointerData ---
+
+CookedPointerData::CookedPointerData() {
+ clear();
+}
+
+void CookedPointerData::clear() {
+ pointerCount = 0;
+ hoveringIdBits.clear();
+ touchingIdBits.clear();
+}
+
+void CookedPointerData::copyFrom(const CookedPointerData& other) {
+ pointerCount = other.pointerCount;
+ hoveringIdBits = other.hoveringIdBits;
+ touchingIdBits = other.touchingIdBits;
+
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].copyFrom(other.pointerProperties[i]);
+ pointerCoords[i].copyFrom(other.pointerCoords[i]);
+
+ int id = pointerProperties[i].id;
+ idToIndex[id] = other.idToIndex[id];
+ }
+}
+
+
+// --- SingleTouchMotionAccumulator ---
+
+SingleTouchMotionAccumulator::SingleTouchMotionAccumulator() {
+ clearAbsoluteAxes();
+}
+
+void SingleTouchMotionAccumulator::reset(InputDevice* device) {
+ mAbsX = device->getAbsoluteAxisValue(ABS_X);
+ mAbsY = device->getAbsoluteAxisValue(ABS_Y);
+ mAbsPressure = device->getAbsoluteAxisValue(ABS_PRESSURE);
+ mAbsToolWidth = device->getAbsoluteAxisValue(ABS_TOOL_WIDTH);
+ mAbsDistance = device->getAbsoluteAxisValue(ABS_DISTANCE);
+ mAbsTiltX = device->getAbsoluteAxisValue(ABS_TILT_X);
+ mAbsTiltY = device->getAbsoluteAxisValue(ABS_TILT_Y);
+}
+
+void SingleTouchMotionAccumulator::clearAbsoluteAxes() {
+ mAbsX = 0;
+ mAbsY = 0;
+ mAbsPressure = 0;
+ mAbsToolWidth = 0;
+ mAbsDistance = 0;
+ mAbsTiltX = 0;
+ mAbsTiltY = 0;
+}
+
+void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_ABS) {
+ switch (rawEvent->code) {
+ case ABS_X:
+ mAbsX = rawEvent->value;
+ break;
+ case ABS_Y:
+ mAbsY = rawEvent->value;
+ break;
+ case ABS_PRESSURE:
+ mAbsPressure = rawEvent->value;
+ break;
+ case ABS_TOOL_WIDTH:
+ mAbsToolWidth = rawEvent->value;
+ break;
+ case ABS_DISTANCE:
+ mAbsDistance = rawEvent->value;
+ break;
+ case ABS_TILT_X:
+ mAbsTiltX = rawEvent->value;
+ break;
+ case ABS_TILT_Y:
+ mAbsTiltY = rawEvent->value;
+ break;
+ }
+ }
+}
+
+
+// --- MultiTouchMotionAccumulator ---
+
+MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() :
+ mCurrentSlot(-1), mSlots(NULL), mSlotCount(0), mUsingSlotsProtocol(false),
+ mHaveStylus(false) {
+}
+
+MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() {
+ delete[] mSlots;
+}
+
+void MultiTouchMotionAccumulator::configure(InputDevice* device,
+ size_t slotCount, bool usingSlotsProtocol) {
+ mSlotCount = slotCount;
+ mUsingSlotsProtocol = usingSlotsProtocol;
+ mHaveStylus = device->hasAbsoluteAxis(ABS_MT_TOOL_TYPE);
+
+ delete[] mSlots;
+ mSlots = new Slot[slotCount];
+}
+
+void MultiTouchMotionAccumulator::reset(InputDevice* device) {
+ // Unfortunately there is no way to read the initial contents of the slots.
+ // So when we reset the accumulator, we must assume they are all zeroes.
+ if (mUsingSlotsProtocol) {
+ // Query the driver for the current slot index and use it as the initial slot
+ // before we start reading events from the device. It is possible that the
+ // current slot index will not be the same as it was when the first event was
+ // written into the evdev buffer, which means the input mapper could start
+ // out of sync with the initial state of the events in the evdev buffer.
+ // In the extremely unlikely case that this happens, the data from
+ // two slots will be confused until the next ABS_MT_SLOT event is received.
+ // This can cause the touch point to "jump", but at least there will be
+ // no stuck touches.
+ int32_t initialSlot;
+ status_t status = device->getEventHub()->getAbsoluteAxisValue(device->getId(),
+ ABS_MT_SLOT, &initialSlot);
+ if (status) {
+ ALOGD("Could not retrieve current multitouch slot index. status=%d", status);
+ initialSlot = -1;
+ }
+ clearSlots(initialSlot);
+ } else {
+ clearSlots(-1);
+ }
+}
+
+void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) {
+ if (mSlots) {
+ for (size_t i = 0; i < mSlotCount; i++) {
+ mSlots[i].clear();
+ }
+ }
+ mCurrentSlot = initialSlot;
+}
+
+void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_ABS) {
+#if DEBUG_POINTERS
+ bool newSlot = false;
+#endif
+ if (mUsingSlotsProtocol) {
+ if (rawEvent->code == ABS_MT_SLOT) {
+ mCurrentSlot = rawEvent->value;
+#if DEBUG_POINTERS
+ newSlot = true;
+#endif
+ }
+ } else if (mCurrentSlot < 0) {
+ mCurrentSlot = 0;
+ }
+
+ if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) {
+#if DEBUG_POINTERS
+ if (newSlot) {
+ ALOGW("MultiTouch device emitted invalid slot index %d but it "
+ "should be between 0 and %d; ignoring this slot.",
+ mCurrentSlot, mSlotCount - 1);
+ }
+#endif
+ } else {
+ Slot* slot = &mSlots[mCurrentSlot];
+
+ switch (rawEvent->code) {
+ case ABS_MT_POSITION_X:
+ slot->mInUse = true;
+ slot->mAbsMTPositionX = rawEvent->value;
+ break;
+ case ABS_MT_POSITION_Y:
+ slot->mInUse = true;
+ slot->mAbsMTPositionY = rawEvent->value;
+ break;
+ case ABS_MT_TOUCH_MAJOR:
+ slot->mInUse = true;
+ slot->mAbsMTTouchMajor = rawEvent->value;
+ break;
+ case ABS_MT_TOUCH_MINOR:
+ slot->mInUse = true;
+ slot->mAbsMTTouchMinor = rawEvent->value;
+ slot->mHaveAbsMTTouchMinor = true;
+ break;
+ case ABS_MT_WIDTH_MAJOR:
+ slot->mInUse = true;
+ slot->mAbsMTWidthMajor = rawEvent->value;
+ break;
+ case ABS_MT_WIDTH_MINOR:
+ slot->mInUse = true;
+ slot->mAbsMTWidthMinor = rawEvent->value;
+ slot->mHaveAbsMTWidthMinor = true;
+ break;
+ case ABS_MT_ORIENTATION:
+ slot->mInUse = true;
+ slot->mAbsMTOrientation = rawEvent->value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ if (mUsingSlotsProtocol && rawEvent->value < 0) {
+ // The slot is no longer in use but it retains its previous contents,
+ // which may be reused for subsequent touches.
+ slot->mInUse = false;
+ } else {
+ slot->mInUse = true;
+ slot->mAbsMTTrackingId = rawEvent->value;
+ }
+ break;
+ case ABS_MT_PRESSURE:
+ slot->mInUse = true;
+ slot->mAbsMTPressure = rawEvent->value;
+ break;
+ case ABS_MT_DISTANCE:
+ slot->mInUse = true;
+ slot->mAbsMTDistance = rawEvent->value;
+ break;
+ case ABS_MT_TOOL_TYPE:
+ slot->mInUse = true;
+ slot->mAbsMTToolType = rawEvent->value;
+ slot->mHaveAbsMTToolType = true;
+ break;
+ }
+ }
+ } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
+ // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+ mCurrentSlot += 1;
+ }
+}
+
+void MultiTouchMotionAccumulator::finishSync() {
+ if (!mUsingSlotsProtocol) {
+ clearSlots(-1);
+ }
+}
+
+bool MultiTouchMotionAccumulator::hasStylus() const {
+ return mHaveStylus;
+}
+
+
+// --- MultiTouchMotionAccumulator::Slot ---
+
+MultiTouchMotionAccumulator::Slot::Slot() {
+ clear();
+}
+
+void MultiTouchMotionAccumulator::Slot::clear() {
+ mInUse = false;
+ mHaveAbsMTTouchMinor = false;
+ mHaveAbsMTWidthMinor = false;
+ mHaveAbsMTToolType = false;
+ mAbsMTPositionX = 0;
+ mAbsMTPositionY = 0;
+ mAbsMTTouchMajor = 0;
+ mAbsMTTouchMinor = 0;
+ mAbsMTWidthMajor = 0;
+ mAbsMTWidthMinor = 0;
+ mAbsMTOrientation = 0;
+ mAbsMTTrackingId = -1;
+ mAbsMTPressure = 0;
+ mAbsMTDistance = 0;
+ mAbsMTToolType = 0;
+}
+
+int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
+ if (mHaveAbsMTToolType) {
+ switch (mAbsMTToolType) {
+ case MT_TOOL_FINGER:
+ return AMOTION_EVENT_TOOL_TYPE_FINGER;
+ case MT_TOOL_PEN:
+ return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+ }
+ }
+ return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+
+// --- InputMapper ---
+
+InputMapper::InputMapper(InputDevice* device) :
+ mDevice(device), mContext(device->getContext()) {
+}
+
+InputMapper::~InputMapper() {
+}
+
+void InputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ info->addSource(getSources());
+}
+
+void InputMapper::dump(String8& dump) {
+}
+
+void InputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+}
+
+void InputMapper::reset(nsecs_t when) {
+}
+
+void InputMapper::timeoutExpired(nsecs_t when) {
+}
+
+int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ return false;
+}
+
+void InputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+}
+
+void InputMapper::cancelVibrate(int32_t token) {
+}
+
+int32_t InputMapper::getMetaState() {
+ return 0;
+}
+
+void InputMapper::fadePointer() {
+}
+
+status_t InputMapper::getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo) {
+ return getEventHub()->getAbsoluteAxisInfo(getDeviceId(), axis, axisInfo);
+}
+
+void InputMapper::bumpGeneration() {
+ mDevice->bumpGeneration();
+}
+
+void InputMapper::dumpRawAbsoluteAxisInfo(String8& dump,
+ const RawAbsoluteAxisInfo& axis, const char* name) {
+ if (axis.valid) {
+ dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n",
+ name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution);
+ } else {
+ dump.appendFormat(INDENT4 "%s: unknown range\n", name);
+ }
+}
+
+
+// --- SwitchInputMapper ---
+
+SwitchInputMapper::SwitchInputMapper(InputDevice* device) :
+ InputMapper(device), mUpdatedSwitchValues(0), mUpdatedSwitchMask(0) {
+}
+
+SwitchInputMapper::~SwitchInputMapper() {
+}
+
+uint32_t SwitchInputMapper::getSources() {
+ return AINPUT_SOURCE_SWITCH;
+}
+
+void SwitchInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_SW:
+ processSwitch(rawEvent->code, rawEvent->value);
+ break;
+
+ case EV_SYN:
+ if (rawEvent->code == SYN_REPORT) {
+ sync(rawEvent->when);
+ }
+ }
+}
+
+void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) {
+ if (switchCode >= 0 && switchCode < 32) {
+ if (switchValue) {
+ mUpdatedSwitchValues |= 1 << switchCode;
+ }
+ mUpdatedSwitchMask |= 1 << switchCode;
+ }
+}
+
+void SwitchInputMapper::sync(nsecs_t when) {
+ if (mUpdatedSwitchMask) {
+ NotifySwitchArgs args(when, 0, mUpdatedSwitchValues, mUpdatedSwitchMask);
+ getListener()->notifySwitch(&args);
+
+ mUpdatedSwitchValues = 0;
+ mUpdatedSwitchMask = 0;
+ }
+}
+
+int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return getEventHub()->getSwitchState(getDeviceId(), switchCode);
+}
+
+
+// --- VibratorInputMapper ---
+
+VibratorInputMapper::VibratorInputMapper(InputDevice* device) :
+ InputMapper(device), mVibrating(false) {
+}
+
+VibratorInputMapper::~VibratorInputMapper() {
+}
+
+uint32_t VibratorInputMapper::getSources() {
+ return 0;
+}
+
+void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ info->setVibrator(true);
+}
+
+void VibratorInputMapper::process(const RawEvent* rawEvent) {
+ // TODO: Handle FF_STATUS, although it does not seem to be widely supported.
+}
+
+void VibratorInputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+#if DEBUG_VIBRATOR
+ String8 patternStr;
+ for (size_t i = 0; i < patternSize; i++) {
+ if (i != 0) {
+ patternStr.append(", ");
+ }
+ patternStr.appendFormat("%lld", pattern[i]);
+ }
+ ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%ld, token=%d",
+ getDeviceId(), patternStr.string(), repeat, token);
+#endif
+
+ mVibrating = true;
+ memcpy(mPattern, pattern, patternSize * sizeof(nsecs_t));
+ mPatternSize = patternSize;
+ mRepeat = repeat;
+ mToken = token;
+ mIndex = -1;
+
+ nextStep();
+}
+
+void VibratorInputMapper::cancelVibrate(int32_t token) {
+#if DEBUG_VIBRATOR
+ ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
+#endif
+
+ if (mVibrating && mToken == token) {
+ stopVibrating();
+ }
+}
+
+void VibratorInputMapper::timeoutExpired(nsecs_t when) {
+ if (mVibrating) {
+ if (when >= mNextStepTime) {
+ nextStep();
+ } else {
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+ }
+ }
+}
+
+void VibratorInputMapper::nextStep() {
+ mIndex += 1;
+ if (size_t(mIndex) >= mPatternSize) {
+ if (mRepeat < 0) {
+ // We are done.
+ stopVibrating();
+ return;
+ }
+ mIndex = mRepeat;
+ }
+
+ bool vibratorOn = mIndex & 1;
+ nsecs_t duration = mPattern[mIndex];
+ if (vibratorOn) {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending vibrate deviceId=%d, duration=%lld",
+ getDeviceId(), duration);
+#endif
+ getEventHub()->vibrate(getDeviceId(), duration);
+ } else {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+ }
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ mNextStepTime = now + duration;
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: scheduled timeout in %0.3fms", duration * 0.000001f);
+#endif
+}
+
+void VibratorInputMapper::stopVibrating() {
+ mVibrating = false;
+#if DEBUG_VIBRATOR
+ ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+}
+
+void VibratorInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Vibrator Input Mapper:\n");
+ dump.appendFormat(INDENT3 "Vibrating: %s\n", toString(mVibrating));
+}
+
+
+// --- KeyboardInputMapper ---
+
+KeyboardInputMapper::KeyboardInputMapper(InputDevice* device,
+ uint32_t source, int32_t keyboardType) :
+ InputMapper(device), mSource(source),
+ mKeyboardType(keyboardType) {
+}
+
+KeyboardInputMapper::~KeyboardInputMapper() {
+}
+
+uint32_t KeyboardInputMapper::getSources() {
+ return mSource;
+}
+
+void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ info->setKeyboardType(mKeyboardType);
+ info->setKeyCharacterMap(getEventHub()->getKeyCharacterMap(getDeviceId()));
+}
+
+void KeyboardInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Keyboard Input Mapper:\n");
+ dumpParameters(dump);
+ dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType);
+ dump.appendFormat(INDENT3 "Orientation: %d\n", mOrientation);
+ dump.appendFormat(INDENT3 "KeyDowns: %d keys currently down\n", mKeyDowns.size());
+ dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mMetaState);
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", mDownTime);
+}
+
+
+void KeyboardInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ // Configure basic parameters.
+ configureParameters();
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+ DisplayViewport v;
+ if (config->getDisplayInfo(false /*external*/, &v)) {
+ mOrientation = v.orientation;
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ }
+}
+
+void KeyboardInputMapper::configureParameters() {
+ mParameters.orientationAware = false;
+ getDevice()->getConfiguration().tryGetProperty(String8("keyboard.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.hasAssociatedDisplay = false;
+ if (mParameters.orientationAware) {
+ mParameters.hasAssociatedDisplay = true;
+ }
+}
+
+void KeyboardInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+ dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n",
+ toString(mParameters.hasAssociatedDisplay));
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void KeyboardInputMapper::reset(nsecs_t when) {
+ mMetaState = AMETA_NONE;
+ mDownTime = 0;
+ mKeyDowns.clear();
+ mCurrentHidUsage = 0;
+
+ resetLedState();
+
+ InputMapper::reset(when);
+}
+
+void KeyboardInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_KEY: {
+ int32_t scanCode = rawEvent->code;
+ int32_t usageCode = mCurrentHidUsage;
+ mCurrentHidUsage = 0;
+
+ if (isKeyboardOrGamepadKey(scanCode)) {
+ int32_t keyCode;
+ uint32_t flags;
+ if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
+ keyCode = AKEYCODE_UNKNOWN;
+ flags = 0;
+ }
+ processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
+ }
+ break;
+ }
+ case EV_MSC: {
+ if (rawEvent->code == MSC_SCAN) {
+ mCurrentHidUsage = rawEvent->value;
+ }
+ break;
+ }
+ case EV_SYN: {
+ if (rawEvent->code == SYN_REPORT) {
+ mCurrentHidUsage = 0;
+ }
+ }
+ }
+}
+
+bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
+ return scanCode < BTN_MOUSE
+ || scanCode >= KEY_OK
+ || (scanCode >= BTN_MISC && scanCode < BTN_MOUSE)
+ || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
+}
+
+void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
+ int32_t scanCode, uint32_t policyFlags) {
+
+ if (down) {
+ // Rotate key codes according to orientation if needed.
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+ keyCode = rotateKeyCode(keyCode, mOrientation);
+ }
+
+ // Add key down.
+ ssize_t keyDownIndex = findKeyDown(scanCode);
+ if (keyDownIndex >= 0) {
+ // key repeat, be sure to use same keycode as before in case of rotation
+ keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
+ } else {
+ // key down
+ if ((policyFlags & POLICY_FLAG_VIRTUAL)
+ && mContext->shouldDropVirtualKey(when,
+ getDevice(), keyCode, scanCode)) {
+ return;
+ }
+
+ mKeyDowns.push();
+ KeyDown& keyDown = mKeyDowns.editTop();
+ keyDown.keyCode = keyCode;
+ keyDown.scanCode = scanCode;
+ }
+
+ mDownTime = when;
+ } else {
+ // Remove key down.
+ ssize_t keyDownIndex = findKeyDown(scanCode);
+ if (keyDownIndex >= 0) {
+ // key up, be sure to use same keycode as before in case of rotation
+ keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
+ mKeyDowns.removeAt(size_t(keyDownIndex));
+ } else {
+ // key was not actually down
+ ALOGI("Dropping key up from device %s because the key was not down. "
+ "keyCode=%d, scanCode=%d",
+ getDeviceName().string(), keyCode, scanCode);
+ return;
+ }
+ }
+
+ bool metaStateChanged = false;
+ int32_t oldMetaState = mMetaState;
+ int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState);
+ if (oldMetaState != newMetaState) {
+ mMetaState = newMetaState;
+ metaStateChanged = true;
+ updateLedState(false);
+ }
+
+ nsecs_t downTime = mDownTime;
+
+ // Key down on external an keyboard should wake the device.
+ // We don't do this for internal keyboards to prevent them from waking up in your pocket.
+ // For internal keyboards, the key layout file should specify the policy flags for
+ // each wake key individually.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ if (down && getDevice()->isExternal()
+ && !(policyFlags & (POLICY_FLAG_WAKE | POLICY_FLAG_WAKE_DROPPED))) {
+ policyFlags |= POLICY_FLAG_WAKE_DROPPED;
+ }
+
+ if (metaStateChanged) {
+ getContext()->updateGlobalMetaState();
+ }
+
+ if (down && !isMetaKey(keyCode)) {
+ getContext()->fadePointer();
+ }
+
+ NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
+ down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
+ getListener()->notifyKey(&args);
+}
+
+ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) {
+ size_t n = mKeyDowns.size();
+ for (size_t i = 0; i < n; i++) {
+ if (mKeyDowns[i].scanCode == scanCode) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return getEventHub()->getKeyCodeState(getDeviceId(), keyCode);
+}
+
+int32_t KeyboardInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+}
+
+bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ return getEventHub()->markSupportedKeyCodes(getDeviceId(), numCodes, keyCodes, outFlags);
+}
+
+int32_t KeyboardInputMapper::getMetaState() {
+ return mMetaState;
+}
+
+void KeyboardInputMapper::resetLedState() {
+ initializeLedState(mCapsLockLedState, LED_CAPSL);
+ initializeLedState(mNumLockLedState, LED_NUML);
+ initializeLedState(mScrollLockLedState, LED_SCROLLL);
+
+ updateLedState(true);
+}
+
+void KeyboardInputMapper::initializeLedState(LedState& ledState, int32_t led) {
+ ledState.avail = getEventHub()->hasLed(getDeviceId(), led);
+ ledState.on = false;
+}
+
+void KeyboardInputMapper::updateLedState(bool reset) {
+ updateLedStateForModifier(mCapsLockLedState, LED_CAPSL,
+ AMETA_CAPS_LOCK_ON, reset);
+ updateLedStateForModifier(mNumLockLedState, LED_NUML,
+ AMETA_NUM_LOCK_ON, reset);
+ updateLedStateForModifier(mScrollLockLedState, LED_SCROLLL,
+ AMETA_SCROLL_LOCK_ON, reset);
+}
+
+void KeyboardInputMapper::updateLedStateForModifier(LedState& ledState,
+ int32_t led, int32_t modifier, bool reset) {
+ if (ledState.avail) {
+ bool desiredState = (mMetaState & modifier) != 0;
+ if (reset || ledState.on != desiredState) {
+ getEventHub()->setLedState(getDeviceId(), led, desiredState);
+ ledState.on = desiredState;
+ }
+ }
+}
+
+
+// --- CursorInputMapper ---
+
+CursorInputMapper::CursorInputMapper(InputDevice* device) :
+ InputMapper(device) {
+}
+
+CursorInputMapper::~CursorInputMapper() {
+}
+
+uint32_t CursorInputMapper::getSources() {
+ return mSource;
+}
+
+void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ if (mParameters.mode == Parameters::MODE_POINTER) {
+ float minX, minY, maxX, maxY;
+ if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f);
+ }
+ } else {
+ info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f);
+ }
+ info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+
+ if (mCursorScrollAccumulator.haveRelativeVWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+ }
+ if (mCursorScrollAccumulator.haveRelativeHWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+ }
+}
+
+void CursorInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Cursor Input Mapper:\n");
+ dumpParameters(dump);
+ dump.appendFormat(INDENT3 "XScale: %0.3f\n", mXScale);
+ dump.appendFormat(INDENT3 "YScale: %0.3f\n", mYScale);
+ dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
+ dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
+ dump.appendFormat(INDENT3 "HaveVWheel: %s\n",
+ toString(mCursorScrollAccumulator.haveRelativeVWheel()));
+ dump.appendFormat(INDENT3 "HaveHWheel: %s\n",
+ toString(mCursorScrollAccumulator.haveRelativeHWheel()));
+ dump.appendFormat(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
+ dump.appendFormat(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
+ dump.appendFormat(INDENT3 "Orientation: %d\n", mOrientation);
+ dump.appendFormat(INDENT3 "ButtonState: 0x%08x\n", mButtonState);
+ dump.appendFormat(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState)));
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", mDownTime);
+}
+
+void CursorInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ mCursorScrollAccumulator.configure(getDevice());
+
+ // Configure basic parameters.
+ configureParameters();
+
+ // Configure device mode.
+ switch (mParameters.mode) {
+ case Parameters::MODE_POINTER:
+ mSource = AINPUT_SOURCE_MOUSE;
+ mXPrecision = 1.0f;
+ mYPrecision = 1.0f;
+ mXScale = 1.0f;
+ mYScale = 1.0f;
+ mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+ break;
+ case Parameters::MODE_NAVIGATION:
+ mSource = AINPUT_SOURCE_TRACKBALL;
+ mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+ mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+ mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+ mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+ break;
+ }
+
+ mVWheelScale = 1.0f;
+ mHWheelScale = 1.0f;
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
+ mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters);
+ mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters);
+ mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters);
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+ DisplayViewport v;
+ if (config->getDisplayInfo(false /*external*/, &v)) {
+ mOrientation = v.orientation;
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ bumpGeneration();
+ }
+}
+
+void CursorInputMapper::configureParameters() {
+ mParameters.mode = Parameters::MODE_POINTER;
+ String8 cursorModeString;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("cursor.mode"), cursorModeString)) {
+ if (cursorModeString == "navigation") {
+ mParameters.mode = Parameters::MODE_NAVIGATION;
+ } else if (cursorModeString != "pointer" && cursorModeString != "default") {
+ ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string());
+ }
+ }
+
+ mParameters.orientationAware = false;
+ getDevice()->getConfiguration().tryGetProperty(String8("cursor.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.hasAssociatedDisplay = false;
+ if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) {
+ mParameters.hasAssociatedDisplay = true;
+ }
+}
+
+void CursorInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+ dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n",
+ toString(mParameters.hasAssociatedDisplay));
+
+ switch (mParameters.mode) {
+ case Parameters::MODE_POINTER:
+ dump.append(INDENT4 "Mode: pointer\n");
+ break;
+ case Parameters::MODE_NAVIGATION:
+ dump.append(INDENT4 "Mode: navigation\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void CursorInputMapper::reset(nsecs_t when) {
+ mButtonState = 0;
+ mDownTime = 0;
+
+ mPointerVelocityControl.reset();
+ mWheelXVelocityControl.reset();
+ mWheelYVelocityControl.reset();
+
+ mCursorButtonAccumulator.reset(getDevice());
+ mCursorMotionAccumulator.reset(getDevice());
+ mCursorScrollAccumulator.reset(getDevice());
+
+ InputMapper::reset(when);
+}
+
+void CursorInputMapper::process(const RawEvent* rawEvent) {
+ mCursorButtonAccumulator.process(rawEvent);
+ mCursorMotionAccumulator.process(rawEvent);
+ mCursorScrollAccumulator.process(rawEvent);
+
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ sync(rawEvent->when);
+ }
+}
+
+void CursorInputMapper::sync(nsecs_t when) {
+ int32_t lastButtonState = mButtonState;
+ int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
+ mButtonState = currentButtonState;
+
+ bool wasDown = isPointerDown(lastButtonState);
+ bool down = isPointerDown(currentButtonState);
+ bool downChanged;
+ if (!wasDown && down) {
+ mDownTime = when;
+ downChanged = true;
+ } else if (wasDown && !down) {
+ downChanged = true;
+ } else {
+ downChanged = false;
+ }
+ nsecs_t downTime = mDownTime;
+ bool buttonsChanged = currentButtonState != lastButtonState;
+ bool buttonsPressed = currentButtonState & ~lastButtonState;
+
+ float deltaX = mCursorMotionAccumulator.getRelativeX() * mXScale;
+ float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale;
+ bool moved = deltaX != 0 || deltaY != 0;
+
+ // Rotate delta according to orientation if needed.
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay
+ && (deltaX != 0.0f || deltaY != 0.0f)) {
+ rotateDelta(mOrientation, &deltaX, &deltaY);
+ }
+
+ // Move the pointer.
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+
+ float vscroll = mCursorScrollAccumulator.getRelativeVWheel();
+ float hscroll = mCursorScrollAccumulator.getRelativeHWheel();
+ bool scrolled = vscroll != 0 || hscroll != 0;
+
+ mWheelYVelocityControl.move(when, NULL, &vscroll);
+ mWheelXVelocityControl.move(when, &hscroll, NULL);
+
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ int32_t displayId;
+ if (mPointerController != NULL) {
+ if (moved || scrolled || buttonsChanged) {
+ mPointerController->setPresentation(
+ PointerControllerInterface::PRESENTATION_POINTER);
+
+ if (moved) {
+ mPointerController->move(deltaX, deltaY);
+ }
+
+ if (buttonsChanged) {
+ mPointerController->setButtonState(currentButtonState);
+ }
+
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ }
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ displayId = ADISPLAY_ID_DEFAULT;
+ } else {
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY);
+ displayId = ADISPLAY_ID_NONE;
+ }
+
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
+
+ // Moving an external trackball or mouse should wake the device.
+ // We don't do this for internal cursor devices to prevent them from waking up
+ // the device in your pocket.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ uint32_t policyFlags = 0;
+ if ((buttonsPressed || moved || scrolled) && getDevice()->isExternal()) {
+ policyFlags |= POLICY_FLAG_WAKE_DROPPED;
+ }
+
+ // Synthesize key down from buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
+ policyFlags, lastButtonState, currentButtonState);
+
+ // Send motion event.
+ if (downChanged || moved || scrolled || buttonsChanged) {
+ int32_t metaState = mContext->getGlobalMetaState();
+ int32_t motionEventAction;
+ if (downChanged) {
+ motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+ } else if (down || mPointerController == NULL) {
+ motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+ } else {
+ motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE;
+ }
+
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ motionEventAction, 0, metaState, currentButtonState, 0,
+ displayId, 1, &pointerProperties, &pointerCoords,
+ mXPrecision, mYPrecision, downTime);
+ getListener()->notifyMotion(&args);
+
+ // Send hover move after UP to tell the application that the mouse is hovering now.
+ if (motionEventAction == AMOTION_EVENT_ACTION_UP
+ && mPointerController != NULL) {
+ NotifyMotionArgs hoverArgs(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, currentButtonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ displayId, 1, &pointerProperties, &pointerCoords,
+ mXPrecision, mYPrecision, downTime);
+ getListener()->notifyMotion(&hoverArgs);
+ }
+
+ // Send scroll events.
+ if (scrolled) {
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
+
+ NotifyMotionArgs scrollArgs(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_SCROLL, 0, metaState, currentButtonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ displayId, 1, &pointerProperties, &pointerCoords,
+ mXPrecision, mYPrecision, downTime);
+ getListener()->notifyMotion(&scrollArgs);
+ }
+ }
+
+ // Synthesize key up from buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+ policyFlags, lastButtonState, currentButtonState);
+
+ mCursorMotionAccumulator.finishSync();
+ mCursorScrollAccumulator.finishSync();
+}
+
+int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) {
+ return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+ } else {
+ return AKEY_STATE_UNKNOWN;
+ }
+}
+
+void CursorInputMapper::fadePointer() {
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+}
+
+
+// --- TouchInputMapper ---
+
+TouchInputMapper::TouchInputMapper(InputDevice* device) :
+ InputMapper(device),
+ mSource(0), mDeviceMode(DEVICE_MODE_DISABLED),
+ mSurfaceWidth(-1), mSurfaceHeight(-1), mSurfaceLeft(0), mSurfaceTop(0),
+ mSurfaceOrientation(DISPLAY_ORIENTATION_0) {
+}
+
+TouchInputMapper::~TouchInputMapper() {
+}
+
+uint32_t TouchInputMapper::getSources() {
+ return mSource;
+}
+
+void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ if (mDeviceMode != DEVICE_MODE_DISABLED) {
+ info->addMotionRange(mOrientedRanges.x);
+ info->addMotionRange(mOrientedRanges.y);
+ info->addMotionRange(mOrientedRanges.pressure);
+
+ if (mOrientedRanges.haveSize) {
+ info->addMotionRange(mOrientedRanges.size);
+ }
+
+ if (mOrientedRanges.haveTouchSize) {
+ info->addMotionRange(mOrientedRanges.touchMajor);
+ info->addMotionRange(mOrientedRanges.touchMinor);
+ }
+
+ if (mOrientedRanges.haveToolSize) {
+ info->addMotionRange(mOrientedRanges.toolMajor);
+ info->addMotionRange(mOrientedRanges.toolMinor);
+ }
+
+ if (mOrientedRanges.haveOrientation) {
+ info->addMotionRange(mOrientedRanges.orientation);
+ }
+
+ if (mOrientedRanges.haveDistance) {
+ info->addMotionRange(mOrientedRanges.distance);
+ }
+
+ if (mOrientedRanges.haveTilt) {
+ info->addMotionRange(mOrientedRanges.tilt);
+ }
+
+ if (mCursorScrollAccumulator.haveRelativeVWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f);
+ }
+ if (mCursorScrollAccumulator.haveRelativeHWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f);
+ }
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+ const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
+ const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat,
+ x.fuzz, x.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat,
+ y.fuzz, y.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat,
+ x.fuzz, x.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat,
+ y.fuzz, y.resolution);
+ }
+ }
+}
+
+void TouchInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Touch Input Mapper:\n");
+ dumpParameters(dump);
+ dumpVirtualKeys(dump);
+ dumpRawPointerAxes(dump);
+ dumpCalibration(dump);
+ dumpSurface(dump);
+
+ dump.appendFormat(INDENT3 "Translation and Scaling Factors:\n");
+ dump.appendFormat(INDENT4 "XTranslate: %0.3f\n", mXTranslate);
+ dump.appendFormat(INDENT4 "YTranslate: %0.3f\n", mYTranslate);
+ dump.appendFormat(INDENT4 "XScale: %0.3f\n", mXScale);
+ dump.appendFormat(INDENT4 "YScale: %0.3f\n", mYScale);
+ dump.appendFormat(INDENT4 "XPrecision: %0.3f\n", mXPrecision);
+ dump.appendFormat(INDENT4 "YPrecision: %0.3f\n", mYPrecision);
+ dump.appendFormat(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale);
+ dump.appendFormat(INDENT4 "PressureScale: %0.3f\n", mPressureScale);
+ dump.appendFormat(INDENT4 "SizeScale: %0.3f\n", mSizeScale);
+ dump.appendFormat(INDENT4 "OrientationScale: %0.3f\n", mOrientationScale);
+ dump.appendFormat(INDENT4 "DistanceScale: %0.3f\n", mDistanceScale);
+ dump.appendFormat(INDENT4 "HaveTilt: %s\n", toString(mHaveTilt));
+ dump.appendFormat(INDENT4 "TiltXCenter: %0.3f\n", mTiltXCenter);
+ dump.appendFormat(INDENT4 "TiltXScale: %0.3f\n", mTiltXScale);
+ dump.appendFormat(INDENT4 "TiltYCenter: %0.3f\n", mTiltYCenter);
+ dump.appendFormat(INDENT4 "TiltYScale: %0.3f\n", mTiltYScale);
+
+ dump.appendFormat(INDENT3 "Last Button State: 0x%08x\n", mLastButtonState);
+
+ dump.appendFormat(INDENT3 "Last Raw Touch: pointerCount=%d\n",
+ mLastRawPointerData.pointerCount);
+ for (uint32_t i = 0; i < mLastRawPointerData.pointerCount; i++) {
+ const RawPointerData::Pointer& pointer = mLastRawPointerData.pointers[i];
+ dump.appendFormat(INDENT4 "[%d]: id=%d, x=%d, y=%d, pressure=%d, "
+ "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, "
+ "orientation=%d, tiltX=%d, tiltY=%d, distance=%d, "
+ "toolType=%d, isHovering=%s\n", i,
+ pointer.id, pointer.x, pointer.y, pointer.pressure,
+ pointer.touchMajor, pointer.touchMinor,
+ pointer.toolMajor, pointer.toolMinor,
+ pointer.orientation, pointer.tiltX, pointer.tiltY, pointer.distance,
+ pointer.toolType, toString(pointer.isHovering));
+ }
+
+ dump.appendFormat(INDENT3 "Last Cooked Touch: pointerCount=%d\n",
+ mLastCookedPointerData.pointerCount);
+ for (uint32_t i = 0; i < mLastCookedPointerData.pointerCount; i++) {
+ const PointerProperties& pointerProperties = mLastCookedPointerData.pointerProperties[i];
+ const PointerCoords& pointerCoords = mLastCookedPointerData.pointerCoords[i];
+ dump.appendFormat(INDENT4 "[%d]: id=%d, x=%0.3f, y=%0.3f, pressure=%0.3f, "
+ "touchMajor=%0.3f, touchMinor=%0.3f, toolMajor=%0.3f, toolMinor=%0.3f, "
+ "orientation=%0.3f, tilt=%0.3f, distance=%0.3f, "
+ "toolType=%d, isHovering=%s\n", i,
+ pointerProperties.id,
+ pointerCoords.getX(),
+ pointerCoords.getY(),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TILT),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE),
+ pointerProperties.toolType,
+ toString(mLastCookedPointerData.isHovering(i)));
+ }
+
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ dump.appendFormat(INDENT3 "Pointer Gesture Detector:\n");
+ dump.appendFormat(INDENT4 "XMovementScale: %0.3f\n",
+ mPointerXMovementScale);
+ dump.appendFormat(INDENT4 "YMovementScale: %0.3f\n",
+ mPointerYMovementScale);
+ dump.appendFormat(INDENT4 "XZoomScale: %0.3f\n",
+ mPointerXZoomScale);
+ dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n",
+ mPointerYZoomScale);
+ dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n",
+ mPointerGestureMaxSwipeWidth);
+ }
+}
+
+void TouchInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ mConfig = *config;
+
+ if (!changes) { // first time only
+ // Configure basic parameters.
+ configureParameters();
+
+ // Configure common accumulators.
+ mCursorScrollAccumulator.configure(getDevice());
+ mTouchButtonAccumulator.configure(getDevice());
+
+ // Configure absolute axis information.
+ configureRawPointerAxes();
+
+ // Prepare input device calibration.
+ parseCalibration();
+ resolveCalibration();
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
+ // Update pointer speed.
+ mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters);
+ mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
+ mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
+ }
+
+ bool resetNeeded = false;
+ if (!changes || (changes & (InputReaderConfiguration::CHANGE_DISPLAY_INFO
+ | InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT
+ | InputReaderConfiguration::CHANGE_SHOW_TOUCHES))) {
+ // Configure device sources, surface dimensions, orientation and
+ // scaling factors.
+ configureSurface(when, &resetNeeded);
+ }
+
+ if (changes && resetNeeded) {
+ // Send reset, unless this is the first time the device has been configured,
+ // in which case the reader will call reset itself after all mappers are ready.
+ getDevice()->notifyReset(when);
+ }
+}
+
+void TouchInputMapper::configureParameters() {
+ // Use the pointer presentation mode for devices that do not support distinct
+ // multitouch. The spot-based presentation relies on being able to accurately
+ // locate two or more fingers on the touch pad.
+ mParameters.gestureMode = getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_SEMI_MT)
+ ? Parameters::GESTURE_MODE_POINTER : Parameters::GESTURE_MODE_SPOTS;
+
+ String8 gestureModeString;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("touch.gestureMode"),
+ gestureModeString)) {
+ if (gestureModeString == "pointer") {
+ mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER;
+ } else if (gestureModeString == "spots") {
+ mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS;
+ } else if (gestureModeString != "default") {
+ ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.string());
+ }
+ }
+
+ if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_DIRECT)) {
+ // The device is a touch screen.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ } else if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_POINTER)) {
+ // The device is a pointing device like a track pad.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+ } else if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X)
+ || getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
+ // The device is a cursor device with a touch pad attached.
+ // By default don't use the touch pad to move the pointer.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+ } else {
+ // The device is a touch pad of unknown purpose.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+ }
+
+ String8 deviceTypeString;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"),
+ deviceTypeString)) {
+ if (deviceTypeString == "touchScreen") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ } else if (deviceTypeString == "touchPad") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+ } else if (deviceTypeString == "touchNavigation") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_NAVIGATION;
+ } else if (deviceTypeString == "pointer") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+ } else if (deviceTypeString != "default") {
+ ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
+ }
+ }
+
+ mParameters.orientationAware = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.hasAssociatedDisplay = false;
+ mParameters.associatedDisplayIsExternal = false;
+ if (mParameters.orientationAware
+ || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
+ || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
+ mParameters.hasAssociatedDisplay = true;
+ mParameters.associatedDisplayIsExternal =
+ mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
+ && getDevice()->isExternal();
+ }
+}
+
+void TouchInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+
+ switch (mParameters.gestureMode) {
+ case Parameters::GESTURE_MODE_POINTER:
+ dump.append(INDENT4 "GestureMode: pointer\n");
+ break;
+ case Parameters::GESTURE_MODE_SPOTS:
+ dump.append(INDENT4 "GestureMode: spots\n");
+ break;
+ default:
+ assert(false);
+ }
+
+ switch (mParameters.deviceType) {
+ case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
+ dump.append(INDENT4 "DeviceType: touchScreen\n");
+ break;
+ case Parameters::DEVICE_TYPE_TOUCH_PAD:
+ dump.append(INDENT4 "DeviceType: touchPad\n");
+ break;
+ case Parameters::DEVICE_TYPE_TOUCH_NAVIGATION:
+ dump.append(INDENT4 "DeviceType: touchNavigation\n");
+ break;
+ case Parameters::DEVICE_TYPE_POINTER:
+ dump.append(INDENT4 "DeviceType: pointer\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ dump.appendFormat(INDENT4 "AssociatedDisplay: hasAssociatedDisplay=%s, isExternal=%s\n",
+ toString(mParameters.hasAssociatedDisplay),
+ toString(mParameters.associatedDisplayIsExternal));
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void TouchInputMapper::configureRawPointerAxes() {
+ mRawPointerAxes.clear();
+}
+
+void TouchInputMapper::dumpRawPointerAxes(String8& dump) {
+ dump.append(INDENT3 "Raw Touch Axes:\n");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.x, "X");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.y, "Y");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.pressure, "Pressure");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMajor, "TouchMajor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMinor, "TouchMinor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMajor, "ToolMajor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMinor, "ToolMinor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.orientation, "Orientation");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.distance, "Distance");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltX, "TiltX");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltY, "TiltY");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.trackingId, "TrackingId");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.slot, "Slot");
+}
+
+void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
+ int32_t oldDeviceMode = mDeviceMode;
+
+ // Determine device mode.
+ if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER
+ && mConfig.pointerGesturesEnabled) {
+ mSource = AINPUT_SOURCE_MOUSE;
+ mDeviceMode = DEVICE_MODE_POINTER;
+ if (hasStylus()) {
+ mSource |= AINPUT_SOURCE_STYLUS;
+ }
+ } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
+ && mParameters.hasAssociatedDisplay) {
+ mSource = AINPUT_SOURCE_TOUCHSCREEN;
+ mDeviceMode = DEVICE_MODE_DIRECT;
+ if (hasStylus()) {
+ mSource |= AINPUT_SOURCE_STYLUS;
+ }
+ } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_NAVIGATION) {
+ mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
+ mDeviceMode = DEVICE_MODE_NAVIGATION;
+ } else {
+ mSource = AINPUT_SOURCE_TOUCHPAD;
+ mDeviceMode = DEVICE_MODE_UNSCALED;
+ }
+
+ // Ensure we have valid X and Y axes.
+ if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
+ ALOGW(INDENT "Touch device '%s' did not report support for X or Y axis! "
+ "The device will be inoperable.", getDeviceName().string());
+ mDeviceMode = DEVICE_MODE_DISABLED;
+ return;
+ }
+
+ // Raw width and height in the natural orientation.
+ int32_t rawWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1;
+ int32_t rawHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1;
+
+ // Get associated display dimensions.
+ bool viewportChanged = false;
+ DisplayViewport newViewport;
+ if (mParameters.hasAssociatedDisplay) {
+ if (!mConfig.getDisplayInfo(mParameters.associatedDisplayIsExternal, &newViewport)) {
+ ALOGI(INDENT "Touch device '%s' could not query the properties of its associated "
+ "display. The device will be inoperable until the display size "
+ "becomes available.",
+ getDeviceName().string());
+ mDeviceMode = DEVICE_MODE_DISABLED;
+ return;
+ }
+ } else {
+ newViewport.setNonDisplayViewport(rawWidth, rawHeight);
+ }
+ if (mViewport != newViewport) {
+ mViewport = newViewport;
+ viewportChanged = true;
+
+ if (mDeviceMode == DEVICE_MODE_DIRECT || mDeviceMode == DEVICE_MODE_POINTER) {
+ // Convert rotated viewport to natural surface coordinates.
+ int32_t naturalLogicalWidth, naturalLogicalHeight;
+ int32_t naturalPhysicalWidth, naturalPhysicalHeight;
+ int32_t naturalPhysicalLeft, naturalPhysicalTop;
+ int32_t naturalDeviceWidth, naturalDeviceHeight;
+ switch (mViewport.orientation) {
+ case DISPLAY_ORIENTATION_90:
+ naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom;
+ naturalPhysicalTop = mViewport.physicalLeft;
+ naturalDeviceWidth = mViewport.deviceHeight;
+ naturalDeviceHeight = mViewport.deviceWidth;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight;
+ naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom;
+ naturalDeviceWidth = mViewport.deviceWidth;
+ naturalDeviceHeight = mViewport.deviceHeight;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalLeft = mViewport.physicalTop;
+ naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight;
+ naturalDeviceWidth = mViewport.deviceHeight;
+ naturalDeviceHeight = mViewport.deviceWidth;
+ break;
+ case DISPLAY_ORIENTATION_0:
+ default:
+ naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalLeft = mViewport.physicalLeft;
+ naturalPhysicalTop = mViewport.physicalTop;
+ naturalDeviceWidth = mViewport.deviceWidth;
+ naturalDeviceHeight = mViewport.deviceHeight;
+ break;
+ }
+
+ mSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
+ mSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
+ mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
+ mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
+
+ mSurfaceOrientation = mParameters.orientationAware ?
+ mViewport.orientation : DISPLAY_ORIENTATION_0;
+ } else {
+ mSurfaceWidth = rawWidth;
+ mSurfaceHeight = rawHeight;
+ mSurfaceLeft = 0;
+ mSurfaceTop = 0;
+ mSurfaceOrientation = DISPLAY_ORIENTATION_0;
+ }
+ }
+
+ // If moving between pointer modes, need to reset some state.
+ bool deviceModeChanged = mDeviceMode != oldDeviceMode;
+ if (deviceModeChanged) {
+ mOrientedRanges.clear();
+ }
+
+ // Create pointer controller if needed.
+ if (mDeviceMode == DEVICE_MODE_POINTER ||
+ (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches)) {
+ if (mPointerController == NULL) {
+ mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+ }
+ } else {
+ mPointerController.clear();
+ }
+
+ if (viewportChanged || deviceModeChanged) {
+ ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
+ "display id %d",
+ getDeviceId(), getDeviceName().string(), mSurfaceWidth, mSurfaceHeight,
+ mSurfaceOrientation, mDeviceMode, mViewport.displayId);
+
+ // Configure X and Y factors.
+ mXScale = float(mSurfaceWidth) / rawWidth;
+ mYScale = float(mSurfaceHeight) / rawHeight;
+ mXTranslate = -mSurfaceLeft;
+ mYTranslate = -mSurfaceTop;
+ mXPrecision = 1.0f / mXScale;
+ mYPrecision = 1.0f / mYScale;
+
+ mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
+ mOrientedRanges.x.source = mSource;
+ mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y;
+ mOrientedRanges.y.source = mSource;
+
+ configureVirtualKeys();
+
+ // Scale factor for terms that are not oriented in a particular axis.
+ // If the pixels are square then xScale == yScale otherwise we fake it
+ // by choosing an average.
+ mGeometricScale = avg(mXScale, mYScale);
+
+ // Size of diagonal axis.
+ float diagonalSize = hypotf(mSurfaceWidth, mSurfaceHeight);
+
+ // Size factors.
+ if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) {
+ if (mRawPointerAxes.touchMajor.valid
+ && mRawPointerAxes.touchMajor.maxValue != 0) {
+ mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue;
+ } else if (mRawPointerAxes.toolMajor.valid
+ && mRawPointerAxes.toolMajor.maxValue != 0) {
+ mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue;
+ } else {
+ mSizeScale = 0.0f;
+ }
+
+ mOrientedRanges.haveTouchSize = true;
+ mOrientedRanges.haveToolSize = true;
+ mOrientedRanges.haveSize = true;
+
+ mOrientedRanges.touchMajor.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR;
+ mOrientedRanges.touchMajor.source = mSource;
+ mOrientedRanges.touchMajor.min = 0;
+ mOrientedRanges.touchMajor.max = diagonalSize;
+ mOrientedRanges.touchMajor.flat = 0;
+ mOrientedRanges.touchMajor.fuzz = 0;
+ mOrientedRanges.touchMajor.resolution = 0;
+
+ mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
+ mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
+
+ mOrientedRanges.toolMajor.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR;
+ mOrientedRanges.toolMajor.source = mSource;
+ mOrientedRanges.toolMajor.min = 0;
+ mOrientedRanges.toolMajor.max = diagonalSize;
+ mOrientedRanges.toolMajor.flat = 0;
+ mOrientedRanges.toolMajor.fuzz = 0;
+ mOrientedRanges.toolMajor.resolution = 0;
+
+ mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
+ mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
+
+ mOrientedRanges.size.axis = AMOTION_EVENT_AXIS_SIZE;
+ mOrientedRanges.size.source = mSource;
+ mOrientedRanges.size.min = 0;
+ mOrientedRanges.size.max = 1.0;
+ mOrientedRanges.size.flat = 0;
+ mOrientedRanges.size.fuzz = 0;
+ mOrientedRanges.size.resolution = 0;
+ } else {
+ mSizeScale = 0.0f;
+ }
+
+ // Pressure factors.
+ mPressureScale = 0;
+ if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL
+ || mCalibration.pressureCalibration
+ == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) {
+ if (mCalibration.havePressureScale) {
+ mPressureScale = mCalibration.pressureScale;
+ } else if (mRawPointerAxes.pressure.valid
+ && mRawPointerAxes.pressure.maxValue != 0) {
+ mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue;
+ }
+ }
+
+ mOrientedRanges.pressure.axis = AMOTION_EVENT_AXIS_PRESSURE;
+ mOrientedRanges.pressure.source = mSource;
+ mOrientedRanges.pressure.min = 0;
+ mOrientedRanges.pressure.max = 1.0;
+ mOrientedRanges.pressure.flat = 0;
+ mOrientedRanges.pressure.fuzz = 0;
+ mOrientedRanges.pressure.resolution = 0;
+
+ // Tilt
+ mTiltXCenter = 0;
+ mTiltXScale = 0;
+ mTiltYCenter = 0;
+ mTiltYScale = 0;
+ mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid;
+ if (mHaveTilt) {
+ mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue,
+ mRawPointerAxes.tiltX.maxValue);
+ mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue,
+ mRawPointerAxes.tiltY.maxValue);
+ mTiltXScale = M_PI / 180;
+ mTiltYScale = M_PI / 180;
+
+ mOrientedRanges.haveTilt = true;
+
+ mOrientedRanges.tilt.axis = AMOTION_EVENT_AXIS_TILT;
+ mOrientedRanges.tilt.source = mSource;
+ mOrientedRanges.tilt.min = 0;
+ mOrientedRanges.tilt.max = M_PI_2;
+ mOrientedRanges.tilt.flat = 0;
+ mOrientedRanges.tilt.fuzz = 0;
+ mOrientedRanges.tilt.resolution = 0;
+ }
+
+ // Orientation
+ mOrientationScale = 0;
+ if (mHaveTilt) {
+ mOrientedRanges.haveOrientation = true;
+
+ mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+ mOrientedRanges.orientation.source = mSource;
+ mOrientedRanges.orientation.min = -M_PI;
+ mOrientedRanges.orientation.max = M_PI;
+ mOrientedRanges.orientation.flat = 0;
+ mOrientedRanges.orientation.fuzz = 0;
+ mOrientedRanges.orientation.resolution = 0;
+ } else if (mCalibration.orientationCalibration !=
+ Calibration::ORIENTATION_CALIBRATION_NONE) {
+ if (mCalibration.orientationCalibration
+ == Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) {
+ if (mRawPointerAxes.orientation.valid) {
+ if (mRawPointerAxes.orientation.maxValue > 0) {
+ mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue;
+ } else if (mRawPointerAxes.orientation.minValue < 0) {
+ mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue;
+ } else {
+ mOrientationScale = 0;
+ }
+ }
+ }
+
+ mOrientedRanges.haveOrientation = true;
+
+ mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+ mOrientedRanges.orientation.source = mSource;
+ mOrientedRanges.orientation.min = -M_PI_2;
+ mOrientedRanges.orientation.max = M_PI_2;
+ mOrientedRanges.orientation.flat = 0;
+ mOrientedRanges.orientation.fuzz = 0;
+ mOrientedRanges.orientation.resolution = 0;
+ }
+
+ // Distance
+ mDistanceScale = 0;
+ if (mCalibration.distanceCalibration != Calibration::DISTANCE_CALIBRATION_NONE) {
+ if (mCalibration.distanceCalibration
+ == Calibration::DISTANCE_CALIBRATION_SCALED) {
+ if (mCalibration.haveDistanceScale) {
+ mDistanceScale = mCalibration.distanceScale;
+ } else {
+ mDistanceScale = 1.0f;
+ }
+ }
+
+ mOrientedRanges.haveDistance = true;
+
+ mOrientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE;
+ mOrientedRanges.distance.source = mSource;
+ mOrientedRanges.distance.min =
+ mRawPointerAxes.distance.minValue * mDistanceScale;
+ mOrientedRanges.distance.max =
+ mRawPointerAxes.distance.maxValue * mDistanceScale;
+ mOrientedRanges.distance.flat = 0;
+ mOrientedRanges.distance.fuzz =
+ mRawPointerAxes.distance.fuzz * mDistanceScale;
+ mOrientedRanges.distance.resolution = 0;
+ }
+
+ // Compute oriented precision, scales and ranges.
+ // Note that the maximum value reported is an inclusive maximum value so it is one
+ // unit less than the total width or height of surface.
+ switch (mSurfaceOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ case DISPLAY_ORIENTATION_270:
+ mOrientedXPrecision = mYPrecision;
+ mOrientedYPrecision = mXPrecision;
+
+ mOrientedRanges.x.min = mYTranslate;
+ mOrientedRanges.x.max = mSurfaceHeight + mYTranslate - 1;
+ mOrientedRanges.x.flat = 0;
+ mOrientedRanges.x.fuzz = 0;
+ mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
+
+ mOrientedRanges.y.min = mXTranslate;
+ mOrientedRanges.y.max = mSurfaceWidth + mXTranslate - 1;
+ mOrientedRanges.y.flat = 0;
+ mOrientedRanges.y.fuzz = 0;
+ mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
+ break;
+
+ default:
+ mOrientedXPrecision = mXPrecision;
+ mOrientedYPrecision = mYPrecision;
+
+ mOrientedRanges.x.min = mXTranslate;
+ mOrientedRanges.x.max = mSurfaceWidth + mXTranslate - 1;
+ mOrientedRanges.x.flat = 0;
+ mOrientedRanges.x.fuzz = 0;
+ mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
+
+ mOrientedRanges.y.min = mYTranslate;
+ mOrientedRanges.y.max = mSurfaceHeight + mYTranslate - 1;
+ mOrientedRanges.y.flat = 0;
+ mOrientedRanges.y.fuzz = 0;
+ mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
+ break;
+ }
+
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ // Compute pointer gesture detection parameters.
+ float rawDiagonal = hypotf(rawWidth, rawHeight);
+ float displayDiagonal = hypotf(mSurfaceWidth, mSurfaceHeight);
+
+ // Scale movements such that one whole swipe of the touch pad covers a
+ // given area relative to the diagonal size of the display when no acceleration
+ // is applied.
+ // Assume that the touch pad has a square aspect ratio such that movements in
+ // X and Y of the same number of raw units cover the same physical distance.
+ mPointerXMovementScale = mConfig.pointerGestureMovementSpeedRatio
+ * displayDiagonal / rawDiagonal;
+ mPointerYMovementScale = mPointerXMovementScale;
+
+ // Scale zooms to cover a smaller range of the display than movements do.
+ // This value determines the area around the pointer that is affected by freeform
+ // pointer gestures.
+ mPointerXZoomScale = mConfig.pointerGestureZoomSpeedRatio
+ * displayDiagonal / rawDiagonal;
+ mPointerYZoomScale = mPointerXZoomScale;
+
+ // Max width between pointers to detect a swipe gesture is more than some fraction
+ // of the diagonal axis of the touch pad. Touches that are wider than this are
+ // translated into freeform gestures.
+ mPointerGestureMaxSwipeWidth =
+ mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal;
+
+ // Abort current pointer usages because the state has changed.
+ abortPointerUsage(when, 0 /*policyFlags*/);
+ }
+
+ // Inform the dispatcher about the changes.
+ *outResetNeeded = true;
+ bumpGeneration();
+ }
+}
+
+void TouchInputMapper::dumpSurface(String8& dump) {
+ dump.appendFormat(INDENT3 "Viewport: displayId=%d, orientation=%d, "
+ "logicalFrame=[%d, %d, %d, %d], "
+ "physicalFrame=[%d, %d, %d, %d], "
+ "deviceSize=[%d, %d]\n",
+ mViewport.displayId, mViewport.orientation,
+ mViewport.logicalLeft, mViewport.logicalTop,
+ mViewport.logicalRight, mViewport.logicalBottom,
+ mViewport.physicalLeft, mViewport.physicalTop,
+ mViewport.physicalRight, mViewport.physicalBottom,
+ mViewport.deviceWidth, mViewport.deviceHeight);
+
+ dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mSurfaceWidth);
+ dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mSurfaceHeight);
+ dump.appendFormat(INDENT3 "SurfaceLeft: %d\n", mSurfaceLeft);
+ dump.appendFormat(INDENT3 "SurfaceTop: %d\n", mSurfaceTop);
+ dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mSurfaceOrientation);
+}
+
+void TouchInputMapper::configureVirtualKeys() {
+ Vector<VirtualKeyDefinition> virtualKeyDefinitions;
+ getEventHub()->getVirtualKeyDefinitions(getDeviceId(), virtualKeyDefinitions);
+
+ mVirtualKeys.clear();
+
+ if (virtualKeyDefinitions.size() == 0) {
+ return;
+ }
+
+ mVirtualKeys.setCapacity(virtualKeyDefinitions.size());
+
+ int32_t touchScreenLeft = mRawPointerAxes.x.minValue;
+ int32_t touchScreenTop = mRawPointerAxes.y.minValue;
+ int32_t touchScreenWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1;
+ int32_t touchScreenHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1;
+
+ for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
+ const VirtualKeyDefinition& virtualKeyDefinition =
+ virtualKeyDefinitions[i];
+
+ mVirtualKeys.add();
+ VirtualKey& virtualKey = mVirtualKeys.editTop();
+
+ virtualKey.scanCode = virtualKeyDefinition.scanCode;
+ int32_t keyCode;
+ uint32_t flags;
+ if (getEventHub()->mapKey(getDeviceId(), virtualKey.scanCode, 0, &keyCode, &flags)) {
+ ALOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring",
+ virtualKey.scanCode);
+ mVirtualKeys.pop(); // drop the key
+ continue;
+ }
+
+ virtualKey.keyCode = keyCode;
+ virtualKey.flags = flags;
+
+ // convert the key definition's display coordinates into touch coordinates for a hit box
+ int32_t halfWidth = virtualKeyDefinition.width / 2;
+ int32_t halfHeight = virtualKeyDefinition.height / 2;
+
+ virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth)
+ * touchScreenWidth / mSurfaceWidth + touchScreenLeft;
+ virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth)
+ * touchScreenWidth / mSurfaceWidth + touchScreenLeft;
+ virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight)
+ * touchScreenHeight / mSurfaceHeight + touchScreenTop;
+ virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
+ * touchScreenHeight / mSurfaceHeight + touchScreenTop;
+ }
+}
+
+void TouchInputMapper::dumpVirtualKeys(String8& dump) {
+ if (!mVirtualKeys.isEmpty()) {
+ dump.append(INDENT3 "Virtual Keys:\n");
+
+ for (size_t i = 0; i < mVirtualKeys.size(); i++) {
+ const VirtualKey& virtualKey = mVirtualKeys.itemAt(i);
+ dump.appendFormat(INDENT4 "%d: scanCode=%d, keyCode=%d, "
+ "hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n",
+ i, virtualKey.scanCode, virtualKey.keyCode,
+ virtualKey.hitLeft, virtualKey.hitRight,
+ virtualKey.hitTop, virtualKey.hitBottom);
+ }
+ }
+}
+
+void TouchInputMapper::parseCalibration() {
+ const PropertyMap& in = getDevice()->getConfiguration();
+ Calibration& out = mCalibration;
+
+ // Size
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_DEFAULT;
+ String8 sizeCalibrationString;
+ if (in.tryGetProperty(String8("touch.size.calibration"), sizeCalibrationString)) {
+ if (sizeCalibrationString == "none") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+ } else if (sizeCalibrationString == "geometric") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
+ } else if (sizeCalibrationString == "diameter") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_DIAMETER;
+ } else if (sizeCalibrationString == "box") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_BOX;
+ } else if (sizeCalibrationString == "area") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_AREA;
+ } else if (sizeCalibrationString != "default") {
+ ALOGW("Invalid value for touch.size.calibration: '%s'",
+ sizeCalibrationString.string());
+ }
+ }
+
+ out.haveSizeScale = in.tryGetProperty(String8("touch.size.scale"),
+ out.sizeScale);
+ out.haveSizeBias = in.tryGetProperty(String8("touch.size.bias"),
+ out.sizeBias);
+ out.haveSizeIsSummed = in.tryGetProperty(String8("touch.size.isSummed"),
+ out.sizeIsSummed);
+
+ // Pressure
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_DEFAULT;
+ String8 pressureCalibrationString;
+ if (in.tryGetProperty(String8("touch.pressure.calibration"), pressureCalibrationString)) {
+ if (pressureCalibrationString == "none") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+ } else if (pressureCalibrationString == "physical") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+ } else if (pressureCalibrationString == "amplitude") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
+ } else if (pressureCalibrationString != "default") {
+ ALOGW("Invalid value for touch.pressure.calibration: '%s'",
+ pressureCalibrationString.string());
+ }
+ }
+
+ out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"),
+ out.pressureScale);
+
+ // Orientation
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_DEFAULT;
+ String8 orientationCalibrationString;
+ if (in.tryGetProperty(String8("touch.orientation.calibration"), orientationCalibrationString)) {
+ if (orientationCalibrationString == "none") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+ } else if (orientationCalibrationString == "interpolated") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+ } else if (orientationCalibrationString == "vector") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_VECTOR;
+ } else if (orientationCalibrationString != "default") {
+ ALOGW("Invalid value for touch.orientation.calibration: '%s'",
+ orientationCalibrationString.string());
+ }
+ }
+
+ // Distance
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_DEFAULT;
+ String8 distanceCalibrationString;
+ if (in.tryGetProperty(String8("touch.distance.calibration"), distanceCalibrationString)) {
+ if (distanceCalibrationString == "none") {
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+ } else if (distanceCalibrationString == "scaled") {
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+ } else if (distanceCalibrationString != "default") {
+ ALOGW("Invalid value for touch.distance.calibration: '%s'",
+ distanceCalibrationString.string());
+ }
+ }
+
+ out.haveDistanceScale = in.tryGetProperty(String8("touch.distance.scale"),
+ out.distanceScale);
+
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_DEFAULT;
+ String8 coverageCalibrationString;
+ if (in.tryGetProperty(String8("touch.coverage.calibration"), coverageCalibrationString)) {
+ if (coverageCalibrationString == "none") {
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+ } else if (coverageCalibrationString == "box") {
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_BOX;
+ } else if (coverageCalibrationString != "default") {
+ ALOGW("Invalid value for touch.coverage.calibration: '%s'",
+ coverageCalibrationString.string());
+ }
+ }
+}
+
+void TouchInputMapper::resolveCalibration() {
+ // Size
+ if (mRawPointerAxes.touchMajor.valid || mRawPointerAxes.toolMajor.valid) {
+ if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DEFAULT) {
+ mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
+ }
+ } else {
+ mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+ }
+
+ // Pressure
+ if (mRawPointerAxes.pressure.valid) {
+ if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_DEFAULT) {
+ mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+ }
+ } else {
+ mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+ }
+
+ // Orientation
+ if (mRawPointerAxes.orientation.valid) {
+ if (mCalibration.orientationCalibration == Calibration::ORIENTATION_CALIBRATION_DEFAULT) {
+ mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+ }
+ } else {
+ mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+ }
+
+ // Distance
+ if (mRawPointerAxes.distance.valid) {
+ if (mCalibration.distanceCalibration == Calibration::DISTANCE_CALIBRATION_DEFAULT) {
+ mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+ }
+ } else {
+ mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+ }
+
+ // Coverage
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_DEFAULT) {
+ mCalibration.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+ }
+}
+
+void TouchInputMapper::dumpCalibration(String8& dump) {
+ dump.append(INDENT3 "Calibration:\n");
+
+ // Size
+ switch (mCalibration.sizeCalibration) {
+ case Calibration::SIZE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.size.calibration: none\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_GEOMETRIC:
+ dump.append(INDENT4 "touch.size.calibration: geometric\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_DIAMETER:
+ dump.append(INDENT4 "touch.size.calibration: diameter\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_BOX:
+ dump.append(INDENT4 "touch.size.calibration: box\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_AREA:
+ dump.append(INDENT4 "touch.size.calibration: area\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ if (mCalibration.haveSizeScale) {
+ dump.appendFormat(INDENT4 "touch.size.scale: %0.3f\n",
+ mCalibration.sizeScale);
+ }
+
+ if (mCalibration.haveSizeBias) {
+ dump.appendFormat(INDENT4 "touch.size.bias: %0.3f\n",
+ mCalibration.sizeBias);
+ }
+
+ if (mCalibration.haveSizeIsSummed) {
+ dump.appendFormat(INDENT4 "touch.size.isSummed: %s\n",
+ toString(mCalibration.sizeIsSummed));
+ }
+
+ // Pressure
+ switch (mCalibration.pressureCalibration) {
+ case Calibration::PRESSURE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.pressure.calibration: none\n");
+ break;
+ case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+ dump.append(INDENT4 "touch.pressure.calibration: physical\n");
+ break;
+ case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+ dump.append(INDENT4 "touch.pressure.calibration: amplitude\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ if (mCalibration.havePressureScale) {
+ dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n",
+ mCalibration.pressureScale);
+ }
+
+ // Orientation
+ switch (mCalibration.orientationCalibration) {
+ case Calibration::ORIENTATION_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.orientation.calibration: none\n");
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+ dump.append(INDENT4 "touch.orientation.calibration: interpolated\n");
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_VECTOR:
+ dump.append(INDENT4 "touch.orientation.calibration: vector\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ // Distance
+ switch (mCalibration.distanceCalibration) {
+ case Calibration::DISTANCE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.distance.calibration: none\n");
+ break;
+ case Calibration::DISTANCE_CALIBRATION_SCALED:
+ dump.append(INDENT4 "touch.distance.calibration: scaled\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ if (mCalibration.haveDistanceScale) {
+ dump.appendFormat(INDENT4 "touch.distance.scale: %0.3f\n",
+ mCalibration.distanceScale);
+ }
+
+ switch (mCalibration.coverageCalibration) {
+ case Calibration::COVERAGE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.coverage.calibration: none\n");
+ break;
+ case Calibration::COVERAGE_CALIBRATION_BOX:
+ dump.append(INDENT4 "touch.coverage.calibration: box\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+}
+
+void TouchInputMapper::reset(nsecs_t when) {
+ mCursorButtonAccumulator.reset(getDevice());
+ mCursorScrollAccumulator.reset(getDevice());
+ mTouchButtonAccumulator.reset(getDevice());
+
+ mPointerVelocityControl.reset();
+ mWheelXVelocityControl.reset();
+ mWheelYVelocityControl.reset();
+
+ mCurrentRawPointerData.clear();
+ mLastRawPointerData.clear();
+ mCurrentCookedPointerData.clear();
+ mLastCookedPointerData.clear();
+ mCurrentButtonState = 0;
+ mLastButtonState = 0;
+ mCurrentRawVScroll = 0;
+ mCurrentRawHScroll = 0;
+ mCurrentFingerIdBits.clear();
+ mLastFingerIdBits.clear();
+ mCurrentStylusIdBits.clear();
+ mLastStylusIdBits.clear();
+ mCurrentMouseIdBits.clear();
+ mLastMouseIdBits.clear();
+ mPointerUsage = POINTER_USAGE_NONE;
+ mSentHoverEnter = false;
+ mDownTime = 0;
+
+ mCurrentVirtualKey.down = false;
+
+ mPointerGesture.reset();
+ mPointerSimple.reset();
+
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ mPointerController->clearSpots();
+ }
+
+ InputMapper::reset(when);
+}
+
+void TouchInputMapper::process(const RawEvent* rawEvent) {
+ mCursorButtonAccumulator.process(rawEvent);
+ mCursorScrollAccumulator.process(rawEvent);
+ mTouchButtonAccumulator.process(rawEvent);
+
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ sync(rawEvent->when);
+ }
+}
+
+void TouchInputMapper::sync(nsecs_t when) {
+ // Sync button state.
+ mCurrentButtonState = mTouchButtonAccumulator.getButtonState()
+ | mCursorButtonAccumulator.getButtonState();
+
+ // Sync scroll state.
+ mCurrentRawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
+ mCurrentRawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
+ mCursorScrollAccumulator.finishSync();
+
+ // Sync touch state.
+ bool havePointerIds = true;
+ mCurrentRawPointerData.clear();
+ syncTouch(when, &havePointerIds);
+
+#if DEBUG_RAW_EVENTS
+ if (!havePointerIds) {
+ ALOGD("syncTouch: pointerCount %d -> %d, no pointer ids",
+ mLastRawPointerData.pointerCount,
+ mCurrentRawPointerData.pointerCount);
+ } else {
+ ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
+ "hovering ids 0x%08x -> 0x%08x",
+ mLastRawPointerData.pointerCount,
+ mCurrentRawPointerData.pointerCount,
+ mLastRawPointerData.touchingIdBits.value,
+ mCurrentRawPointerData.touchingIdBits.value,
+ mLastRawPointerData.hoveringIdBits.value,
+ mCurrentRawPointerData.hoveringIdBits.value);
+ }
+#endif
+
+ // Reset state that we will compute below.
+ mCurrentFingerIdBits.clear();
+ mCurrentStylusIdBits.clear();
+ mCurrentMouseIdBits.clear();
+ mCurrentCookedPointerData.clear();
+
+ if (mDeviceMode == DEVICE_MODE_DISABLED) {
+ // Drop all input if the device is disabled.
+ mCurrentRawPointerData.clear();
+ mCurrentButtonState = 0;
+ } else {
+ // Preprocess pointer data.
+ if (!havePointerIds) {
+ assignPointerIds();
+ }
+
+ // Handle policy on initial down or hover events.
+ uint32_t policyFlags = 0;
+ bool initialDown = mLastRawPointerData.pointerCount == 0
+ && mCurrentRawPointerData.pointerCount != 0;
+ bool buttonsPressed = mCurrentButtonState & ~mLastButtonState;
+ if (initialDown || buttonsPressed) {
+ // If this is a touch screen, hide the pointer on an initial down.
+ if (mDeviceMode == DEVICE_MODE_DIRECT) {
+ getContext()->fadePointer();
+ }
+
+ // Initial downs on external touch devices should wake the device.
+ // We don't do this for internal touch screens to prevent them from waking
+ // up in your pocket.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ if (getDevice()->isExternal()) {
+ policyFlags |= POLICY_FLAG_WAKE_DROPPED;
+ }
+ }
+
+ // Synthesize key down from raw buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
+ policyFlags, mLastButtonState, mCurrentButtonState);
+
+ // Consume raw off-screen touches before cooking pointer data.
+ // If touches are consumed, subsequent code will not receive any pointer data.
+ if (consumeRawTouches(when, policyFlags)) {
+ mCurrentRawPointerData.clear();
+ }
+
+ // Cook pointer data. This call populates the mCurrentCookedPointerData structure
+ // with cooked pointer data that has the same ids and indices as the raw data.
+ // The following code can use either the raw or cooked data, as needed.
+ cookPointerData();
+
+ // Dispatch the touches either directly or by translation through a pointer on screen.
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ for (BitSet32 idBits(mCurrentRawPointerData.touchingIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS
+ || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+ mCurrentStylusIdBits.markBit(id);
+ } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
+ || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ mCurrentFingerIdBits.markBit(id);
+ } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) {
+ mCurrentMouseIdBits.markBit(id);
+ }
+ }
+ for (BitSet32 idBits(mCurrentRawPointerData.hoveringIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS
+ || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+ mCurrentStylusIdBits.markBit(id);
+ }
+ }
+
+ // Stylus takes precedence over all tools, then mouse, then finger.
+ PointerUsage pointerUsage = mPointerUsage;
+ if (!mCurrentStylusIdBits.isEmpty()) {
+ mCurrentMouseIdBits.clear();
+ mCurrentFingerIdBits.clear();
+ pointerUsage = POINTER_USAGE_STYLUS;
+ } else if (!mCurrentMouseIdBits.isEmpty()) {
+ mCurrentFingerIdBits.clear();
+ pointerUsage = POINTER_USAGE_MOUSE;
+ } else if (!mCurrentFingerIdBits.isEmpty() || isPointerDown(mCurrentButtonState)) {
+ pointerUsage = POINTER_USAGE_GESTURES;
+ }
+
+ dispatchPointerUsage(when, policyFlags, pointerUsage);
+ } else {
+ if (mDeviceMode == DEVICE_MODE_DIRECT
+ && mConfig.showTouches && mPointerController != NULL) {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+
+ mPointerController->setButtonState(mCurrentButtonState);
+ mPointerController->setSpots(mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mCurrentCookedPointerData.touchingIdBits);
+ }
+
+ dispatchHoverExit(when, policyFlags);
+ dispatchTouches(when, policyFlags);
+ dispatchHoverEnterAndMove(when, policyFlags);
+ }
+
+ // Synthesize key up from raw buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+ policyFlags, mLastButtonState, mCurrentButtonState);
+ }
+
+ // Copy current touch to last touch in preparation for the next cycle.
+ mLastRawPointerData.copyFrom(mCurrentRawPointerData);
+ mLastCookedPointerData.copyFrom(mCurrentCookedPointerData);
+ mLastButtonState = mCurrentButtonState;
+ mLastFingerIdBits = mCurrentFingerIdBits;
+ mLastStylusIdBits = mCurrentStylusIdBits;
+ mLastMouseIdBits = mCurrentMouseIdBits;
+
+ // Clear some transient state.
+ mCurrentRawVScroll = 0;
+ mCurrentRawHScroll = 0;
+}
+
+void TouchInputMapper::timeoutExpired(nsecs_t when) {
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ if (mPointerUsage == POINTER_USAGE_GESTURES) {
+ dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/);
+ }
+ }
+}
+
+bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags) {
+ // Check for release of a virtual key.
+ if (mCurrentVirtualKey.down) {
+ if (mCurrentRawPointerData.touchingIdBits.isEmpty()) {
+ // Pointer went up while virtual key was down.
+ mCurrentVirtualKey.down = false;
+ if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
+ mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+ dispatchVirtualKey(when, policyFlags,
+ AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+ }
+ return true;
+ }
+
+ if (mCurrentRawPointerData.touchingIdBits.count() == 1) {
+ uint32_t id = mCurrentRawPointerData.touchingIdBits.firstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
+ if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) {
+ // Pointer is still within the space of the virtual key.
+ return true;
+ }
+ }
+
+ // Pointer left virtual key area or another pointer also went down.
+ // Send key cancellation but do not consume the touch yet.
+ // This is useful when the user swipes through from the virtual key area
+ // into the main display surface.
+ mCurrentVirtualKey.down = false;
+ if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
+ mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+ dispatchVirtualKey(when, policyFlags,
+ AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+ | AKEY_EVENT_FLAG_CANCELED);
+ }
+ }
+
+ if (mLastRawPointerData.touchingIdBits.isEmpty()
+ && !mCurrentRawPointerData.touchingIdBits.isEmpty()) {
+ // Pointer just went down. Check for virtual key press or off-screen touches.
+ uint32_t id = mCurrentRawPointerData.touchingIdBits.firstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ if (!isPointInsideSurface(pointer.x, pointer.y)) {
+ // If exactly one pointer went down, check for virtual key hit.
+ // Otherwise we will drop the entire stroke.
+ if (mCurrentRawPointerData.touchingIdBits.count() == 1) {
+ const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
+ if (virtualKey) {
+ mCurrentVirtualKey.down = true;
+ mCurrentVirtualKey.downTime = when;
+ mCurrentVirtualKey.keyCode = virtualKey->keyCode;
+ mCurrentVirtualKey.scanCode = virtualKey->scanCode;
+ mCurrentVirtualKey.ignored = mContext->shouldDropVirtualKey(
+ when, getDevice(), virtualKey->keyCode, virtualKey->scanCode);
+
+ if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
+ mCurrentVirtualKey.keyCode,
+ mCurrentVirtualKey.scanCode);
+#endif
+ dispatchVirtualKey(when, policyFlags,
+ AKEY_EVENT_ACTION_DOWN,
+ AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ // Disable all virtual key touches that happen within a short time interval of the
+ // most recent touch within the screen area. The idea is to filter out stray
+ // virtual key presses when interacting with the touch screen.
+ //
+ // Problems we're trying to solve:
+ //
+ // 1. While scrolling a list or dragging the window shade, the user swipes down into a
+ // virtual key area that is implemented by a separate touch panel and accidentally
+ // triggers a virtual key.
+ //
+ // 2. While typing in the on screen keyboard, the user taps slightly outside the screen
+ // area and accidentally triggers a virtual key. This often happens when virtual keys
+ // are layed out below the screen near to where the on screen keyboard's space bar
+ // is displayed.
+ if (mConfig.virtualKeyQuietTime > 0 && !mCurrentRawPointerData.touchingIdBits.isEmpty()) {
+ mContext->disableVirtualKeysUntil(when + mConfig.virtualKeyQuietTime);
+ }
+ return false;
+}
+
+void TouchInputMapper::dispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
+ int32_t keyEventAction, int32_t keyEventFlags) {
+ int32_t keyCode = mCurrentVirtualKey.keyCode;
+ int32_t scanCode = mCurrentVirtualKey.scanCode;
+ nsecs_t downTime = mCurrentVirtualKey.downTime;
+ int32_t metaState = mContext->getGlobalMetaState();
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+
+ NotifyKeyArgs args(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
+ keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
+ getListener()->notifyKey(&args);
+}
+
+void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
+ BitSet32 currentIdBits = mCurrentCookedPointerData.touchingIdBits;
+ BitSet32 lastIdBits = mLastCookedPointerData.touchingIdBits;
+ int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mCurrentButtonState;
+
+ if (currentIdBits == lastIdBits) {
+ if (!currentIdBits.isEmpty()) {
+ // No pointer id changes so this is a move event.
+ // The listener takes care of batching moves so we don't have to deal with that here.
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ currentIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+ } else {
+ // There may be pointers going up and pointers going down and pointers moving
+ // all at the same time.
+ BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value);
+ BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value);
+ BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
+ BitSet32 dispatchedIdBits(lastIdBits.value);
+
+ // Update last coordinates of pointers that have moved so that we observe the new
+ // pointer positions at the same time as other pointers that have just gone up.
+ bool moveNeeded = updateMovedPointers(
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mLastCookedPointerData.pointerProperties,
+ mLastCookedPointerData.pointerCoords,
+ mLastCookedPointerData.idToIndex,
+ moveIdBits);
+ if (buttonState != mLastButtonState) {
+ moveNeeded = true;
+ }
+
+ // Dispatch pointer up events.
+ while (!upIdBits.isEmpty()) {
+ uint32_t upId = upIdBits.clearFirstMarkedBit();
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_UP, 0, metaState, buttonState, 0,
+ mLastCookedPointerData.pointerProperties,
+ mLastCookedPointerData.pointerCoords,
+ mLastCookedPointerData.idToIndex,
+ dispatchedIdBits, upId,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ dispatchedIdBits.clearBit(upId);
+ }
+
+ // Dispatch move events if any of the remaining pointers moved from their old locations.
+ // Although applications receive new locations as part of individual pointer up
+ // events, they do not generally handle them except when presented in a move event.
+ if (moveNeeded) {
+ ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ dispatchedIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+
+ // Dispatch pointer down events using the new pointer locations.
+ while (!downIdBits.isEmpty()) {
+ uint32_t downId = downIdBits.clearFirstMarkedBit();
+ dispatchedIdBits.markBit(downId);
+
+ if (dispatchedIdBits.count() == 1) {
+ // First pointer is going down. Set down time.
+ mDownTime = when;
+ }
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, buttonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ dispatchedIdBits, downId,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+ }
+}
+
+void TouchInputMapper::dispatchHoverExit(nsecs_t when, uint32_t policyFlags) {
+ if (mSentHoverEnter &&
+ (mCurrentCookedPointerData.hoveringIdBits.isEmpty()
+ || !mCurrentCookedPointerData.touchingIdBits.isEmpty())) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_EXIT, 0, metaState, mLastButtonState, 0,
+ mLastCookedPointerData.pointerProperties,
+ mLastCookedPointerData.pointerCoords,
+ mLastCookedPointerData.idToIndex,
+ mLastCookedPointerData.hoveringIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ mSentHoverEnter = false;
+ }
+}
+
+void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags) {
+ if (mCurrentCookedPointerData.touchingIdBits.isEmpty()
+ && !mCurrentCookedPointerData.hoveringIdBits.isEmpty()) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ if (!mSentHoverEnter) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_ENTER, 0, metaState, mCurrentButtonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mCurrentCookedPointerData.hoveringIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ mSentHoverEnter = true;
+ }
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, mCurrentButtonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mCurrentCookedPointerData.hoveringIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+}
+
+void TouchInputMapper::cookPointerData() {
+ uint32_t currentPointerCount = mCurrentRawPointerData.pointerCount;
+
+ mCurrentCookedPointerData.clear();
+ mCurrentCookedPointerData.pointerCount = currentPointerCount;
+ mCurrentCookedPointerData.hoveringIdBits = mCurrentRawPointerData.hoveringIdBits;
+ mCurrentCookedPointerData.touchingIdBits = mCurrentRawPointerData.touchingIdBits;
+
+ // Walk through the the active pointers and map device coordinates onto
+ // surface coordinates and adjust for display orientation.
+ for (uint32_t i = 0; i < currentPointerCount; i++) {
+ const RawPointerData::Pointer& in = mCurrentRawPointerData.pointers[i];
+
+ // Size
+ float touchMajor, touchMinor, toolMajor, toolMinor, size;
+ switch (mCalibration.sizeCalibration) {
+ case Calibration::SIZE_CALIBRATION_GEOMETRIC:
+ case Calibration::SIZE_CALIBRATION_DIAMETER:
+ case Calibration::SIZE_CALIBRATION_BOX:
+ case Calibration::SIZE_CALIBRATION_AREA:
+ if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.toolMajor.valid) {
+ touchMajor = in.touchMajor;
+ touchMinor = mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
+ toolMajor = in.toolMajor;
+ toolMinor = mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
+ size = mRawPointerAxes.touchMinor.valid
+ ? avg(in.touchMajor, in.touchMinor) : in.touchMajor;
+ } else if (mRawPointerAxes.touchMajor.valid) {
+ toolMajor = touchMajor = in.touchMajor;
+ toolMinor = touchMinor = mRawPointerAxes.touchMinor.valid
+ ? in.touchMinor : in.touchMajor;
+ size = mRawPointerAxes.touchMinor.valid
+ ? avg(in.touchMajor, in.touchMinor) : in.touchMajor;
+ } else if (mRawPointerAxes.toolMajor.valid) {
+ touchMajor = toolMajor = in.toolMajor;
+ touchMinor = toolMinor = mRawPointerAxes.toolMinor.valid
+ ? in.toolMinor : in.toolMajor;
+ size = mRawPointerAxes.toolMinor.valid
+ ? avg(in.toolMajor, in.toolMinor) : in.toolMajor;
+ } else {
+ ALOG_ASSERT(false, "No touch or tool axes. "
+ "Size calibration should have been resolved to NONE.");
+ touchMajor = 0;
+ touchMinor = 0;
+ toolMajor = 0;
+ toolMinor = 0;
+ size = 0;
+ }
+
+ if (mCalibration.haveSizeIsSummed && mCalibration.sizeIsSummed) {
+ uint32_t touchingCount = mCurrentRawPointerData.touchingIdBits.count();
+ if (touchingCount > 1) {
+ touchMajor /= touchingCount;
+ touchMinor /= touchingCount;
+ toolMajor /= touchingCount;
+ toolMinor /= touchingCount;
+ size /= touchingCount;
+ }
+ }
+
+ if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_GEOMETRIC) {
+ touchMajor *= mGeometricScale;
+ touchMinor *= mGeometricScale;
+ toolMajor *= mGeometricScale;
+ toolMinor *= mGeometricScale;
+ } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_AREA) {
+ touchMajor = touchMajor > 0 ? sqrtf(touchMajor) : 0;
+ touchMinor = touchMajor;
+ toolMajor = toolMajor > 0 ? sqrtf(toolMajor) : 0;
+ toolMinor = toolMajor;
+ } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DIAMETER) {
+ touchMinor = touchMajor;
+ toolMinor = toolMajor;
+ }
+
+ mCalibration.applySizeScaleAndBias(&touchMajor);
+ mCalibration.applySizeScaleAndBias(&touchMinor);
+ mCalibration.applySizeScaleAndBias(&toolMajor);
+ mCalibration.applySizeScaleAndBias(&toolMinor);
+ size *= mSizeScale;
+ break;
+ default:
+ touchMajor = 0;
+ touchMinor = 0;
+ toolMajor = 0;
+ toolMinor = 0;
+ size = 0;
+ break;
+ }
+
+ // Pressure
+ float pressure;
+ switch (mCalibration.pressureCalibration) {
+ case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+ case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+ pressure = in.pressure * mPressureScale;
+ break;
+ default:
+ pressure = in.isHovering ? 0 : 1;
+ break;
+ }
+
+ // Tilt and Orientation
+ float tilt;
+ float orientation;
+ if (mHaveTilt) {
+ float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale;
+ float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale;
+ orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle));
+ tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
+ } else {
+ tilt = 0;
+
+ switch (mCalibration.orientationCalibration) {
+ case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+ orientation = in.orientation * mOrientationScale;
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_VECTOR: {
+ int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
+ int32_t c2 = signExtendNybble(in.orientation & 0x0f);
+ if (c1 != 0 || c2 != 0) {
+ orientation = atan2f(c1, c2) * 0.5f;
+ float confidence = hypotf(c1, c2);
+ float scale = 1.0f + confidence / 16.0f;
+ touchMajor *= scale;
+ touchMinor /= scale;
+ toolMajor *= scale;
+ toolMinor /= scale;
+ } else {
+ orientation = 0;
+ }
+ break;
+ }
+ default:
+ orientation = 0;
+ }
+ }
+
+ // Distance
+ float distance;
+ switch (mCalibration.distanceCalibration) {
+ case Calibration::DISTANCE_CALIBRATION_SCALED:
+ distance = in.distance * mDistanceScale;
+ break;
+ default:
+ distance = 0;
+ }
+
+ // Coverage
+ int32_t rawLeft, rawTop, rawRight, rawBottom;
+ switch (mCalibration.coverageCalibration) {
+ case Calibration::COVERAGE_CALIBRATION_BOX:
+ rawLeft = (in.toolMinor & 0xffff0000) >> 16;
+ rawRight = in.toolMinor & 0x0000ffff;
+ rawBottom = in.toolMajor & 0x0000ffff;
+ rawTop = (in.toolMajor & 0xffff0000) >> 16;
+ break;
+ default:
+ rawLeft = rawTop = rawRight = rawBottom = 0;
+ break;
+ }
+
+ // X, Y, and the bounding box for coverage information
+ // Adjust coords for surface orientation.
+ float x, y, left, top, right, bottom;
+ switch (mSurfaceOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ x = float(in.y - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ y = float(mRawPointerAxes.x.maxValue - in.x) * mXScale + mXTranslate;
+ left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ right = float(rawBottom- mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
+ top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
+ orientation -= M_PI_2;
+ if (orientation < - M_PI_2) {
+ orientation += M_PI;
+ }
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = float(mRawPointerAxes.x.maxValue - in.x) * mXScale + mXTranslate;
+ y = float(mRawPointerAxes.y.maxValue - in.y) * mYScale + mYTranslate;
+ left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
+ right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
+ bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
+ top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ x = float(mRawPointerAxes.y.maxValue - in.y) * mYScale + mYTranslate;
+ y = float(in.x - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
+ right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
+ bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ orientation += M_PI_2;
+ if (orientation > M_PI_2) {
+ orientation -= M_PI;
+ }
+ break;
+ default:
+ x = float(in.x - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ y = float(in.y - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ break;
+ }
+
+ // Write output coords.
+ PointerCoords& out = mCurrentCookedPointerData.pointerCoords[i];
+ out.clear();
+ out.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ out.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+ out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt);
+ out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom);
+ } else {
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
+ }
+
+ // Write output properties.
+ PointerProperties& properties = mCurrentCookedPointerData.pointerProperties[i];
+ uint32_t id = in.id;
+ properties.clear();
+ properties.id = id;
+ properties.toolType = in.toolType;
+
+ // Write id index.
+ mCurrentCookedPointerData.idToIndex[id] = i;
+ }
+}
+
+void TouchInputMapper::dispatchPointerUsage(nsecs_t when, uint32_t policyFlags,
+ PointerUsage pointerUsage) {
+ if (pointerUsage != mPointerUsage) {
+ abortPointerUsage(when, policyFlags);
+ mPointerUsage = pointerUsage;
+ }
+
+ switch (mPointerUsage) {
+ case POINTER_USAGE_GESTURES:
+ dispatchPointerGestures(when, policyFlags, false /*isTimeout*/);
+ break;
+ case POINTER_USAGE_STYLUS:
+ dispatchPointerStylus(when, policyFlags);
+ break;
+ case POINTER_USAGE_MOUSE:
+ dispatchPointerMouse(when, policyFlags);
+ break;
+ default:
+ break;
+ }
+}
+
+void TouchInputMapper::abortPointerUsage(nsecs_t when, uint32_t policyFlags) {
+ switch (mPointerUsage) {
+ case POINTER_USAGE_GESTURES:
+ abortPointerGestures(when, policyFlags);
+ break;
+ case POINTER_USAGE_STYLUS:
+ abortPointerStylus(when, policyFlags);
+ break;
+ case POINTER_USAGE_MOUSE:
+ abortPointerMouse(when, policyFlags);
+ break;
+ default:
+ break;
+ }
+
+ mPointerUsage = POINTER_USAGE_NONE;
+}
+
+void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags,
+ bool isTimeout) {
+ // Update current gesture coordinates.
+ bool cancelPreviousGesture, finishPreviousGesture;
+ bool sendEvents = preparePointerGestures(when,
+ &cancelPreviousGesture, &finishPreviousGesture, isTimeout);
+ if (!sendEvents) {
+ return;
+ }
+ if (finishPreviousGesture) {
+ cancelPreviousGesture = false;
+ }
+
+ // Update the pointer presentation and spots.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);
+ if (finishPreviousGesture || cancelPreviousGesture) {
+ mPointerController->clearSpots();
+ }
+ mPointerController->setSpots(mPointerGesture.currentGestureCoords,
+ mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.currentGestureIdBits);
+ } else {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+ }
+
+ // Show or hide the pointer if needed.
+ switch (mPointerGesture.currentGestureMode) {
+ case PointerGesture::NEUTRAL:
+ case PointerGesture::QUIET:
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+ && (mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)) {
+ // Remind the user of where the pointer is after finishing a gesture with spots.
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+ break;
+ case PointerGesture::TAP:
+ case PointerGesture::TAP_DRAG:
+ case PointerGesture::BUTTON_CLICK_OR_DRAG:
+ case PointerGesture::HOVER:
+ case PointerGesture::PRESS:
+ // Unfade the pointer when the current gesture manipulates the
+ // area directly under the pointer.
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ break;
+ case PointerGesture::SWIPE:
+ case PointerGesture::FREEFORM:
+ // Fade the pointer when the current gesture manipulates a different
+ // area and there are spots to guide the user experience.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ } else {
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ }
+ break;
+ }
+
+ // Send events!
+ int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mCurrentButtonState;
+
+ // Update last coordinates of pointers that have moved so that we observe the new
+ // pointer positions at the same time as other pointers that have just gone up.
+ bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP
+ || mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG
+ || mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG
+ || mPointerGesture.currentGestureMode == PointerGesture::PRESS
+ || mPointerGesture.currentGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.currentGestureMode == PointerGesture::FREEFORM;
+ bool moveNeeded = false;
+ if (down && !cancelPreviousGesture && !finishPreviousGesture
+ && !mPointerGesture.lastGestureIdBits.isEmpty()
+ && !mPointerGesture.currentGestureIdBits.isEmpty()) {
+ BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value
+ & mPointerGesture.lastGestureIdBits.value);
+ moveNeeded = updateMovedPointers(mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ movedGestureIdBits);
+ if (buttonState != mLastButtonState) {
+ moveNeeded = true;
+ }
+ }
+
+ // Send motion events for all pointers that went up or were canceled.
+ BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
+ if (!dispatchedGestureIdBits.isEmpty()) {
+ if (cancelPreviousGesture) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_CANCEL, 0, metaState, buttonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ dispatchedGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+
+ dispatchedGestureIdBits.clear();
+ } else {
+ BitSet32 upGestureIdBits;
+ if (finishPreviousGesture) {
+ upGestureIdBits = dispatchedGestureIdBits;
+ } else {
+ upGestureIdBits.value = dispatchedGestureIdBits.value
+ & ~mPointerGesture.currentGestureIdBits.value;
+ }
+ while (!upGestureIdBits.isEmpty()) {
+ uint32_t id = upGestureIdBits.clearFirstMarkedBit();
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_UP, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ dispatchedGestureIdBits, id,
+ 0, 0, mPointerGesture.downTime);
+
+ dispatchedGestureIdBits.clearBit(id);
+ }
+ }
+ }
+
+ // Send motion events for all pointers that moved.
+ if (moveNeeded) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ dispatchedGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+ }
+
+ // Send motion events for all pointers that went down.
+ if (down) {
+ BitSet32 downGestureIdBits(mPointerGesture.currentGestureIdBits.value
+ & ~dispatchedGestureIdBits.value);
+ while (!downGestureIdBits.isEmpty()) {
+ uint32_t id = downGestureIdBits.clearFirstMarkedBit();
+ dispatchedGestureIdBits.markBit(id);
+
+ if (dispatchedGestureIdBits.count() == 1) {
+ mPointerGesture.downTime = when;
+ }
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, buttonState, 0,
+ mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ dispatchedGestureIdBits, id,
+ 0, 0, mPointerGesture.downTime);
+ }
+ }
+
+ // Send motion events for hover.
+ if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.currentGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+ } else if (dispatchedGestureIdBits.isEmpty()
+ && !mPointerGesture.lastGestureIdBits.isEmpty()) {
+ // Synthesize a hover move event after all pointers go up to indicate that
+ // the pointer is hovering again even if the user is not currently touching
+ // the touch pad. This ensures that a view will receive a fresh hover enter
+ // event after a tap.
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mViewport.displayId, 1, &pointerProperties, &pointerCoords,
+ 0, 0, mPointerGesture.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Update state.
+ mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode;
+ if (!down) {
+ mPointerGesture.lastGestureIdBits.clear();
+ } else {
+ mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits;
+ for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
+ mPointerGesture.lastGestureProperties[index].copyFrom(
+ mPointerGesture.currentGestureProperties[index]);
+ mPointerGesture.lastGestureCoords[index].copyFrom(
+ mPointerGesture.currentGestureCoords[index]);
+ mPointerGesture.lastGestureIdToIndex[id] = index;
+ }
+ }
+}
+
+void TouchInputMapper::abortPointerGestures(nsecs_t when, uint32_t policyFlags) {
+ // Cancel previously dispatches pointers.
+ if (!mPointerGesture.lastGestureIdBits.isEmpty()) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mCurrentButtonState;
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_CANCEL, 0, metaState, buttonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ mPointerGesture.lastGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+ }
+
+ // Reset the current pointer gesture.
+ mPointerGesture.reset();
+ mPointerVelocityControl.reset();
+
+ // Remove any current spots.
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ mPointerController->clearSpots();
+ }
+}
+
+bool TouchInputMapper::preparePointerGestures(nsecs_t when,
+ bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout) {
+ *outCancelPreviousGesture = false;
+ *outFinishPreviousGesture = false;
+
+ // Handle TAP timeout.
+ if (isTimeout) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Processing timeout");
+#endif
+
+ if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+ if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
+ // The tap/drag timeout has not yet expired.
+ getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime
+ + mConfig.pointerGestureTapDragInterval);
+ } else {
+ // The tap is finished.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: TAP finished");
+#endif
+ *outFinishPreviousGesture = true;
+
+ mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+ mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerVelocityControl.reset();
+ return true;
+ }
+ }
+
+ // We did not handle this timeout.
+ return false;
+ }
+
+ const uint32_t currentFingerCount = mCurrentFingerIdBits.count();
+ const uint32_t lastFingerCount = mLastFingerIdBits.count();
+
+ // Update the velocity tracker.
+ {
+ VelocityTracker::Position positions[MAX_POINTERS];
+ uint32_t count = 0;
+ for (BitSet32 idBits(mCurrentFingerIdBits); !idBits.isEmpty(); count++) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ positions[count].x = pointer.x * mPointerXMovementScale;
+ positions[count].y = pointer.y * mPointerYMovementScale;
+ }
+ mPointerGesture.velocityTracker.addMovement(when,
+ mCurrentFingerIdBits, positions);
+ }
+
+ // Pick a new active touch id if needed.
+ // Choose an arbitrary pointer that just went down, if there is one.
+ // Otherwise choose an arbitrary remaining pointer.
+ // This guarantees we always have an active touch id when there is at least one pointer.
+ // We keep the same active touch id for as long as possible.
+ int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
+ int32_t activeTouchId = lastActiveTouchId;
+ if (activeTouchId < 0) {
+ if (!mCurrentFingerIdBits.isEmpty()) {
+ activeTouchId = mPointerGesture.activeTouchId =
+ mCurrentFingerIdBits.firstMarkedBit();
+ mPointerGesture.firstTouchTime = when;
+ }
+ } else if (!mCurrentFingerIdBits.hasBit(activeTouchId)) {
+ if (!mCurrentFingerIdBits.isEmpty()) {
+ activeTouchId = mPointerGesture.activeTouchId =
+ mCurrentFingerIdBits.firstMarkedBit();
+ } else {
+ activeTouchId = mPointerGesture.activeTouchId = -1;
+ }
+ }
+
+ // Determine whether we are in quiet time.
+ bool isQuietTime = false;
+ if (activeTouchId < 0) {
+ mPointerGesture.resetQuietTime();
+ } else {
+ isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval;
+ if (!isQuietTime) {
+ if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS
+ || mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
+ && currentFingerCount < 2) {
+ // Enter quiet time when exiting swipe or freeform state.
+ // This is to prevent accidentally entering the hover state and flinging the
+ // pointer when finishing a swipe and there is still one pointer left onscreen.
+ isQuietTime = true;
+ } else if (mPointerGesture.lastGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG
+ && currentFingerCount >= 2
+ && !isPointerDown(mCurrentButtonState)) {
+ // Enter quiet time when releasing the button and there are still two or more
+ // fingers down. This may indicate that one finger was used to press the button
+ // but it has not gone up yet.
+ isQuietTime = true;
+ }
+ if (isQuietTime) {
+ mPointerGesture.quietTime = when;
+ }
+ }
+ }
+
+ // Switch states based on button and pointer state.
+ if (isQuietTime) {
+ // Case 1: Quiet time. (QUIET)
+#if DEBUG_GESTURES
+ ALOGD("Gestures: QUIET for next %0.3fms", (mPointerGesture.quietTime
+ + mConfig.pointerGestureQuietInterval - when) * 0.000001f);
+#endif
+ if (mPointerGesture.lastGestureMode != PointerGesture::QUIET) {
+ *outFinishPreviousGesture = true;
+ }
+
+ mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::QUIET;
+ mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerVelocityControl.reset();
+ } else if (isPointerDown(mCurrentButtonState)) {
+ // Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG)
+ // The pointer follows the active touch point.
+ // Emit DOWN, MOVE, UP events at the pointer location.
+ //
+ // Only the active touch matters; other fingers are ignored. This policy helps
+ // to handle the case where the user places a second finger on the touch pad
+ // to apply the necessary force to depress an integrated button below the surface.
+ // We don't want the second finger to be delivered to applications.
+ //
+ // For this to work well, we need to make sure to track the pointer that is really
+ // active. If the user first puts one finger down to click then adds another
+ // finger to drag then the active pointer should switch to the finger that is
+ // being dragged.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, "
+ "currentFingerCount=%d", activeTouchId, currentFingerCount);
+#endif
+ // Reset state when just starting.
+ if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) {
+ *outFinishPreviousGesture = true;
+ mPointerGesture.activeGestureId = 0;
+ }
+
+ // Switch pointers if needed.
+ // Find the fastest pointer and follow it.
+ if (activeTouchId >= 0 && currentFingerCount > 1) {
+ int32_t bestId = -1;
+ float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
+ for (BitSet32 idBits(mCurrentFingerIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ float vx, vy;
+ if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) {
+ float speed = hypotf(vx, vy);
+ if (speed > bestSpeed) {
+ bestId = id;
+ bestSpeed = speed;
+ }
+ }
+ }
+ if (bestId >= 0 && bestId != activeTouchId) {
+ mPointerGesture.activeTouchId = activeTouchId = bestId;
+#if DEBUG_GESTURES
+ ALOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, "
+ "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed);
+#endif
+ }
+ }
+
+ if (activeTouchId >= 0 && mLastFingerIdBits.hasBit(activeTouchId)) {
+ const RawPointerData::Pointer& currentPointer =
+ mCurrentRawPointerData.pointerForId(activeTouchId);
+ const RawPointerData::Pointer& lastPointer =
+ mLastRawPointerData.pointerForId(activeTouchId);
+ float deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale;
+ float deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ // Move the pointer using a relative motion.
+ // When using spots, the click will occur at the position of the anchor
+ // spot and all other spots will move there.
+ mPointerController->move(deltaX, deltaY);
+ } else {
+ mPointerVelocityControl.reset();
+ }
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+
+ mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG;
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ } else if (currentFingerCount == 0) {
+ // Case 3. No fingers down and button is not pressed. (NEUTRAL)
+ if (mPointerGesture.lastGestureMode != PointerGesture::NEUTRAL) {
+ *outFinishPreviousGesture = true;
+ }
+
+ // Watch for taps coming out of HOVER or TAP_DRAG mode.
+ // Checking for taps after TAP_DRAG allows us to detect double-taps.
+ bool tapped = false;
+ if ((mPointerGesture.lastGestureMode == PointerGesture::HOVER
+ || mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG)
+ && lastFingerCount == 1) {
+ if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) {
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop
+ && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: TAP");
+#endif
+
+ mPointerGesture.tapUpTime = when;
+ getContext()->requestTimeoutAtTime(when
+ + mConfig.pointerGestureTapDragInterval);
+
+ mPointerGesture.activeGestureId = 0;
+ mPointerGesture.currentGestureMode = PointerGesture::TAP;
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(
+ mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[
+ mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id =
+ mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(
+ AMOTION_EVENT_AXIS_X, mPointerGesture.tapX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(
+ AMOTION_EVENT_AXIS_Y, mPointerGesture.tapY);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(
+ AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ tapped = true;
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f",
+ x - mPointerGesture.tapX,
+ y - mPointerGesture.tapY);
+#endif
+ }
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP, %0.3fms since down",
+ (when - mPointerGesture.tapDownTime) * 0.000001f);
+#endif
+ }
+ }
+
+ mPointerVelocityControl.reset();
+
+ if (!tapped) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: NEUTRAL");
+#endif
+ mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+ mPointerGesture.currentGestureIdBits.clear();
+ }
+ } else if (currentFingerCount == 1) {
+ // Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG)
+ // The pointer follows the active touch point.
+ // When in HOVER, emit HOVER_MOVE events at the pointer location.
+ // When in TAP_DRAG, emit MOVE events at the pointer location.
+ ALOG_ASSERT(activeTouchId >= 0);
+
+ mPointerGesture.currentGestureMode = PointerGesture::HOVER;
+ if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+ if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop
+ && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+ mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
+ x - mPointerGesture.tapX,
+ y - mPointerGesture.tapY);
+#endif
+ }
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up",
+ (when - mPointerGesture.tapUpTime) * 0.000001f);
+#endif
+ }
+ } else if (mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) {
+ mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+ }
+
+ if (mLastFingerIdBits.hasBit(activeTouchId)) {
+ const RawPointerData::Pointer& currentPointer =
+ mCurrentRawPointerData.pointerForId(activeTouchId);
+ const RawPointerData::Pointer& lastPointer =
+ mLastRawPointerData.pointerForId(activeTouchId);
+ float deltaX = (currentPointer.x - lastPointer.x)
+ * mPointerXMovementScale;
+ float deltaY = (currentPointer.y - lastPointer.y)
+ * mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ // Move the pointer using a relative motion.
+ // When using spots, the hover or drag will occur at the position of the anchor spot.
+ mPointerController->move(deltaX, deltaY);
+ } else {
+ mPointerVelocityControl.reset();
+ }
+
+ bool down;
+ if (mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: TAP_DRAG");
+#endif
+ down = true;
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: HOVER");
+#endif
+ if (mPointerGesture.lastGestureMode != PointerGesture::HOVER) {
+ *outFinishPreviousGesture = true;
+ }
+ mPointerGesture.activeGestureId = 0;
+ down = false;
+ }
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+ down ? 1.0f : 0.0f);
+
+ if (lastFingerCount == 0 && currentFingerCount != 0) {
+ mPointerGesture.resetTap();
+ mPointerGesture.tapDownTime = when;
+ mPointerGesture.tapX = x;
+ mPointerGesture.tapY = y;
+ }
+ } else {
+ // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
+ // We need to provide feedback for each finger that goes down so we cannot wait
+ // for the fingers to move before deciding what to do.
+ //
+ // The ambiguous case is deciding what to do when there are two fingers down but they
+ // have not moved enough to determine whether they are part of a drag or part of a
+ // freeform gesture, or just a press or long-press at the pointer location.
+ //
+ // When there are two fingers we start with the PRESS hypothesis and we generate a
+ // down at the pointer location.
+ //
+ // When the two fingers move enough or when additional fingers are added, we make
+ // a decision to transition into SWIPE or FREEFORM mode accordingly.
+ ALOG_ASSERT(activeTouchId >= 0);
+
+ bool settled = when >= mPointerGesture.firstTouchTime
+ + mConfig.pointerGestureMultitouchSettleInterval;
+ if (mPointerGesture.lastGestureMode != PointerGesture::PRESS
+ && mPointerGesture.lastGestureMode != PointerGesture::SWIPE
+ && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
+ *outFinishPreviousGesture = true;
+ } else if (!settled && currentFingerCount > lastFingerCount) {
+ // Additional pointers have gone down but not yet settled.
+ // Reset the gesture.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, "
+ "settle time remaining %0.3fms", (mPointerGesture.firstTouchTime
+ + mConfig.pointerGestureMultitouchSettleInterval - when)
+ * 0.000001f);
+#endif
+ *outCancelPreviousGesture = true;
+ } else {
+ // Continue previous gesture.
+ mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
+ }
+
+ if (*outFinishPreviousGesture || *outCancelPreviousGesture) {
+ mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+ mPointerGesture.activeGestureId = 0;
+ mPointerGesture.referenceIdBits.clear();
+ mPointerVelocityControl.reset();
+
+ // Use the centroid and pointer location as the reference points for the gesture.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Using centroid as reference for MULTITOUCH, "
+ "settle time remaining %0.3fms", (mPointerGesture.firstTouchTime
+ + mConfig.pointerGestureMultitouchSettleInterval - when)
+ * 0.000001f);
+#endif
+ mCurrentRawPointerData.getCentroidOfTouchingPointers(
+ &mPointerGesture.referenceTouchX,
+ &mPointerGesture.referenceTouchY);
+ mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+ &mPointerGesture.referenceGestureY);
+ }
+
+ // Clear the reference deltas for fingers not yet included in the reference calculation.
+ for (BitSet32 idBits(mCurrentFingerIdBits.value
+ & ~mPointerGesture.referenceIdBits.value); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ mPointerGesture.referenceDeltas[id].dx = 0;
+ mPointerGesture.referenceDeltas[id].dy = 0;
+ }
+ mPointerGesture.referenceIdBits = mCurrentFingerIdBits;
+
+ // Add delta for all fingers and calculate a common movement delta.
+ float commonDeltaX = 0, commonDeltaY = 0;
+ BitSet32 commonIdBits(mLastFingerIdBits.value
+ & mCurrentFingerIdBits.value);
+ for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) {
+ bool first = (idBits == commonIdBits);
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& cpd = mCurrentRawPointerData.pointerForId(id);
+ const RawPointerData::Pointer& lpd = mLastRawPointerData.pointerForId(id);
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ delta.dx += cpd.x - lpd.x;
+ delta.dy += cpd.y - lpd.y;
+
+ if (first) {
+ commonDeltaX = delta.dx;
+ commonDeltaY = delta.dy;
+ } else {
+ commonDeltaX = calculateCommonVector(commonDeltaX, delta.dx);
+ commonDeltaY = calculateCommonVector(commonDeltaY, delta.dy);
+ }
+ }
+
+ // Consider transitions from PRESS to SWIPE or MULTITOUCH.
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+ float dist[MAX_POINTER_ID + 1];
+ int32_t distOverThreshold = 0;
+ for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ dist[id] = hypotf(delta.dx * mPointerXZoomScale,
+ delta.dy * mPointerYZoomScale);
+ if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
+ distOverThreshold += 1;
+ }
+ }
+
+ // Only transition when at least two pointers have moved further than
+ // the minimum distance threshold.
+ if (distOverThreshold >= 2) {
+ if (currentFingerCount > 2) {
+ // There are more than two pointers, switch to FREEFORM.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+ currentFingerCount);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ } else {
+ // There are exactly two pointers.
+ BitSet32 idBits(mCurrentFingerIdBits);
+ uint32_t id1 = idBits.clearFirstMarkedBit();
+ uint32_t id2 = idBits.firstMarkedBit();
+ const RawPointerData::Pointer& p1 = mCurrentRawPointerData.pointerForId(id1);
+ const RawPointerData::Pointer& p2 = mCurrentRawPointerData.pointerForId(id2);
+ float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
+ if (mutualDistance > mPointerGestureMaxSwipeWidth) {
+ // There are two pointers but they are too far apart for a SWIPE,
+ // switch to FREEFORM.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+ mutualDistance, mPointerGestureMaxSwipeWidth);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ } else {
+ // There are two pointers. Wait for both pointers to start moving
+ // before deciding whether this is a SWIPE or FREEFORM gesture.
+ float dist1 = dist[id1];
+ float dist2 = dist[id2];
+ if (dist1 >= mConfig.pointerGestureMultitouchMinDistance
+ && dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
+ // Calculate the dot product of the displacement vectors.
+ // When the vectors are oriented in approximately the same direction,
+ // the angle betweeen them is near zero and the cosine of the angle
+ // approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2).
+ PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
+ PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
+ float dx1 = delta1.dx * mPointerXZoomScale;
+ float dy1 = delta1.dy * mPointerYZoomScale;
+ float dx2 = delta2.dx * mPointerXZoomScale;
+ float dy2 = delta2.dy * mPointerYZoomScale;
+ float dot = dx1 * dx2 + dy1 * dy2;
+ float cosine = dot / (dist1 * dist2); // denominator always > 0
+ if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
+ // Pointers are moving in the same direction. Switch to SWIPE.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to SWIPE, "
+ "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+ "cosine %0.3f >= %0.3f",
+ dist1, mConfig.pointerGestureMultitouchMinDistance,
+ dist2, mConfig.pointerGestureMultitouchMinDistance,
+ cosine, mConfig.pointerGestureSwipeTransitionAngleCosine);
+#endif
+ mPointerGesture.currentGestureMode = PointerGesture::SWIPE;
+ } else {
+ // Pointers are moving in different directions. Switch to FREEFORM.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to FREEFORM, "
+ "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+ "cosine %0.3f < %0.3f",
+ dist1, mConfig.pointerGestureMultitouchMinDistance,
+ dist2, mConfig.pointerGestureMultitouchMinDistance,
+ cosine, mConfig.pointerGestureSwipeTransitionAngleCosine);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ }
+ }
+ }
+ }
+ }
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+ // Switch from SWIPE to FREEFORM if additional pointers go down.
+ // Cancel previous gesture.
+ if (currentFingerCount > 2) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+ currentFingerCount);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ }
+ }
+
+ // Move the reference points based on the overall group motion of the fingers
+ // except in PRESS mode while waiting for a transition to occur.
+ if (mPointerGesture.currentGestureMode != PointerGesture::PRESS
+ && (commonDeltaX || commonDeltaY)) {
+ for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ delta.dx = 0;
+ delta.dy = 0;
+ }
+
+ mPointerGesture.referenceTouchX += commonDeltaX;
+ mPointerGesture.referenceTouchY += commonDeltaY;
+
+ commonDeltaX *= mPointerXMovementScale;
+ commonDeltaY *= mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &commonDeltaX, &commonDeltaY);
+ mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
+
+ mPointerGesture.referenceGestureX += commonDeltaX;
+ mPointerGesture.referenceGestureY += commonDeltaY;
+ }
+
+ // Report gestures.
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS
+ || mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+ // PRESS or SWIPE mode.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS or SWIPE activeTouchId=%d,"
+ "activeGestureId=%d, currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+#endif
+ ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ mPointerGesture.referenceGestureX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ mPointerGesture.referenceGestureY);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
+ // FREEFORM mode.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM activeTouchId=%d,"
+ "activeGestureId=%d, currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+#endif
+ ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+
+ BitSet32 mappedTouchIdBits;
+ BitSet32 usedGestureIdBits;
+ if (mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
+ // Initially, assign the active gesture id to the active touch point
+ // if there is one. No other touch id bits are mapped yet.
+ if (!*outCancelPreviousGesture) {
+ mappedTouchIdBits.markBit(activeTouchId);
+ usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
+ mPointerGesture.activeGestureId;
+ } else {
+ mPointerGesture.activeGestureId = -1;
+ }
+ } else {
+ // Otherwise, assume we mapped all touches from the previous frame.
+ // Reuse all mappings that are still applicable.
+ mappedTouchIdBits.value = mLastFingerIdBits.value
+ & mCurrentFingerIdBits.value;
+ usedGestureIdBits = mPointerGesture.lastGestureIdBits;
+
+ // Check whether we need to choose a new active gesture id because the
+ // current went went up.
+ for (BitSet32 upTouchIdBits(mLastFingerIdBits.value
+ & ~mCurrentFingerIdBits.value);
+ !upTouchIdBits.isEmpty(); ) {
+ uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
+ uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
+ if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
+ mPointerGesture.activeGestureId = -1;
+ break;
+ }
+ }
+ }
+
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM follow up "
+ "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, "
+ "activeGestureId=%d",
+ mappedTouchIdBits.value, usedGestureIdBits.value,
+ mPointerGesture.activeGestureId);
+#endif
+
+ BitSet32 idBits(mCurrentFingerIdBits);
+ for (uint32_t i = 0; i < currentFingerCount; i++) {
+ uint32_t touchId = idBits.clearFirstMarkedBit();
+ uint32_t gestureId;
+ if (!mappedTouchIdBits.hasBit(touchId)) {
+ gestureId = usedGestureIdBits.markFirstUnmarkedBit();
+ mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM "
+ "new mapping for touch id %d -> gesture id %d",
+ touchId, gestureId);
+#endif
+ } else {
+ gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM "
+ "existing mapping for touch id %d -> gesture id %d",
+ touchId, gestureId);
+#endif
+ }
+ mPointerGesture.currentGestureIdBits.markBit(gestureId);
+ mPointerGesture.currentGestureIdToIndex[gestureId] = i;
+
+ const RawPointerData::Pointer& pointer =
+ mCurrentRawPointerData.pointerForId(touchId);
+ float deltaX = (pointer.x - mPointerGesture.referenceTouchX)
+ * mPointerXZoomScale;
+ float deltaY = (pointer.y - mPointerGesture.referenceTouchY)
+ * mPointerYZoomScale;
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+
+ mPointerGesture.currentGestureProperties[i].clear();
+ mPointerGesture.currentGestureProperties[i].id = gestureId;
+ mPointerGesture.currentGestureProperties[i].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[i].clear();
+ mPointerGesture.currentGestureCoords[i].setAxisValue(
+ AMOTION_EVENT_AXIS_X, mPointerGesture.referenceGestureX + deltaX);
+ mPointerGesture.currentGestureCoords[i].setAxisValue(
+ AMOTION_EVENT_AXIS_Y, mPointerGesture.referenceGestureY + deltaY);
+ mPointerGesture.currentGestureCoords[i].setAxisValue(
+ AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ }
+
+ if (mPointerGesture.activeGestureId < 0) {
+ mPointerGesture.activeGestureId =
+ mPointerGesture.currentGestureIdBits.firstMarkedBit();
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM new "
+ "activeGestureId=%d", mPointerGesture.activeGestureId);
+#endif
+ }
+ }
+ }
+
+ mPointerController->setButtonState(mCurrentButtonState);
+
+#if DEBUG_GESTURES
+ ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
+ "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
+ "lastGestureMode=%d, lastGestureIdBits=0x%08x",
+ toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
+ mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
+ mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
+ for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
+ const PointerProperties& properties = mPointerGesture.currentGestureProperties[index];
+ const PointerCoords& coords = mPointerGesture.currentGestureCoords[index];
+ ALOGD(" currentGesture[%d]: index=%d, toolType=%d, "
+ "x=%0.3f, y=%0.3f, pressure=%0.3f",
+ id, index, properties.toolType,
+ coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+ for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = mPointerGesture.lastGestureIdToIndex[id];
+ const PointerProperties& properties = mPointerGesture.lastGestureProperties[index];
+ const PointerCoords& coords = mPointerGesture.lastGestureCoords[index];
+ ALOGD(" lastGesture[%d]: index=%d, toolType=%d, "
+ "x=%0.3f, y=%0.3f, pressure=%0.3f",
+ id, index, properties.toolType,
+ coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+#endif
+ return true;
+}
+
+void TouchInputMapper::dispatchPointerStylus(nsecs_t when, uint32_t policyFlags) {
+ mPointerSimple.currentCoords.clear();
+ mPointerSimple.currentProperties.clear();
+
+ bool down, hovering;
+ if (!mCurrentStylusIdBits.isEmpty()) {
+ uint32_t id = mCurrentStylusIdBits.firstMarkedBit();
+ uint32_t index = mCurrentCookedPointerData.idToIndex[id];
+ float x = mCurrentCookedPointerData.pointerCoords[index].getX();
+ float y = mCurrentCookedPointerData.pointerCoords[index].getY();
+ mPointerController->setPosition(x, y);
+
+ hovering = mCurrentCookedPointerData.hoveringIdBits.hasBit(id);
+ down = !hovering;
+
+ mPointerController->getPosition(&x, &y);
+ mPointerSimple.currentCoords.copyFrom(mCurrentCookedPointerData.pointerCoords[index]);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerSimple.currentProperties.id = 0;
+ mPointerSimple.currentProperties.toolType =
+ mCurrentCookedPointerData.pointerProperties[index].toolType;
+ } else {
+ down = false;
+ hovering = false;
+ }
+
+ dispatchPointerSimple(when, policyFlags, down, hovering);
+}
+
+void TouchInputMapper::abortPointerStylus(nsecs_t when, uint32_t policyFlags) {
+ abortPointerSimple(when, policyFlags);
+}
+
+void TouchInputMapper::dispatchPointerMouse(nsecs_t when, uint32_t policyFlags) {
+ mPointerSimple.currentCoords.clear();
+ mPointerSimple.currentProperties.clear();
+
+ bool down, hovering;
+ if (!mCurrentMouseIdBits.isEmpty()) {
+ uint32_t id = mCurrentMouseIdBits.firstMarkedBit();
+ uint32_t currentIndex = mCurrentRawPointerData.idToIndex[id];
+ if (mLastMouseIdBits.hasBit(id)) {
+ uint32_t lastIndex = mCurrentRawPointerData.idToIndex[id];
+ float deltaX = (mCurrentRawPointerData.pointers[currentIndex].x
+ - mLastRawPointerData.pointers[lastIndex].x)
+ * mPointerXMovementScale;
+ float deltaY = (mCurrentRawPointerData.pointers[currentIndex].y
+ - mLastRawPointerData.pointers[lastIndex].y)
+ * mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ mPointerController->move(deltaX, deltaY);
+ } else {
+ mPointerVelocityControl.reset();
+ }
+
+ down = isPointerDown(mCurrentButtonState);
+ hovering = !down;
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ mPointerSimple.currentCoords.copyFrom(
+ mCurrentCookedPointerData.pointerCoords[currentIndex]);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+ hovering ? 0.0f : 1.0f);
+ mPointerSimple.currentProperties.id = 0;
+ mPointerSimple.currentProperties.toolType =
+ mCurrentCookedPointerData.pointerProperties[currentIndex].toolType;
+ } else {
+ mPointerVelocityControl.reset();
+
+ down = false;
+ hovering = false;
+ }
+
+ dispatchPointerSimple(when, policyFlags, down, hovering);
+}
+
+void TouchInputMapper::abortPointerMouse(nsecs_t when, uint32_t policyFlags) {
+ abortPointerSimple(when, policyFlags);
+
+ mPointerVelocityControl.reset();
+}
+
+void TouchInputMapper::dispatchPointerSimple(nsecs_t when, uint32_t policyFlags,
+ bool down, bool hovering) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+
+ if (mPointerController != NULL) {
+ if (down || hovering) {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+ mPointerController->clearSpots();
+ mPointerController->setButtonState(mCurrentButtonState);
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+ }
+
+ if (mPointerSimple.down && !down) {
+ mPointerSimple.down = false;
+
+ // Send up.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_UP, 0, metaState, mLastButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (mPointerSimple.hovering && !hovering) {
+ mPointerSimple.hovering = false;
+
+ // Send hover exit.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_EXIT, 0, metaState, mLastButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (down) {
+ if (!mPointerSimple.down) {
+ mPointerSimple.down = true;
+ mPointerSimple.downTime = when;
+
+ // Send down.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_DOWN, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Send move.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (hovering) {
+ if (!mPointerSimple.hovering) {
+ mPointerSimple.hovering = true;
+
+ // Send hover enter.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_ENTER, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Send hover move.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (mCurrentRawVScroll || mCurrentRawHScroll) {
+ float vscroll = mCurrentRawVScroll;
+ float hscroll = mCurrentRawHScroll;
+ mWheelYVelocityControl.move(when, NULL, &vscroll);
+ mWheelXVelocityControl.move(when, &hscroll, NULL);
+
+ // Send scroll.
+ PointerCoords pointerCoords;
+ pointerCoords.copyFrom(mPointerSimple.currentCoords);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
+
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_SCROLL, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &pointerCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Save state.
+ if (down || hovering) {
+ mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
+ mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+ } else {
+ mPointerSimple.reset();
+ }
+}
+
+void TouchInputMapper::abortPointerSimple(nsecs_t when, uint32_t policyFlags) {
+ mPointerSimple.currentCoords.clear();
+ mPointerSimple.currentProperties.clear();
+
+ dispatchPointerSimple(when, policyFlags, false, false);
+}
+
+void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags,
+ const PointerProperties* properties, const PointerCoords* coords,
+ const uint32_t* idToIndex, BitSet32 idBits,
+ int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) {
+ PointerCoords pointerCoords[MAX_POINTERS];
+ PointerProperties pointerProperties[MAX_POINTERS];
+ uint32_t pointerCount = 0;
+ while (!idBits.isEmpty()) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = idToIndex[id];
+ pointerProperties[pointerCount].copyFrom(properties[index]);
+ pointerCoords[pointerCount].copyFrom(coords[index]);
+
+ if (changedId >= 0 && id == uint32_t(changedId)) {
+ action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ pointerCount += 1;
+ }
+
+ ALOG_ASSERT(pointerCount != 0);
+
+ if (changedId >= 0 && pointerCount == 1) {
+ // Replace initial down and final up action.
+ // We can compare the action without masking off the changed pointer index
+ // because we know the index is 0.
+ if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ action = AMOTION_EVENT_ACTION_DOWN;
+ } else if (action == AMOTION_EVENT_ACTION_POINTER_UP) {
+ action = AMOTION_EVENT_ACTION_UP;
+ } else {
+ // Can't happen.
+ ALOG_ASSERT(false);
+ }
+ }
+
+ NotifyMotionArgs args(when, getDeviceId(), source, policyFlags,
+ action, flags, metaState, buttonState, edgeFlags,
+ mViewport.displayId, pointerCount, pointerProperties, pointerCoords,
+ xPrecision, yPrecision, downTime);
+ getListener()->notifyMotion(&args);
+}
+
+bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties,
+ const PointerCoords* inCoords, const uint32_t* inIdToIndex,
+ PointerProperties* outProperties, PointerCoords* outCoords, const uint32_t* outIdToIndex,
+ BitSet32 idBits) const {
+ bool changed = false;
+ while (!idBits.isEmpty()) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t inIndex = inIdToIndex[id];
+ uint32_t outIndex = outIdToIndex[id];
+
+ const PointerProperties& curInProperties = inProperties[inIndex];
+ const PointerCoords& curInCoords = inCoords[inIndex];
+ PointerProperties& curOutProperties = outProperties[outIndex];
+ PointerCoords& curOutCoords = outCoords[outIndex];
+
+ if (curInProperties != curOutProperties) {
+ curOutProperties.copyFrom(curInProperties);
+ changed = true;
+ }
+
+ if (curInCoords != curOutCoords) {
+ curOutCoords.copyFrom(curInCoords);
+ changed = true;
+ }
+ }
+ return changed;
+}
+
+void TouchInputMapper::fadePointer() {
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+}
+
+bool TouchInputMapper::isPointInsideSurface(int32_t x, int32_t y) {
+ return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue
+ && y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue;
+}
+
+const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(
+ int32_t x, int32_t y) {
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
+ "left=%d, top=%d, right=%d, bottom=%d",
+ x, y,
+ virtualKey.keyCode, virtualKey.scanCode,
+ virtualKey.hitLeft, virtualKey.hitTop,
+ virtualKey.hitRight, virtualKey.hitBottom);
+#endif
+
+ if (virtualKey.isHit(x, y)) {
+ return & virtualKey;
+ }
+ }
+
+ return NULL;
+}
+
+void TouchInputMapper::assignPointerIds() {
+ uint32_t currentPointerCount = mCurrentRawPointerData.pointerCount;
+ uint32_t lastPointerCount = mLastRawPointerData.pointerCount;
+
+ mCurrentRawPointerData.clearIdBits();
+
+ if (currentPointerCount == 0) {
+ // No pointers to assign.
+ return;
+ }
+
+ if (lastPointerCount == 0) {
+ // All pointers are new.
+ for (uint32_t i = 0; i < currentPointerCount; i++) {
+ uint32_t id = i;
+ mCurrentRawPointerData.pointers[i].id = id;
+ mCurrentRawPointerData.idToIndex[id] = i;
+ mCurrentRawPointerData.markIdBit(id, mCurrentRawPointerData.isHovering(i));
+ }
+ return;
+ }
+
+ if (currentPointerCount == 1 && lastPointerCount == 1
+ && mCurrentRawPointerData.pointers[0].toolType
+ == mLastRawPointerData.pointers[0].toolType) {
+ // Only one pointer and no change in count so it must have the same id as before.
+ uint32_t id = mLastRawPointerData.pointers[0].id;
+ mCurrentRawPointerData.pointers[0].id = id;
+ mCurrentRawPointerData.idToIndex[id] = 0;
+ mCurrentRawPointerData.markIdBit(id, mCurrentRawPointerData.isHovering(0));
+ return;
+ }
+
+ // General case.
+ // We build a heap of squared euclidean distances between current and last pointers
+ // associated with the current and last pointer indices. Then, we find the best
+ // match (by distance) for each current pointer.
+ // The pointers must have the same tool type but it is possible for them to
+ // transition from hovering to touching or vice-versa while retaining the same id.
+ PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS];
+
+ uint32_t heapSize = 0;
+ for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
+ currentPointerIndex++) {
+ for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
+ lastPointerIndex++) {
+ const RawPointerData::Pointer& currentPointer =
+ mCurrentRawPointerData.pointers[currentPointerIndex];
+ const RawPointerData::Pointer& lastPointer =
+ mLastRawPointerData.pointers[lastPointerIndex];
+ if (currentPointer.toolType == lastPointer.toolType) {
+ int64_t deltaX = currentPointer.x - lastPointer.x;
+ int64_t deltaY = currentPointer.y - lastPointer.y;
+
+ uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+ // Insert new element into the heap (sift up).
+ heap[heapSize].currentPointerIndex = currentPointerIndex;
+ heap[heapSize].lastPointerIndex = lastPointerIndex;
+ heap[heapSize].distance = distance;
+ heapSize += 1;
+ }
+ }
+ }
+
+ // Heapify
+ for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) {
+ startIndex -= 1;
+ for (uint32_t parentIndex = startIndex; ;) {
+ uint32_t childIndex = parentIndex * 2 + 1;
+ if (childIndex >= heapSize) {
+ break;
+ }
+
+ if (childIndex + 1 < heapSize
+ && heap[childIndex + 1].distance < heap[childIndex].distance) {
+ childIndex += 1;
+ }
+
+ if (heap[parentIndex].distance <= heap[childIndex].distance) {
+ break;
+ }
+
+ swap(heap[parentIndex], heap[childIndex]);
+ parentIndex = childIndex;
+ }
+ }
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - initial distance min-heap: size=%d", heapSize);
+ for (size_t i = 0; i < heapSize; i++) {
+ ALOGD(" heap[%d]: cur=%d, last=%d, distance=%lld",
+ i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+ heap[i].distance);
+ }
+#endif
+
+ // Pull matches out by increasing order of distance.
+ // To avoid reassigning pointers that have already been matched, the loop keeps track
+ // of which last and current pointers have been matched using the matchedXXXBits variables.
+ // It also tracks the used pointer id bits.
+ BitSet32 matchedLastBits(0);
+ BitSet32 matchedCurrentBits(0);
+ BitSet32 usedIdBits(0);
+ bool first = true;
+ for (uint32_t i = min(currentPointerCount, lastPointerCount); heapSize > 0 && i > 0; i--) {
+ while (heapSize > 0) {
+ if (first) {
+ // The first time through the loop, we just consume the root element of
+ // the heap (the one with smallest distance).
+ first = false;
+ } else {
+ // Previous iterations consumed the root element of the heap.
+ // Pop root element off of the heap (sift down).
+ heap[0] = heap[heapSize];
+ for (uint32_t parentIndex = 0; ;) {
+ uint32_t childIndex = parentIndex * 2 + 1;
+ if (childIndex >= heapSize) {
+ break;
+ }
+
+ if (childIndex + 1 < heapSize
+ && heap[childIndex + 1].distance < heap[childIndex].distance) {
+ childIndex += 1;
+ }
+
+ if (heap[parentIndex].distance <= heap[childIndex].distance) {
+ break;
+ }
+
+ swap(heap[parentIndex], heap[childIndex]);
+ parentIndex = childIndex;
+ }
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - reduced distance min-heap: size=%d", heapSize);
+ for (size_t i = 0; i < heapSize; i++) {
+ ALOGD(" heap[%d]: cur=%d, last=%d, distance=%lld",
+ i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+ heap[i].distance);
+ }
+#endif
+ }
+
+ heapSize -= 1;
+
+ uint32_t currentPointerIndex = heap[0].currentPointerIndex;
+ if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched
+
+ uint32_t lastPointerIndex = heap[0].lastPointerIndex;
+ if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched
+
+ matchedCurrentBits.markBit(currentPointerIndex);
+ matchedLastBits.markBit(lastPointerIndex);
+
+ uint32_t id = mLastRawPointerData.pointers[lastPointerIndex].id;
+ mCurrentRawPointerData.pointers[currentPointerIndex].id = id;
+ mCurrentRawPointerData.idToIndex[id] = currentPointerIndex;
+ mCurrentRawPointerData.markIdBit(id,
+ mCurrentRawPointerData.isHovering(currentPointerIndex));
+ usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld",
+ lastPointerIndex, currentPointerIndex, id, heap[0].distance);
+#endif
+ break;
+ }
+ }
+
+ // Assign fresh ids to pointers that were not matched in the process.
+ for (uint32_t i = currentPointerCount - matchedCurrentBits.count(); i != 0; i--) {
+ uint32_t currentPointerIndex = matchedCurrentBits.markFirstUnmarkedBit();
+ uint32_t id = usedIdBits.markFirstUnmarkedBit();
+
+ mCurrentRawPointerData.pointers[currentPointerIndex].id = id;
+ mCurrentRawPointerData.idToIndex[id] = currentPointerIndex;
+ mCurrentRawPointerData.markIdBit(id,
+ mCurrentRawPointerData.isHovering(currentPointerIndex));
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - assigned: cur=%d, id=%d",
+ currentPointerIndex, id);
+#endif
+ }
+}
+
+int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ if (mCurrentVirtualKey.down && mCurrentVirtualKey.keyCode == keyCode) {
+ return AKEY_STATE_VIRTUAL;
+ }
+
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+ if (virtualKey.keyCode == keyCode) {
+ return AKEY_STATE_UP;
+ }
+ }
+
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ if (mCurrentVirtualKey.down && mCurrentVirtualKey.scanCode == scanCode) {
+ return AKEY_STATE_VIRTUAL;
+ }
+
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+ if (virtualKey.scanCode == scanCode) {
+ return AKEY_STATE_UP;
+ }
+ }
+
+ return AKEY_STATE_UNKNOWN;
+}
+
+bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+
+ for (size_t i = 0; i < numCodes; i++) {
+ if (virtualKey.keyCode == keyCodes[i]) {
+ outFlags[i] = 1;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+// --- SingleTouchInputMapper ---
+
+SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device) :
+ TouchInputMapper(device) {
+}
+
+SingleTouchInputMapper::~SingleTouchInputMapper() {
+}
+
+void SingleTouchInputMapper::reset(nsecs_t when) {
+ mSingleTouchMotionAccumulator.reset(getDevice());
+
+ TouchInputMapper::reset(when);
+}
+
+void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+ TouchInputMapper::process(rawEvent);
+
+ mSingleTouchMotionAccumulator.process(rawEvent);
+}
+
+void SingleTouchInputMapper::syncTouch(nsecs_t when, bool* outHavePointerIds) {
+ if (mTouchButtonAccumulator.isToolActive()) {
+ mCurrentRawPointerData.pointerCount = 1;
+ mCurrentRawPointerData.idToIndex[0] = 0;
+
+ bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE
+ && (mTouchButtonAccumulator.isHovering()
+ || (mRawPointerAxes.pressure.valid
+ && mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0));
+ mCurrentRawPointerData.markIdBit(0, isHovering);
+
+ RawPointerData::Pointer& outPointer = mCurrentRawPointerData.pointers[0];
+ outPointer.id = 0;
+ outPointer.x = mSingleTouchMotionAccumulator.getAbsoluteX();
+ outPointer.y = mSingleTouchMotionAccumulator.getAbsoluteY();
+ outPointer.pressure = mSingleTouchMotionAccumulator.getAbsolutePressure();
+ outPointer.touchMajor = 0;
+ outPointer.touchMinor = 0;
+ outPointer.toolMajor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
+ outPointer.toolMinor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
+ outPointer.orientation = 0;
+ outPointer.distance = mSingleTouchMotionAccumulator.getAbsoluteDistance();
+ outPointer.tiltX = mSingleTouchMotionAccumulator.getAbsoluteTiltX();
+ outPointer.tiltY = mSingleTouchMotionAccumulator.getAbsoluteTiltY();
+ outPointer.toolType = mTouchButtonAccumulator.getToolType();
+ if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ }
+ outPointer.isHovering = isHovering;
+ }
+}
+
+void SingleTouchInputMapper::configureRawPointerAxes() {
+ TouchInputMapper::configureRawPointerAxes();
+
+ getAbsoluteAxisInfo(ABS_X, &mRawPointerAxes.x);
+ getAbsoluteAxisInfo(ABS_Y, &mRawPointerAxes.y);
+ getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPointerAxes.pressure);
+ getAbsoluteAxisInfo(ABS_TOOL_WIDTH, &mRawPointerAxes.toolMajor);
+ getAbsoluteAxisInfo(ABS_DISTANCE, &mRawPointerAxes.distance);
+ getAbsoluteAxisInfo(ABS_TILT_X, &mRawPointerAxes.tiltX);
+ getAbsoluteAxisInfo(ABS_TILT_Y, &mRawPointerAxes.tiltY);
+}
+
+bool SingleTouchInputMapper::hasStylus() const {
+ return mTouchButtonAccumulator.hasStylus();
+}
+
+
+// --- MultiTouchInputMapper ---
+
+MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) :
+ TouchInputMapper(device) {
+}
+
+MultiTouchInputMapper::~MultiTouchInputMapper() {
+}
+
+void MultiTouchInputMapper::reset(nsecs_t when) {
+ mMultiTouchMotionAccumulator.reset(getDevice());
+
+ mPointerIdBits.clear();
+
+ TouchInputMapper::reset(when);
+}
+
+void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+ TouchInputMapper::process(rawEvent);
+
+ mMultiTouchMotionAccumulator.process(rawEvent);
+}
+
+void MultiTouchInputMapper::syncTouch(nsecs_t when, bool* outHavePointerIds) {
+ size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
+ size_t outCount = 0;
+ BitSet32 newPointerIdBits;
+
+ for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
+ const MultiTouchMotionAccumulator::Slot* inSlot =
+ mMultiTouchMotionAccumulator.getSlot(inIndex);
+ if (!inSlot->isInUse()) {
+ continue;
+ }
+
+ if (outCount >= MAX_POINTERS) {
+#if DEBUG_POINTERS
+ ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
+ "ignoring the rest.",
+ getDeviceName().string(), MAX_POINTERS);
+#endif
+ break; // too many fingers!
+ }
+
+ RawPointerData::Pointer& outPointer = mCurrentRawPointerData.pointers[outCount];
+ outPointer.x = inSlot->getX();
+ outPointer.y = inSlot->getY();
+ outPointer.pressure = inSlot->getPressure();
+ outPointer.touchMajor = inSlot->getTouchMajor();
+ outPointer.touchMinor = inSlot->getTouchMinor();
+ outPointer.toolMajor = inSlot->getToolMajor();
+ outPointer.toolMinor = inSlot->getToolMinor();
+ outPointer.orientation = inSlot->getOrientation();
+ outPointer.distance = inSlot->getDistance();
+ outPointer.tiltX = 0;
+ outPointer.tiltY = 0;
+
+ outPointer.toolType = inSlot->getToolType();
+ if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ outPointer.toolType = mTouchButtonAccumulator.getToolType();
+ if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ }
+ }
+
+ bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE
+ && (mTouchButtonAccumulator.isHovering()
+ || (mRawPointerAxes.pressure.valid && inSlot->getPressure() <= 0));
+ outPointer.isHovering = isHovering;
+
+ // Assign pointer id using tracking id if available.
+ if (*outHavePointerIds) {
+ int32_t trackingId = inSlot->getTrackingId();
+ int32_t id = -1;
+ if (trackingId >= 0) {
+ for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty(); ) {
+ uint32_t n = idBits.clearFirstMarkedBit();
+ if (mPointerTrackingIdMap[n] == trackingId) {
+ id = n;
+ }
+ }
+
+ if (id < 0 && !mPointerIdBits.isFull()) {
+ id = mPointerIdBits.markFirstUnmarkedBit();
+ mPointerTrackingIdMap[id] = trackingId;
+ }
+ }
+ if (id < 0) {
+ *outHavePointerIds = false;
+ mCurrentRawPointerData.clearIdBits();
+ newPointerIdBits.clear();
+ } else {
+ outPointer.id = id;
+ mCurrentRawPointerData.idToIndex[id] = outCount;
+ mCurrentRawPointerData.markIdBit(id, isHovering);
+ newPointerIdBits.markBit(id);
+ }
+ }
+
+ outCount += 1;
+ }
+
+ mCurrentRawPointerData.pointerCount = outCount;
+ mPointerIdBits = newPointerIdBits;
+
+ mMultiTouchMotionAccumulator.finishSync();
+}
+
+void MultiTouchInputMapper::configureRawPointerAxes() {
+ TouchInputMapper::configureRawPointerAxes();
+
+ getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x);
+ getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y);
+ getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor);
+ getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor);
+ getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor);
+ getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor);
+ getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation);
+ getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure);
+ getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance);
+ getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId);
+ getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot);
+
+ if (mRawPointerAxes.trackingId.valid
+ && mRawPointerAxes.slot.valid
+ && mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) {
+ size_t slotCount = mRawPointerAxes.slot.maxValue + 1;
+ if (slotCount > MAX_SLOTS) {
+ ALOGW("MultiTouch Device %s reported %d slots but the framework "
+ "only supports a maximum of %d slots at this time.",
+ getDeviceName().string(), slotCount, MAX_SLOTS);
+ slotCount = MAX_SLOTS;
+ }
+ mMultiTouchMotionAccumulator.configure(getDevice(),
+ slotCount, true /*usingSlotsProtocol*/);
+ } else {
+ mMultiTouchMotionAccumulator.configure(getDevice(),
+ MAX_POINTERS, false /*usingSlotsProtocol*/);
+ }
+}
+
+bool MultiTouchInputMapper::hasStylus() const {
+ return mMultiTouchMotionAccumulator.hasStylus()
+ || mTouchButtonAccumulator.hasStylus();
+}
+
+
+// --- JoystickInputMapper ---
+
+JoystickInputMapper::JoystickInputMapper(InputDevice* device) :
+ InputMapper(device) {
+}
+
+JoystickInputMapper::~JoystickInputMapper() {
+}
+
+uint32_t JoystickInputMapper::getSources() {
+ return AINPUT_SOURCE_JOYSTICK;
+}
+
+void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ for (size_t i = 0; i < mAxes.size(); i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ addMotionRange(axis.axisInfo.axis, axis, info);
+
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ addMotionRange(axis.axisInfo.highAxis, axis, info);
+
+ }
+ }
+}
+
+void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis,
+ InputDeviceInfo* info) {
+ info->addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK,
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ /* In order to ease the transition for developers from using the old axes
+ * to the newer, more semantically correct axes, we'll continue to register
+ * the old axes as duplicates of their corresponding new ones. */
+ int32_t compatAxis = getCompatAxis(axisId);
+ if (compatAxis >= 0) {
+ info->addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK,
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ }
+}
+
+/* A mapping from axes the joystick actually has to the axes that should be
+ * artificially created for compatibility purposes.
+ * Returns -1 if no compatibility axis is needed. */
+int32_t JoystickInputMapper::getCompatAxis(int32_t axis) {
+ switch(axis) {
+ case AMOTION_EVENT_AXIS_LTRIGGER:
+ return AMOTION_EVENT_AXIS_BRAKE;
+ case AMOTION_EVENT_AXIS_RTRIGGER:
+ return AMOTION_EVENT_AXIS_GAS;
+ }
+ return -1;
+}
+
+void JoystickInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Joystick Input Mapper:\n");
+
+ dump.append(INDENT3 "Axes:\n");
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ const char* label = getAxisLabel(axis.axisInfo.axis);
+ if (label) {
+ dump.appendFormat(INDENT4 "%s", label);
+ } else {
+ dump.appendFormat(INDENT4 "%d", axis.axisInfo.axis);
+ }
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ label = getAxisLabel(axis.axisInfo.highAxis);
+ if (label) {
+ dump.appendFormat(" / %s (split at %d)", label, axis.axisInfo.splitValue);
+ } else {
+ dump.appendFormat(" / %d (split at %d)", axis.axisInfo.highAxis,
+ axis.axisInfo.splitValue);
+ }
+ } else if (axis.axisInfo.mode == AxisInfo::MODE_INVERT) {
+ dump.append(" (invert)");
+ }
+
+ dump.appendFormat(": min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f, resolution=%0.5f\n",
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ dump.appendFormat(INDENT4 " scale=%0.5f, offset=%0.5f, "
+ "highScale=%0.5f, highOffset=%0.5f\n",
+ axis.scale, axis.offset, axis.highScale, axis.highOffset);
+ dump.appendFormat(INDENT4 " rawAxis=%d, rawMin=%d, rawMax=%d, "
+ "rawFlat=%d, rawFuzz=%d, rawResolution=%d\n",
+ mAxes.keyAt(i), axis.rawAxisInfo.minValue, axis.rawAxisInfo.maxValue,
+ axis.rawAxisInfo.flat, axis.rawAxisInfo.fuzz, axis.rawAxisInfo.resolution);
+ }
+}
+
+void JoystickInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ // Collect all axes.
+ for (int32_t abs = 0; abs <= ABS_MAX; abs++) {
+ if (!(getAbsAxisUsage(abs, getDevice()->getClasses())
+ & INPUT_DEVICE_CLASS_JOYSTICK)) {
+ continue; // axis must be claimed by a different device
+ }
+
+ RawAbsoluteAxisInfo rawAxisInfo;
+ getAbsoluteAxisInfo(abs, &rawAxisInfo);
+ if (rawAxisInfo.valid) {
+ // Map axis.
+ AxisInfo axisInfo;
+ bool explicitlyMapped = !getEventHub()->mapAxis(getDeviceId(), abs, &axisInfo);
+ if (!explicitlyMapped) {
+ // Axis is not explicitly mapped, will choose a generic axis later.
+ axisInfo.mode = AxisInfo::MODE_NORMAL;
+ axisInfo.axis = -1;
+ }
+
+ // Apply flat override.
+ int32_t rawFlat = axisInfo.flatOverride < 0
+ ? rawAxisInfo.flat : axisInfo.flatOverride;
+
+ // Calculate scaling factors and limits.
+ Axis axis;
+ if (axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ float scale = 1.0f / (axisInfo.splitValue - rawAxisInfo.minValue);
+ float highScale = 1.0f / (rawAxisInfo.maxValue - axisInfo.splitValue);
+ axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+ scale, 0.0f, highScale, 0.0f,
+ 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
+ } else if (isCenteredAxis(axisInfo.axis)) {
+ float scale = 2.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
+ float offset = avg(rawAxisInfo.minValue, rawAxisInfo.maxValue) * -scale;
+ axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+ scale, offset, scale, offset,
+ -1.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
+ } else {
+ float scale = 1.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
+ axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+ scale, 0.0f, scale, 0.0f,
+ 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
+ }
+
+ // To eliminate noise while the joystick is at rest, filter out small variations
+ // in axis values up front.
+ axis.filter = axis.flat * 0.25f;
+
+ mAxes.add(abs, axis);
+ }
+ }
+
+ // If there are too many axes, start dropping them.
+ // Prefer to keep explicitly mapped axes.
+ if (mAxes.size() > PointerCoords::MAX_AXES) {
+ ALOGI("Joystick '%s' has %d axes but the framework only supports a maximum of %d.",
+ getDeviceName().string(), mAxes.size(), PointerCoords::MAX_AXES);
+ pruneAxes(true);
+ pruneAxes(false);
+ }
+
+ // Assign generic axis ids to remaining axes.
+ int32_t nextGenericAxisId = AMOTION_EVENT_AXIS_GENERIC_1;
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ Axis& axis = mAxes.editValueAt(i);
+ if (axis.axisInfo.axis < 0) {
+ while (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16
+ && haveAxis(nextGenericAxisId)) {
+ nextGenericAxisId += 1;
+ }
+
+ if (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16) {
+ axis.axisInfo.axis = nextGenericAxisId;
+ nextGenericAxisId += 1;
+ } else {
+ ALOGI("Ignoring joystick '%s' axis %d because all of the generic axis ids "
+ "have already been assigned to other axes.",
+ getDeviceName().string(), mAxes.keyAt(i));
+ mAxes.removeItemsAt(i--);
+ numAxes -= 1;
+ }
+ }
+ }
+ }
+}
+
+bool JoystickInputMapper::haveAxis(int32_t axisId) {
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ if (axis.axisInfo.axis == axisId
+ || (axis.axisInfo.mode == AxisInfo::MODE_SPLIT
+ && axis.axisInfo.highAxis == axisId)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void JoystickInputMapper::pruneAxes(bool ignoreExplicitlyMappedAxes) {
+ size_t i = mAxes.size();
+ while (mAxes.size() > PointerCoords::MAX_AXES && i-- > 0) {
+ if (ignoreExplicitlyMappedAxes && mAxes.valueAt(i).explicitlyMapped) {
+ continue;
+ }
+ ALOGI("Discarding joystick '%s' axis %d because there are too many axes.",
+ getDeviceName().string(), mAxes.keyAt(i));
+ mAxes.removeItemsAt(i);
+ }
+}
+
+bool JoystickInputMapper::isCenteredAxis(int32_t axis) {
+ switch (axis) {
+ case AMOTION_EVENT_AXIS_X:
+ case AMOTION_EVENT_AXIS_Y:
+ case AMOTION_EVENT_AXIS_Z:
+ case AMOTION_EVENT_AXIS_RX:
+ case AMOTION_EVENT_AXIS_RY:
+ case AMOTION_EVENT_AXIS_RZ:
+ case AMOTION_EVENT_AXIS_HAT_X:
+ case AMOTION_EVENT_AXIS_HAT_Y:
+ case AMOTION_EVENT_AXIS_ORIENTATION:
+ case AMOTION_EVENT_AXIS_RUDDER:
+ case AMOTION_EVENT_AXIS_WHEEL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void JoystickInputMapper::reset(nsecs_t when) {
+ // Recenter all axes.
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ Axis& axis = mAxes.editValueAt(i);
+ axis.resetValue();
+ }
+
+ InputMapper::reset(when);
+}
+
+void JoystickInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_ABS: {
+ ssize_t index = mAxes.indexOfKey(rawEvent->code);
+ if (index >= 0) {
+ Axis& axis = mAxes.editValueAt(index);
+ float newValue, highNewValue;
+ switch (axis.axisInfo.mode) {
+ case AxisInfo::MODE_INVERT:
+ newValue = (axis.rawAxisInfo.maxValue - rawEvent->value)
+ * axis.scale + axis.offset;
+ highNewValue = 0.0f;
+ break;
+ case AxisInfo::MODE_SPLIT:
+ if (rawEvent->value < axis.axisInfo.splitValue) {
+ newValue = (axis.axisInfo.splitValue - rawEvent->value)
+ * axis.scale + axis.offset;
+ highNewValue = 0.0f;
+ } else if (rawEvent->value > axis.axisInfo.splitValue) {
+ newValue = 0.0f;
+ highNewValue = (rawEvent->value - axis.axisInfo.splitValue)
+ * axis.highScale + axis.highOffset;
+ } else {
+ newValue = 0.0f;
+ highNewValue = 0.0f;
+ }
+ break;
+ default:
+ newValue = rawEvent->value * axis.scale + axis.offset;
+ highNewValue = 0.0f;
+ break;
+ }
+ axis.newValue = newValue;
+ axis.highNewValue = highNewValue;
+ }
+ break;
+ }
+
+ case EV_SYN:
+ switch (rawEvent->code) {
+ case SYN_REPORT:
+ sync(rawEvent->when, false /*force*/);
+ break;
+ }
+ break;
+ }
+}
+
+void JoystickInputMapper::sync(nsecs_t when, bool force) {
+ if (!filterAxes(force)) {
+ return;
+ }
+
+ int32_t metaState = mContext->getGlobalMetaState();
+ int32_t buttonState = 0;
+
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.axis, axis.currentValue);
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.highAxis,
+ axis.highCurrentValue);
+ }
+ }
+
+ // Moving a joystick axis should not wake the device because joysticks can
+ // be fairly noisy even when not in use. On the other hand, pushing a gamepad
+ // button will likely wake the device.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ uint32_t policyFlags = 0;
+
+ NotifyMotionArgs args(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, policyFlags,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ ADISPLAY_ID_NONE, 1, &pointerProperties, &pointerCoords, 0, 0, 0);
+ getListener()->notifyMotion(&args);
+}
+
+void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords,
+ int32_t axis, float value) {
+ pointerCoords->setAxisValue(axis, value);
+ /* In order to ease the transition for developers from using the old axes
+ * to the newer, more semantically correct axes, we'll continue to produce
+ * values for the old axes as mirrors of the value of their corresponding
+ * new axes. */
+ int32_t compatAxis = getCompatAxis(axis);
+ if (compatAxis >= 0) {
+ pointerCoords->setAxisValue(compatAxis, value);
+ }
+}
+
+bool JoystickInputMapper::filterAxes(bool force) {
+ bool atLeastOneSignificantChange = force;
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ Axis& axis = mAxes.editValueAt(i);
+ if (force || hasValueChangedSignificantly(axis.filter,
+ axis.newValue, axis.currentValue, axis.min, axis.max)) {
+ axis.currentValue = axis.newValue;
+ atLeastOneSignificantChange = true;
+ }
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ if (force || hasValueChangedSignificantly(axis.filter,
+ axis.highNewValue, axis.highCurrentValue, axis.min, axis.max)) {
+ axis.highCurrentValue = axis.highNewValue;
+ atLeastOneSignificantChange = true;
+ }
+ }
+ }
+ return atLeastOneSignificantChange;
+}
+
+bool JoystickInputMapper::hasValueChangedSignificantly(
+ float filter, float newValue, float currentValue, float min, float max) {
+ if (newValue != currentValue) {
+ // Filter out small changes in value unless the value is converging on the axis
+ // bounds or center point. This is intended to reduce the amount of information
+ // sent to applications by particularly noisy joysticks (such as PS3).
+ if (fabs(newValue - currentValue) > filter
+ || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, min)
+ || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, max)
+ || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, 0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool JoystickInputMapper::hasMovedNearerToValueWithinFilteredRange(
+ float filter, float newValue, float currentValue, float thresholdValue) {
+ float newDistance = fabs(newValue - thresholdValue);
+ if (newDistance < filter) {
+ float oldDistance = fabs(currentValue - thresholdValue);
+ if (newDistance < oldDistance) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputReader.h b/widget/gonk/libui/InputReader.h
new file mode 100644
index 000000000..5c790fdb8
--- /dev/null
+++ b/widget/gonk/libui/InputReader.h
@@ -0,0 +1,1811 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_READER_H
+#define _UI_INPUT_READER_H
+
+#include "EventHub.h"
+#include "PointerController.h"
+#include "InputListener.h"
+
+#include "Input.h"
+#include "VelocityControl.h"
+#include "VelocityTracker.h"
+#include <utils/KeyedVector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/BitSet.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+// Maximum supported size of a vibration pattern.
+// Must be at least 2.
+#define MAX_VIBRATE_PATTERN_SIZE 100
+
+// Maximum allowable delay value in a vibration pattern before
+// which the delay will be truncated.
+#define MAX_VIBRATE_PATTERN_DELAY_NSECS (1000000 * 1000000000LL)
+
+namespace android {
+
+class InputDevice;
+class InputMapper;
+
+/*
+ * Describes how coordinates are mapped on a physical display.
+ * See com.android.server.display.DisplayViewport.
+ */
+struct DisplayViewport {
+ int32_t displayId; // -1 if invalid
+ int32_t orientation;
+ int32_t logicalLeft;
+ int32_t logicalTop;
+ int32_t logicalRight;
+ int32_t logicalBottom;
+ int32_t physicalLeft;
+ int32_t physicalTop;
+ int32_t physicalRight;
+ int32_t physicalBottom;
+ int32_t deviceWidth;
+ int32_t deviceHeight;
+
+ DisplayViewport() :
+ displayId(ADISPLAY_ID_NONE), orientation(DISPLAY_ORIENTATION_0),
+ logicalLeft(0), logicalTop(0), logicalRight(0), logicalBottom(0),
+ physicalLeft(0), physicalTop(0), physicalRight(0), physicalBottom(0),
+ deviceWidth(0), deviceHeight(0) {
+ }
+
+ bool operator==(const DisplayViewport& other) const {
+ return displayId == other.displayId
+ && orientation == other.orientation
+ && logicalLeft == other.logicalLeft
+ && logicalTop == other.logicalTop
+ && logicalRight == other.logicalRight
+ && logicalBottom == other.logicalBottom
+ && physicalLeft == other.physicalLeft
+ && physicalTop == other.physicalTop
+ && physicalRight == other.physicalRight
+ && physicalBottom == other.physicalBottom
+ && deviceWidth == other.deviceWidth
+ && deviceHeight == other.deviceHeight;
+ }
+
+ bool operator!=(const DisplayViewport& other) const {
+ return !(*this == other);
+ }
+
+ inline bool isValid() const {
+ return displayId >= 0;
+ }
+
+ void setNonDisplayViewport(int32_t width, int32_t height) {
+ displayId = ADISPLAY_ID_NONE;
+ orientation = DISPLAY_ORIENTATION_0;
+ logicalLeft = 0;
+ logicalTop = 0;
+ logicalRight = width;
+ logicalBottom = height;
+ physicalLeft = 0;
+ physicalTop = 0;
+ physicalRight = width;
+ physicalBottom = height;
+ deviceWidth = width;
+ deviceHeight = height;
+ }
+};
+
+/*
+ * Input reader configuration.
+ *
+ * Specifies various options that modify the behavior of the input reader.
+ */
+struct InputReaderConfiguration {
+ // Describes changes that have occurred.
+ enum {
+ // The pointer speed changed.
+ CHANGE_POINTER_SPEED = 1 << 0,
+
+ // The pointer gesture control changed.
+ CHANGE_POINTER_GESTURE_ENABLEMENT = 1 << 1,
+
+ // The display size or orientation changed.
+ CHANGE_DISPLAY_INFO = 1 << 2,
+
+ // The visible touches option changed.
+ CHANGE_SHOW_TOUCHES = 1 << 3,
+
+ // The keyboard layouts must be reloaded.
+ CHANGE_KEYBOARD_LAYOUTS = 1 << 4,
+
+ // The device name alias supplied by the may have changed for some devices.
+ CHANGE_DEVICE_ALIAS = 1 << 5,
+
+ // All devices must be reopened.
+ CHANGE_MUST_REOPEN = 1 << 31,
+ };
+
+ // Gets the amount of time to disable virtual keys after the screen is touched
+ // in order to filter out accidental virtual key presses due to swiping gestures
+ // or taps near the edge of the display. May be 0 to disable the feature.
+ nsecs_t virtualKeyQuietTime;
+
+ // The excluded device names for the platform.
+ // Devices with these names will be ignored.
+ Vector<String8> excludedDeviceNames;
+
+ // Velocity control parameters for mouse pointer movements.
+ VelocityControlParameters pointerVelocityControlParameters;
+
+ // Velocity control parameters for mouse wheel movements.
+ VelocityControlParameters wheelVelocityControlParameters;
+
+ // True if pointer gestures are enabled.
+ bool pointerGesturesEnabled;
+
+ // Quiet time between certain pointer gesture transitions.
+ // Time to allow for all fingers or buttons to settle into a stable state before
+ // starting a new gesture.
+ nsecs_t pointerGestureQuietInterval;
+
+ // The minimum speed that a pointer must travel for us to consider switching the active
+ // touch pointer to it during a drag. This threshold is set to avoid switching due
+ // to noise from a finger resting on the touch pad (perhaps just pressing it down).
+ float pointerGestureDragMinSwitchSpeed; // in pixels per second
+
+ // Tap gesture delay time.
+ // The time between down and up must be less than this to be considered a tap.
+ nsecs_t pointerGestureTapInterval;
+
+ // Tap drag gesture delay time.
+ // The time between the previous tap's up and the next down must be less than
+ // this to be considered a drag. Otherwise, the previous tap is finished and a
+ // new tap begins.
+ //
+ // Note that the previous tap will be held down for this entire duration so this
+ // interval must be shorter than the long press timeout.
+ nsecs_t pointerGestureTapDragInterval;
+
+ // The distance in pixels that the pointer is allowed to move from initial down
+ // to up and still be called a tap.
+ float pointerGestureTapSlop; // in pixels
+
+ // Time after the first touch points go down to settle on an initial centroid.
+ // This is intended to be enough time to handle cases where the user puts down two
+ // fingers at almost but not quite exactly the same time.
+ nsecs_t pointerGestureMultitouchSettleInterval;
+
+ // The transition from PRESS to SWIPE or FREEFORM gesture mode is made when
+ // at least two pointers have moved at least this far from their starting place.
+ float pointerGestureMultitouchMinDistance; // in pixels
+
+ // The transition from PRESS to SWIPE gesture mode can only occur when the
+ // cosine of the angle between the two vectors is greater than or equal to than this value
+ // which indicates that the vectors are oriented in the same direction.
+ // When the vectors are oriented in the exactly same direction, the cosine is 1.0.
+ // (In exactly opposite directions, the cosine is -1.0.)
+ float pointerGestureSwipeTransitionAngleCosine;
+
+ // The transition from PRESS to SWIPE gesture mode can only occur when the
+ // fingers are no more than this far apart relative to the diagonal size of
+ // the touch pad. For example, a ratio of 0.5 means that the fingers must be
+ // no more than half the diagonal size of the touch pad apart.
+ float pointerGestureSwipeMaxWidthRatio;
+
+ // The gesture movement speed factor relative to the size of the display.
+ // Movement speed applies when the fingers are moving in the same direction.
+ // Without acceleration, a full swipe of the touch pad diagonal in movement mode
+ // will cover this portion of the display diagonal.
+ float pointerGestureMovementSpeedRatio;
+
+ // The gesture zoom speed factor relative to the size of the display.
+ // Zoom speed applies when the fingers are mostly moving relative to each other
+ // to execute a scale gesture or similar.
+ // Without acceleration, a full swipe of the touch pad diagonal in zoom mode
+ // will cover this portion of the display diagonal.
+ float pointerGestureZoomSpeedRatio;
+
+ // True to show the location of touches on the touch screen as spots.
+ bool showTouches;
+
+ InputReaderConfiguration() :
+ virtualKeyQuietTime(0),
+ pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, 3.0f),
+ wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f),
+ pointerGesturesEnabled(true),
+ pointerGestureQuietInterval(100 * 1000000LL), // 100 ms
+ pointerGestureDragMinSwitchSpeed(50), // 50 pixels per second
+ pointerGestureTapInterval(150 * 1000000LL), // 150 ms
+ pointerGestureTapDragInterval(150 * 1000000LL), // 150 ms
+ pointerGestureTapSlop(10.0f), // 10 pixels
+ pointerGestureMultitouchSettleInterval(100 * 1000000LL), // 100 ms
+ pointerGestureMultitouchMinDistance(15), // 15 pixels
+ pointerGestureSwipeTransitionAngleCosine(0.2588f), // cosine of 75 degrees
+ pointerGestureSwipeMaxWidthRatio(0.25f),
+ pointerGestureMovementSpeedRatio(0.8f),
+ pointerGestureZoomSpeedRatio(0.3f),
+ showTouches(false) { }
+
+ bool getDisplayInfo(bool external, DisplayViewport* outViewport) const;
+ void setDisplayInfo(bool external, const DisplayViewport& viewport);
+
+private:
+ DisplayViewport mInternalDisplay;
+ DisplayViewport mExternalDisplay;
+};
+
+
+/*
+ * Input reader policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ *
+ * These methods must NOT re-enter the input reader since they may be called while
+ * holding the input reader lock.
+ */
+class InputReaderPolicyInterface : public virtual RefBase {
+protected:
+ InputReaderPolicyInterface() { }
+ virtual ~InputReaderPolicyInterface() { }
+
+public:
+ /* Gets the input reader configuration. */
+ virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) = 0;
+
+ /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
+ virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+
+ /* Notifies the input reader policy that some input devices have changed
+ * and provides information about all current input devices.
+ */
+ virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) = 0;
+
+ /* Gets the keyboard layout for a particular input device. */
+ virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(const String8& inputDeviceDescriptor) = 0;
+
+ /* Gets a user-supplied alias for a particular input device, or an empty string if none. */
+ virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier) = 0;
+};
+
+
+/* Processes raw input events and sends cooked event data to an input listener. */
+class InputReaderInterface : public virtual RefBase {
+protected:
+ InputReaderInterface() { }
+ virtual ~InputReaderInterface() { }
+
+public:
+ /* Dumps the state of the input reader.
+ *
+ * This method may be called on any thread (usually by the input manager). */
+ virtual void dump(String8& dump) = 0;
+
+ /* Called by the heatbeat to ensures that the reader has not deadlocked. */
+ virtual void monitor() = 0;
+
+ /* Runs a single iteration of the processing loop.
+ * Nominally reads and processes one incoming message from the EventHub.
+ *
+ * This method should be called on the input reader thread.
+ */
+ virtual void loopOnce() = 0;
+
+ /* Gets information about all input devices.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices) = 0;
+
+ /* Query current input state. */
+ virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode) = 0;
+ virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode) = 0;
+ virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+ int32_t sw) = 0;
+
+ /* Determine whether physical keys exist for the given framework-domain key codes. */
+ virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0;
+
+ /* Requests that a reconfiguration of all input devices.
+ * The changes flag is a bitfield that indicates what has changed and whether
+ * the input devices must all be reopened. */
+ virtual void requestRefreshConfiguration(uint32_t changes) = 0;
+
+ /* Controls the vibrator of a particular input device. */
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) = 0;
+ virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0;
+};
+
+
+/* Internal interface used by individual input devices to access global input device state
+ * and parameters maintained by the input reader.
+ */
+class InputReaderContext {
+public:
+ InputReaderContext() { }
+ virtual ~InputReaderContext() { }
+
+ virtual void updateGlobalMetaState() = 0;
+ virtual int32_t getGlobalMetaState() = 0;
+
+ virtual void disableVirtualKeysUntil(nsecs_t time) = 0;
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) = 0;
+
+ virtual void fadePointer() = 0;
+
+ virtual void requestTimeoutAtTime(nsecs_t when) = 0;
+ virtual int32_t bumpGeneration() = 0;
+
+ virtual InputReaderPolicyInterface* getPolicy() = 0;
+ virtual InputListenerInterface* getListener() = 0;
+ virtual EventHubInterface* getEventHub() = 0;
+};
+
+
+/* The input reader reads raw event data from the event hub and processes it into input events
+ * that it sends to the input listener. Some functions of the input reader, such as early
+ * event filtering in low power states, are controlled by a separate policy object.
+ *
+ * The InputReader owns a collection of InputMappers. Most of the work it does happens
+ * on the input reader thread but the InputReader can receive queries from other system
+ * components running on arbitrary threads. To keep things manageable, the InputReader
+ * uses a single Mutex to guard its state. The Mutex may be held while calling into the
+ * EventHub or the InputReaderPolicy but it is never held while calling into the
+ * InputListener.
+ */
+class InputReader : public InputReaderInterface {
+public:
+ InputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputListenerInterface>& listener);
+ virtual ~InputReader();
+
+ virtual void dump(String8& dump);
+ virtual void monitor();
+
+ virtual void loopOnce();
+
+ virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices);
+
+ virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode);
+ virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode);
+ virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+ int32_t sw);
+
+ virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual void requestRefreshConfiguration(uint32_t changes);
+
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token);
+ virtual void cancelVibrate(int32_t deviceId, int32_t token);
+
+protected:
+ // These members are protected so they can be instrumented by test cases.
+ virtual InputDevice* createDeviceLocked(int32_t deviceId,
+ const InputDeviceIdentifier& identifier, uint32_t classes);
+
+ class ContextImpl : public InputReaderContext {
+ InputReader* mReader;
+
+ public:
+ ContextImpl(InputReader* reader);
+
+ virtual void updateGlobalMetaState();
+ virtual int32_t getGlobalMetaState();
+ virtual void disableVirtualKeysUntil(nsecs_t time);
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode);
+ virtual void fadePointer();
+ virtual void requestTimeoutAtTime(nsecs_t when);
+ virtual int32_t bumpGeneration();
+ virtual InputReaderPolicyInterface* getPolicy();
+ virtual InputListenerInterface* getListener();
+ virtual EventHubInterface* getEventHub();
+ } mContext;
+
+ friend class ContextImpl;
+
+private:
+ Mutex mLock;
+
+ Condition mReaderIsAliveCondition;
+
+ sp<EventHubInterface> mEventHub;
+ sp<InputReaderPolicyInterface> mPolicy;
+ sp<QueuedInputListener> mQueuedListener;
+
+ InputReaderConfiguration mConfig;
+
+ // The event queue.
+ static const int EVENT_BUFFER_SIZE = 256;
+ RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
+
+ KeyedVector<int32_t, InputDevice*> mDevices;
+
+ // low-level input event decoding and device management
+ void processEventsLocked(const RawEvent* rawEvents, size_t count);
+
+ void addDeviceLocked(nsecs_t when, int32_t deviceId);
+ void removeDeviceLocked(nsecs_t when, int32_t deviceId);
+ void processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count);
+ void timeoutExpiredLocked(nsecs_t when);
+
+ void handleConfigurationChangedLocked(nsecs_t when);
+
+ int32_t mGlobalMetaState;
+ void updateGlobalMetaStateLocked();
+ int32_t getGlobalMetaStateLocked();
+
+ void fadePointerLocked();
+
+ int32_t mGeneration;
+ int32_t bumpGenerationLocked();
+
+ void getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices);
+
+ nsecs_t mDisableVirtualKeysTimeout;
+ void disableVirtualKeysUntilLocked(nsecs_t time);
+ bool shouldDropVirtualKeyLocked(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode);
+
+ nsecs_t mNextTimeout;
+ void requestTimeoutAtTimeLocked(nsecs_t when);
+
+ uint32_t mConfigurationChangesToRefresh;
+ void refreshConfigurationLocked(uint32_t changes);
+
+ // state queries
+ typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+ int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
+ GetStateFunc getStateFunc);
+ bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+};
+
+
+/* Reads raw events from the event hub and processes them, endlessly. */
+class InputReaderThread : public Thread {
+public:
+ InputReaderThread(const sp<InputReaderInterface>& reader);
+ virtual ~InputReaderThread();
+
+private:
+ uint32_t mFoo;
+ sp<InputReaderInterface> mReader;
+
+ virtual bool threadLoop();
+};
+
+
+/* Represents the state of a single input device. */
+class InputDevice {
+public:
+ InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
+ const InputDeviceIdentifier& identifier, uint32_t classes);
+ ~InputDevice();
+
+ inline InputReaderContext* getContext() { return mContext; }
+ inline int32_t getId() { return mId; }
+ inline int32_t getGeneration() { return mGeneration; }
+ inline const String8& getName() { return mIdentifier.name; }
+ inline uint32_t getClasses() { return mClasses; }
+ inline uint32_t getSources() { return mSources; }
+
+ inline bool isExternal() { return mIsExternal; }
+ inline void setExternal(bool external) { mIsExternal = external; }
+
+ inline bool isIgnored() { return mMappers.isEmpty(); }
+
+ void dump(String8& dump);
+ void addMapper(InputMapper* mapper);
+ void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ void reset(nsecs_t when);
+ void process(const RawEvent* rawEvents, size_t count);
+ void timeoutExpired(nsecs_t when);
+
+ void getDeviceInfo(InputDeviceInfo* outDeviceInfo);
+ int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+ bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+ void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, int32_t token);
+ void cancelVibrate(int32_t token);
+
+ int32_t getMetaState();
+
+ void fadePointer();
+
+ void bumpGeneration();
+
+ void notifyReset(nsecs_t when);
+
+ inline const PropertyMap& getConfiguration() { return mConfiguration; }
+ inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+ bool hasKey(int32_t code) {
+ return getEventHub()->hasScanCode(mId, code);
+ }
+
+ bool hasAbsoluteAxis(int32_t code) {
+ RawAbsoluteAxisInfo info;
+ getEventHub()->getAbsoluteAxisInfo(mId, code, &info);
+ return info.valid;
+ }
+
+ bool isKeyPressed(int32_t code) {
+ return getEventHub()->getScanCodeState(mId, code) == AKEY_STATE_DOWN;
+ }
+
+ int32_t getAbsoluteAxisValue(int32_t code) {
+ int32_t value;
+ getEventHub()->getAbsoluteAxisValue(mId, code, &value);
+ return value;
+ }
+
+private:
+ InputReaderContext* mContext;
+ int32_t mId;
+ int32_t mGeneration;
+ InputDeviceIdentifier mIdentifier;
+ String8 mAlias;
+ uint32_t mClasses;
+
+ Vector<InputMapper*> mMappers;
+
+ uint32_t mSources;
+ bool mIsExternal;
+ bool mDropUntilNextSync;
+
+ typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+ int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
+
+ PropertyMap mConfiguration;
+};
+
+
+/* Keeps track of the state of mouse or touch pad buttons. */
+class CursorButtonAccumulator {
+public:
+ CursorButtonAccumulator();
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+
+ uint32_t getButtonState() const;
+
+private:
+ bool mBtnLeft;
+ bool mBtnRight;
+ bool mBtnMiddle;
+ bool mBtnBack;
+ bool mBtnSide;
+ bool mBtnForward;
+ bool mBtnExtra;
+ bool mBtnTask;
+
+ void clearButtons();
+};
+
+
+/* Keeps track of cursor movements. */
+
+class CursorMotionAccumulator {
+public:
+ CursorMotionAccumulator();
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+
+ inline int32_t getRelativeX() const { return mRelX; }
+ inline int32_t getRelativeY() const { return mRelY; }
+
+private:
+ int32_t mRelX;
+ int32_t mRelY;
+
+ void clearRelativeAxes();
+};
+
+
+/* Keeps track of cursor scrolling motions. */
+
+class CursorScrollAccumulator {
+public:
+ CursorScrollAccumulator();
+ void configure(InputDevice* device);
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+
+ inline bool haveRelativeVWheel() const { return mHaveRelWheel; }
+ inline bool haveRelativeHWheel() const { return mHaveRelHWheel; }
+
+ inline int32_t getRelativeX() const { return mRelX; }
+ inline int32_t getRelativeY() const { return mRelY; }
+ inline int32_t getRelativeVWheel() const { return mRelWheel; }
+ inline int32_t getRelativeHWheel() const { return mRelHWheel; }
+
+private:
+ bool mHaveRelWheel;
+ bool mHaveRelHWheel;
+
+ int32_t mRelX;
+ int32_t mRelY;
+ int32_t mRelWheel;
+ int32_t mRelHWheel;
+
+ void clearRelativeAxes();
+};
+
+
+/* Keeps track of the state of touch, stylus and tool buttons. */
+class TouchButtonAccumulator {
+public:
+ TouchButtonAccumulator();
+ void configure(InputDevice* device);
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+
+ uint32_t getButtonState() const;
+ int32_t getToolType() const;
+ bool isToolActive() const;
+ bool isHovering() const;
+ bool hasStylus() const;
+
+private:
+ bool mHaveBtnTouch;
+ bool mHaveStylus;
+
+ bool mBtnTouch;
+ bool mBtnStylus;
+ bool mBtnStylus2;
+ bool mBtnToolFinger;
+ bool mBtnToolPen;
+ bool mBtnToolRubber;
+ bool mBtnToolBrush;
+ bool mBtnToolPencil;
+ bool mBtnToolAirbrush;
+ bool mBtnToolMouse;
+ bool mBtnToolLens;
+ bool mBtnToolDoubleTap;
+ bool mBtnToolTripleTap;
+ bool mBtnToolQuadTap;
+
+ void clearButtons();
+};
+
+
+/* Raw axis information from the driver. */
+struct RawPointerAxes {
+ RawAbsoluteAxisInfo x;
+ RawAbsoluteAxisInfo y;
+ RawAbsoluteAxisInfo pressure;
+ RawAbsoluteAxisInfo touchMajor;
+ RawAbsoluteAxisInfo touchMinor;
+ RawAbsoluteAxisInfo toolMajor;
+ RawAbsoluteAxisInfo toolMinor;
+ RawAbsoluteAxisInfo orientation;
+ RawAbsoluteAxisInfo distance;
+ RawAbsoluteAxisInfo tiltX;
+ RawAbsoluteAxisInfo tiltY;
+ RawAbsoluteAxisInfo trackingId;
+ RawAbsoluteAxisInfo slot;
+
+ RawPointerAxes();
+ void clear();
+};
+
+
+/* Raw data for a collection of pointers including a pointer id mapping table. */
+struct RawPointerData {
+ struct Pointer {
+ uint32_t id;
+ int32_t x;
+ int32_t y;
+ int32_t pressure;
+ int32_t touchMajor;
+ int32_t touchMinor;
+ int32_t toolMajor;
+ int32_t toolMinor;
+ int32_t orientation;
+ int32_t distance;
+ int32_t tiltX;
+ int32_t tiltY;
+ int32_t toolType; // a fully decoded AMOTION_EVENT_TOOL_TYPE constant
+ bool isHovering;
+ };
+
+ uint32_t pointerCount;
+ Pointer pointers[MAX_POINTERS];
+ BitSet32 hoveringIdBits, touchingIdBits;
+ uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+ RawPointerData();
+ void clear();
+ void copyFrom(const RawPointerData& other);
+ void getCentroidOfTouchingPointers(float* outX, float* outY) const;
+
+ inline void markIdBit(uint32_t id, bool isHovering) {
+ if (isHovering) {
+ hoveringIdBits.markBit(id);
+ } else {
+ touchingIdBits.markBit(id);
+ }
+ }
+
+ inline void clearIdBits() {
+ hoveringIdBits.clear();
+ touchingIdBits.clear();
+ }
+
+ inline const Pointer& pointerForId(uint32_t id) const {
+ return pointers[idToIndex[id]];
+ }
+
+ inline bool isHovering(uint32_t pointerIndex) {
+ return pointers[pointerIndex].isHovering;
+ }
+};
+
+
+/* Cooked data for a collection of pointers including a pointer id mapping table. */
+struct CookedPointerData {
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ BitSet32 hoveringIdBits, touchingIdBits;
+ uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+ CookedPointerData();
+ void clear();
+ void copyFrom(const CookedPointerData& other);
+
+ inline const PointerCoords& pointerCoordsForId(uint32_t id) const {
+ return pointerCoords[idToIndex[id]];
+ }
+
+ inline bool isHovering(uint32_t pointerIndex) {
+ return hoveringIdBits.hasBit(pointerProperties[pointerIndex].id);
+ }
+};
+
+
+/* Keeps track of the state of single-touch protocol. */
+class SingleTouchMotionAccumulator {
+public:
+ SingleTouchMotionAccumulator();
+
+ void process(const RawEvent* rawEvent);
+ void reset(InputDevice* device);
+
+ inline int32_t getAbsoluteX() const { return mAbsX; }
+ inline int32_t getAbsoluteY() const { return mAbsY; }
+ inline int32_t getAbsolutePressure() const { return mAbsPressure; }
+ inline int32_t getAbsoluteToolWidth() const { return mAbsToolWidth; }
+ inline int32_t getAbsoluteDistance() const { return mAbsDistance; }
+ inline int32_t getAbsoluteTiltX() const { return mAbsTiltX; }
+ inline int32_t getAbsoluteTiltY() const { return mAbsTiltY; }
+
+private:
+ int32_t mAbsX;
+ int32_t mAbsY;
+ int32_t mAbsPressure;
+ int32_t mAbsToolWidth;
+ int32_t mAbsDistance;
+ int32_t mAbsTiltX;
+ int32_t mAbsTiltY;
+
+ void clearAbsoluteAxes();
+};
+
+
+/* Keeps track of the state of multi-touch protocol. */
+class MultiTouchMotionAccumulator {
+public:
+ class Slot {
+ public:
+ inline bool isInUse() const { return mInUse; }
+ inline int32_t getX() const { return mAbsMTPositionX; }
+ inline int32_t getY() const { return mAbsMTPositionY; }
+ inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; }
+ inline int32_t getTouchMinor() const {
+ return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor; }
+ inline int32_t getToolMajor() const { return mAbsMTWidthMajor; }
+ inline int32_t getToolMinor() const {
+ return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor; }
+ inline int32_t getOrientation() const { return mAbsMTOrientation; }
+ inline int32_t getTrackingId() const { return mAbsMTTrackingId; }
+ inline int32_t getPressure() const { return mAbsMTPressure; }
+ inline int32_t getDistance() const { return mAbsMTDistance; }
+ inline int32_t getToolType() const;
+
+ private:
+ friend class MultiTouchMotionAccumulator;
+
+ bool mInUse;
+ bool mHaveAbsMTTouchMinor;
+ bool mHaveAbsMTWidthMinor;
+ bool mHaveAbsMTToolType;
+
+ int32_t mAbsMTPositionX;
+ int32_t mAbsMTPositionY;
+ int32_t mAbsMTTouchMajor;
+ int32_t mAbsMTTouchMinor;
+ int32_t mAbsMTWidthMajor;
+ int32_t mAbsMTWidthMinor;
+ int32_t mAbsMTOrientation;
+ int32_t mAbsMTTrackingId;
+ int32_t mAbsMTPressure;
+ int32_t mAbsMTDistance;
+ int32_t mAbsMTToolType;
+
+ Slot();
+ void clear();
+ };
+
+ MultiTouchMotionAccumulator();
+ ~MultiTouchMotionAccumulator();
+
+ void configure(InputDevice* device, size_t slotCount, bool usingSlotsProtocol);
+ void reset(InputDevice* device);
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+ bool hasStylus() const;
+
+ inline size_t getSlotCount() const { return mSlotCount; }
+ inline const Slot* getSlot(size_t index) const { return &mSlots[index]; }
+
+private:
+ int32_t mCurrentSlot;
+ Slot* mSlots;
+ size_t mSlotCount;
+ bool mUsingSlotsProtocol;
+ bool mHaveStylus;
+
+ void clearSlots(int32_t initialSlot);
+};
+
+
+/* An input mapper transforms raw input events into cooked event data.
+ * A single input device can have multiple associated input mappers in order to interpret
+ * different classes of events.
+ *
+ * InputMapper lifecycle:
+ * - create
+ * - configure with 0 changes
+ * - reset
+ * - process, process, process (may occasionally reconfigure with non-zero changes or reset)
+ * - reset
+ * - destroy
+ */
+class InputMapper {
+public:
+ InputMapper(InputDevice* device);
+ virtual ~InputMapper();
+
+ inline InputDevice* getDevice() { return mDevice; }
+ inline int32_t getDeviceId() { return mDevice->getId(); }
+ inline const String8 getDeviceName() { return mDevice->getName(); }
+ inline InputReaderContext* getContext() { return mContext; }
+ inline InputReaderPolicyInterface* getPolicy() { return mContext->getPolicy(); }
+ inline InputListenerInterface* getListener() { return mContext->getListener(); }
+ inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+ virtual uint32_t getSources() = 0;
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent) = 0;
+ virtual void timeoutExpired(nsecs_t when);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
+
+ virtual int32_t getMetaState();
+
+ virtual void fadePointer();
+
+protected:
+ InputDevice* mDevice;
+ InputReaderContext* mContext;
+
+ status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
+ void bumpGeneration();
+
+ static void dumpRawAbsoluteAxisInfo(String8& dump,
+ const RawAbsoluteAxisInfo& axis, const char* name);
+};
+
+
+class SwitchInputMapper : public InputMapper {
+public:
+ SwitchInputMapper(InputDevice* device);
+ virtual ~SwitchInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+
+private:
+ uint32_t mUpdatedSwitchValues;
+ uint32_t mUpdatedSwitchMask;
+
+ void processSwitch(int32_t switchCode, int32_t switchValue);
+ void sync(nsecs_t when);
+};
+
+
+class VibratorInputMapper : public InputMapper {
+public:
+ VibratorInputMapper(InputDevice* device);
+ virtual ~VibratorInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
+ virtual void timeoutExpired(nsecs_t when);
+ virtual void dump(String8& dump);
+
+private:
+ bool mVibrating;
+ nsecs_t mPattern[MAX_VIBRATE_PATTERN_SIZE];
+ size_t mPatternSize;
+ ssize_t mRepeat;
+ int32_t mToken;
+ ssize_t mIndex;
+ nsecs_t mNextStepTime;
+
+ void nextStep();
+ void stopVibrating();
+};
+
+
+class KeyboardInputMapper : public InputMapper {
+public:
+ KeyboardInputMapper(InputDevice* device, uint32_t source, int32_t keyboardType);
+ virtual ~KeyboardInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual int32_t getMetaState();
+
+private:
+ struct KeyDown {
+ int32_t keyCode;
+ int32_t scanCode;
+ };
+
+ uint32_t mSource;
+ int32_t mKeyboardType;
+
+ int32_t mOrientation; // orientation for dpad keys
+
+ Vector<KeyDown> mKeyDowns; // keys that are down
+ int32_t mMetaState;
+ nsecs_t mDownTime; // time of most recent key down
+
+ int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none
+
+ struct LedState {
+ bool avail; // led is available
+ bool on; // we think the led is currently on
+ };
+ LedState mCapsLockLedState;
+ LedState mNumLockLedState;
+ LedState mScrollLockLedState;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ bool hasAssociatedDisplay;
+ bool orientationAware;
+ } mParameters;
+
+ void configureParameters();
+ void dumpParameters(String8& dump);
+
+ bool isKeyboardOrGamepadKey(int32_t scanCode);
+
+ void processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode,
+ uint32_t policyFlags);
+
+ ssize_t findKeyDown(int32_t scanCode);
+
+ void resetLedState();
+ void initializeLedState(LedState& ledState, int32_t led);
+ void updateLedState(bool reset);
+ void updateLedStateForModifier(LedState& ledState, int32_t led,
+ int32_t modifier, bool reset);
+};
+
+
+class CursorInputMapper : public InputMapper {
+public:
+ CursorInputMapper(InputDevice* device);
+ virtual ~CursorInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+
+ virtual void fadePointer();
+
+private:
+ // Amount that trackball needs to move in order to generate a key event.
+ static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ enum Mode {
+ MODE_POINTER,
+ MODE_NAVIGATION,
+ };
+
+ Mode mode;
+ bool hasAssociatedDisplay;
+ bool orientationAware;
+ } mParameters;
+
+ CursorButtonAccumulator mCursorButtonAccumulator;
+ CursorMotionAccumulator mCursorMotionAccumulator;
+ CursorScrollAccumulator mCursorScrollAccumulator;
+
+ int32_t mSource;
+ float mXScale;
+ float mYScale;
+ float mXPrecision;
+ float mYPrecision;
+
+ float mVWheelScale;
+ float mHWheelScale;
+
+ // Velocity controls for mouse pointer and wheel movements.
+ // The controls for X and Y wheel movements are separate to keep them decoupled.
+ VelocityControl mPointerVelocityControl;
+ VelocityControl mWheelXVelocityControl;
+ VelocityControl mWheelYVelocityControl;
+
+ int32_t mOrientation;
+
+ sp<PointerControllerInterface> mPointerController;
+
+ int32_t mButtonState;
+ nsecs_t mDownTime;
+
+ void configureParameters();
+ void dumpParameters(String8& dump);
+
+ void sync(nsecs_t when);
+};
+
+
+class TouchInputMapper : public InputMapper {
+public:
+ TouchInputMapper(InputDevice* device);
+ virtual ~TouchInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual void fadePointer();
+ virtual void timeoutExpired(nsecs_t when);
+
+protected:
+ CursorButtonAccumulator mCursorButtonAccumulator;
+ CursorScrollAccumulator mCursorScrollAccumulator;
+ TouchButtonAccumulator mTouchButtonAccumulator;
+
+ struct VirtualKey {
+ int32_t keyCode;
+ int32_t scanCode;
+ uint32_t flags;
+
+ // computed hit box, specified in touch screen coords based on known display size
+ int32_t hitLeft;
+ int32_t hitTop;
+ int32_t hitRight;
+ int32_t hitBottom;
+
+ inline bool isHit(int32_t x, int32_t y) const {
+ return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom;
+ }
+ };
+
+ // Input sources and device mode.
+ uint32_t mSource;
+
+ enum DeviceMode {
+ DEVICE_MODE_DISABLED, // input is disabled
+ DEVICE_MODE_DIRECT, // direct mapping (touchscreen)
+ DEVICE_MODE_UNSCALED, // unscaled mapping (touchpad)
+ DEVICE_MODE_NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
+ DEVICE_MODE_POINTER, // pointer mapping (pointer)
+ };
+ DeviceMode mDeviceMode;
+
+ // The reader's configuration.
+ InputReaderConfiguration mConfig;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ enum DeviceType {
+ DEVICE_TYPE_TOUCH_SCREEN,
+ DEVICE_TYPE_TOUCH_PAD,
+ DEVICE_TYPE_TOUCH_NAVIGATION,
+ DEVICE_TYPE_POINTER,
+ };
+
+ DeviceType deviceType;
+ bool hasAssociatedDisplay;
+ bool associatedDisplayIsExternal;
+ bool orientationAware;
+
+ enum GestureMode {
+ GESTURE_MODE_POINTER,
+ GESTURE_MODE_SPOTS,
+ };
+ GestureMode gestureMode;
+ } mParameters;
+
+ // Immutable calibration parameters in parsed form.
+ struct Calibration {
+ // Size
+ enum SizeCalibration {
+ SIZE_CALIBRATION_DEFAULT,
+ SIZE_CALIBRATION_NONE,
+ SIZE_CALIBRATION_GEOMETRIC,
+ SIZE_CALIBRATION_DIAMETER,
+ SIZE_CALIBRATION_BOX,
+ SIZE_CALIBRATION_AREA,
+ };
+
+ SizeCalibration sizeCalibration;
+
+ bool haveSizeScale;
+ float sizeScale;
+ bool haveSizeBias;
+ float sizeBias;
+ bool haveSizeIsSummed;
+ bool sizeIsSummed;
+
+ // Pressure
+ enum PressureCalibration {
+ PRESSURE_CALIBRATION_DEFAULT,
+ PRESSURE_CALIBRATION_NONE,
+ PRESSURE_CALIBRATION_PHYSICAL,
+ PRESSURE_CALIBRATION_AMPLITUDE,
+ };
+
+ PressureCalibration pressureCalibration;
+ bool havePressureScale;
+ float pressureScale;
+
+ // Orientation
+ enum OrientationCalibration {
+ ORIENTATION_CALIBRATION_DEFAULT,
+ ORIENTATION_CALIBRATION_NONE,
+ ORIENTATION_CALIBRATION_INTERPOLATED,
+ ORIENTATION_CALIBRATION_VECTOR,
+ };
+
+ OrientationCalibration orientationCalibration;
+
+ // Distance
+ enum DistanceCalibration {
+ DISTANCE_CALIBRATION_DEFAULT,
+ DISTANCE_CALIBRATION_NONE,
+ DISTANCE_CALIBRATION_SCALED,
+ };
+
+ DistanceCalibration distanceCalibration;
+ bool haveDistanceScale;
+ float distanceScale;
+
+ enum CoverageCalibration {
+ COVERAGE_CALIBRATION_DEFAULT,
+ COVERAGE_CALIBRATION_NONE,
+ COVERAGE_CALIBRATION_BOX,
+ };
+
+ CoverageCalibration coverageCalibration;
+
+ inline void applySizeScaleAndBias(float* outSize) const {
+ if (haveSizeScale) {
+ *outSize *= sizeScale;
+ }
+ if (haveSizeBias) {
+ *outSize += sizeBias;
+ }
+ }
+ } mCalibration;
+
+ // Raw pointer axis information from the driver.
+ RawPointerAxes mRawPointerAxes;
+
+ // Raw pointer sample data.
+ RawPointerData mCurrentRawPointerData;
+ RawPointerData mLastRawPointerData;
+
+ // Cooked pointer sample data.
+ CookedPointerData mCurrentCookedPointerData;
+ CookedPointerData mLastCookedPointerData;
+
+ // Button state.
+ int32_t mCurrentButtonState;
+ int32_t mLastButtonState;
+
+ // Scroll state.
+ int32_t mCurrentRawVScroll;
+ int32_t mCurrentRawHScroll;
+
+ // Id bits used to differentiate fingers, stylus and mouse tools.
+ BitSet32 mCurrentFingerIdBits; // finger or unknown
+ BitSet32 mLastFingerIdBits;
+ BitSet32 mCurrentStylusIdBits; // stylus or eraser
+ BitSet32 mLastStylusIdBits;
+ BitSet32 mCurrentMouseIdBits; // mouse or lens
+ BitSet32 mLastMouseIdBits;
+
+ // True if we sent a HOVER_ENTER event.
+ bool mSentHoverEnter;
+
+ // The time the primary pointer last went down.
+ nsecs_t mDownTime;
+
+ // The pointer controller, or null if the device is not a pointer.
+ sp<PointerControllerInterface> mPointerController;
+
+ Vector<VirtualKey> mVirtualKeys;
+
+ virtual void configureParameters();
+ virtual void dumpParameters(String8& dump);
+ virtual void configureRawPointerAxes();
+ virtual void dumpRawPointerAxes(String8& dump);
+ virtual void configureSurface(nsecs_t when, bool* outResetNeeded);
+ virtual void dumpSurface(String8& dump);
+ virtual void configureVirtualKeys();
+ virtual void dumpVirtualKeys(String8& dump);
+ virtual void parseCalibration();
+ virtual void resolveCalibration();
+ virtual void dumpCalibration(String8& dump);
+ virtual bool hasStylus() const = 0;
+
+ virtual void syncTouch(nsecs_t when, bool* outHavePointerIds) = 0;
+
+private:
+ // The current viewport.
+ // The components of the viewport are specified in the display's rotated orientation.
+ DisplayViewport mViewport;
+
+ // The surface orientation, width and height set by configureSurface().
+ // The width and height are derived from the viewport but are specified
+ // in the natural orientation.
+ // The surface origin specifies how the surface coordinates should be translated
+ // to align with the logical display coordinate space.
+ // The orientation may be different from the viewport orientation as it specifies
+ // the rotation of the surface coordinates required to produce the viewport's
+ // requested orientation, so it will depend on whether the device is orientation aware.
+ int32_t mSurfaceWidth;
+ int32_t mSurfaceHeight;
+ int32_t mSurfaceLeft;
+ int32_t mSurfaceTop;
+ int32_t mSurfaceOrientation;
+
+ // Translation and scaling factors, orientation-independent.
+ float mXTranslate;
+ float mXScale;
+ float mXPrecision;
+
+ float mYTranslate;
+ float mYScale;
+ float mYPrecision;
+
+ float mGeometricScale;
+
+ float mPressureScale;
+
+ float mSizeScale;
+
+ float mOrientationScale;
+
+ float mDistanceScale;
+
+ bool mHaveTilt;
+ float mTiltXCenter;
+ float mTiltXScale;
+ float mTiltYCenter;
+ float mTiltYScale;
+
+ // Oriented motion ranges for input device info.
+ struct OrientedRanges {
+ InputDeviceInfo::MotionRange x;
+ InputDeviceInfo::MotionRange y;
+ InputDeviceInfo::MotionRange pressure;
+
+ bool haveSize;
+ InputDeviceInfo::MotionRange size;
+
+ bool haveTouchSize;
+ InputDeviceInfo::MotionRange touchMajor;
+ InputDeviceInfo::MotionRange touchMinor;
+
+ bool haveToolSize;
+ InputDeviceInfo::MotionRange toolMajor;
+ InputDeviceInfo::MotionRange toolMinor;
+
+ bool haveOrientation;
+ InputDeviceInfo::MotionRange orientation;
+
+ bool haveDistance;
+ InputDeviceInfo::MotionRange distance;
+
+ bool haveTilt;
+ InputDeviceInfo::MotionRange tilt;
+
+ OrientedRanges() {
+ clear();
+ }
+
+ void clear() {
+ haveSize = false;
+ haveTouchSize = false;
+ haveToolSize = false;
+ haveOrientation = false;
+ haveDistance = false;
+ haveTilt = false;
+ }
+ } mOrientedRanges;
+
+ // Oriented dimensions and precision.
+ float mOrientedXPrecision;
+ float mOrientedYPrecision;
+
+ struct CurrentVirtualKeyState {
+ bool down;
+ bool ignored;
+ nsecs_t downTime;
+ int32_t keyCode;
+ int32_t scanCode;
+ } mCurrentVirtualKey;
+
+ // Scale factor for gesture or mouse based pointer movements.
+ float mPointerXMovementScale;
+ float mPointerYMovementScale;
+
+ // Scale factor for gesture based zooming and other freeform motions.
+ float mPointerXZoomScale;
+ float mPointerYZoomScale;
+
+ // The maximum swipe width.
+ float mPointerGestureMaxSwipeWidth;
+
+ struct PointerDistanceHeapElement {
+ uint32_t currentPointerIndex : 8;
+ uint32_t lastPointerIndex : 8;
+ uint64_t distance : 48; // squared distance
+ };
+
+ enum PointerUsage {
+ POINTER_USAGE_NONE,
+ POINTER_USAGE_GESTURES,
+ POINTER_USAGE_STYLUS,
+ POINTER_USAGE_MOUSE,
+ };
+ PointerUsage mPointerUsage;
+
+ struct PointerGesture {
+ enum Mode {
+ // No fingers, button is not pressed.
+ // Nothing happening.
+ NEUTRAL,
+
+ // No fingers, button is not pressed.
+ // Tap detected.
+ // Emits DOWN and UP events at the pointer location.
+ TAP,
+
+ // Exactly one finger dragging following a tap.
+ // Pointer follows the active finger.
+ // Emits DOWN, MOVE and UP events at the pointer location.
+ //
+ // Detect double-taps when the finger goes up while in TAP_DRAG mode.
+ TAP_DRAG,
+
+ // Button is pressed.
+ // Pointer follows the active finger if there is one. Other fingers are ignored.
+ // Emits DOWN, MOVE and UP events at the pointer location.
+ BUTTON_CLICK_OR_DRAG,
+
+ // Exactly one finger, button is not pressed.
+ // Pointer follows the active finger.
+ // Emits HOVER_MOVE events at the pointer location.
+ //
+ // Detect taps when the finger goes up while in HOVER mode.
+ HOVER,
+
+ // Exactly two fingers but neither have moved enough to clearly indicate
+ // whether a swipe or freeform gesture was intended. We consider the
+ // pointer to be pressed so this enables clicking or long-pressing on buttons.
+ // Pointer does not move.
+ // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate.
+ PRESS,
+
+ // Exactly two fingers moving in the same direction, button is not pressed.
+ // Pointer does not move.
+ // Emits DOWN, MOVE and UP events with a single pointer coordinate that
+ // follows the midpoint between both fingers.
+ SWIPE,
+
+ // Two or more fingers moving in arbitrary directions, button is not pressed.
+ // Pointer does not move.
+ // Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow
+ // each finger individually relative to the initial centroid of the finger.
+ FREEFORM,
+
+ // Waiting for quiet time to end before starting the next gesture.
+ QUIET,
+ };
+
+ // Time the first finger went down.
+ nsecs_t firstTouchTime;
+
+ // The active pointer id from the raw touch data.
+ int32_t activeTouchId; // -1 if none
+
+ // The active pointer id from the gesture last delivered to the application.
+ int32_t activeGestureId; // -1 if none
+
+ // Pointer coords and ids for the current and previous pointer gesture.
+ Mode currentGestureMode;
+ BitSet32 currentGestureIdBits;
+ uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
+ PointerProperties currentGestureProperties[MAX_POINTERS];
+ PointerCoords currentGestureCoords[MAX_POINTERS];
+
+ Mode lastGestureMode;
+ BitSet32 lastGestureIdBits;
+ uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
+ PointerProperties lastGestureProperties[MAX_POINTERS];
+ PointerCoords lastGestureCoords[MAX_POINTERS];
+
+ // Time the pointer gesture last went down.
+ nsecs_t downTime;
+
+ // Time when the pointer went down for a TAP.
+ nsecs_t tapDownTime;
+
+ // Time when the pointer went up for a TAP.
+ nsecs_t tapUpTime;
+
+ // Location of initial tap.
+ float tapX, tapY;
+
+ // Time we started waiting for quiescence.
+ nsecs_t quietTime;
+
+ // Reference points for multitouch gestures.
+ float referenceTouchX; // reference touch X/Y coordinates in surface units
+ float referenceTouchY;
+ float referenceGestureX; // reference gesture X/Y coordinates in pixels
+ float referenceGestureY;
+
+ // Distance that each pointer has traveled which has not yet been
+ // subsumed into the reference gesture position.
+ BitSet32 referenceIdBits;
+ struct Delta {
+ float dx, dy;
+ };
+ Delta referenceDeltas[MAX_POINTER_ID + 1];
+
+ // Describes how touch ids are mapped to gesture ids for freeform gestures.
+ uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
+
+ // A velocity tracker for determining whether to switch active pointers during drags.
+ VelocityTracker velocityTracker;
+
+ void reset() {
+ firstTouchTime = LLONG_MIN;
+ activeTouchId = -1;
+ activeGestureId = -1;
+ currentGestureMode = NEUTRAL;
+ currentGestureIdBits.clear();
+ lastGestureMode = NEUTRAL;
+ lastGestureIdBits.clear();
+ downTime = 0;
+ velocityTracker.clear();
+ resetTap();
+ resetQuietTime();
+ }
+
+ void resetTap() {
+ tapDownTime = LLONG_MIN;
+ tapUpTime = LLONG_MIN;
+ }
+
+ void resetQuietTime() {
+ quietTime = LLONG_MIN;
+ }
+ } mPointerGesture;
+
+ struct PointerSimple {
+ PointerCoords currentCoords;
+ PointerProperties currentProperties;
+ PointerCoords lastCoords;
+ PointerProperties lastProperties;
+
+ // True if the pointer is down.
+ bool down;
+
+ // True if the pointer is hovering.
+ bool hovering;
+
+ // Time the pointer last went down.
+ nsecs_t downTime;
+
+ void reset() {
+ currentCoords.clear();
+ currentProperties.clear();
+ lastCoords.clear();
+ lastProperties.clear();
+ down = false;
+ hovering = false;
+ downTime = 0;
+ }
+ } mPointerSimple;
+
+ // The pointer and scroll velocity controls.
+ VelocityControl mPointerVelocityControl;
+ VelocityControl mWheelXVelocityControl;
+ VelocityControl mWheelYVelocityControl;
+
+ void sync(nsecs_t when);
+
+ bool consumeRawTouches(nsecs_t when, uint32_t policyFlags);
+ void dispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
+ int32_t keyEventAction, int32_t keyEventFlags);
+
+ void dispatchTouches(nsecs_t when, uint32_t policyFlags);
+ void dispatchHoverExit(nsecs_t when, uint32_t policyFlags);
+ void dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags);
+ void cookPointerData();
+
+ void dispatchPointerUsage(nsecs_t when, uint32_t policyFlags, PointerUsage pointerUsage);
+ void abortPointerUsage(nsecs_t when, uint32_t policyFlags);
+
+ void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout);
+ void abortPointerGestures(nsecs_t when, uint32_t policyFlags);
+ bool preparePointerGestures(nsecs_t when,
+ bool* outCancelPreviousGesture, bool* outFinishPreviousGesture,
+ bool isTimeout);
+
+ void dispatchPointerStylus(nsecs_t when, uint32_t policyFlags);
+ void abortPointerStylus(nsecs_t when, uint32_t policyFlags);
+
+ void dispatchPointerMouse(nsecs_t when, uint32_t policyFlags);
+ void abortPointerMouse(nsecs_t when, uint32_t policyFlags);
+
+ void dispatchPointerSimple(nsecs_t when, uint32_t policyFlags,
+ bool down, bool hovering);
+ void abortPointerSimple(nsecs_t when, uint32_t policyFlags);
+
+ // Dispatches a motion event.
+ // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
+ // method will take care of setting the index and transmuting the action to DOWN or UP
+ // it is the first / last pointer to go down / up.
+ void dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags,
+ const PointerProperties* properties, const PointerCoords* coords,
+ const uint32_t* idToIndex, BitSet32 idBits,
+ int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime);
+
+ // Updates pointer coords and properties for pointers with specified ids that have moved.
+ // Returns true if any of them changed.
+ bool updateMovedPointers(const PointerProperties* inProperties,
+ const PointerCoords* inCoords, const uint32_t* inIdToIndex,
+ PointerProperties* outProperties, PointerCoords* outCoords,
+ const uint32_t* outIdToIndex, BitSet32 idBits) const;
+
+ bool isPointInsideSurface(int32_t x, int32_t y);
+ const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y);
+
+ void assignPointerIds();
+};
+
+
+class SingleTouchInputMapper : public TouchInputMapper {
+public:
+ SingleTouchInputMapper(InputDevice* device);
+ virtual ~SingleTouchInputMapper();
+
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+protected:
+ virtual void syncTouch(nsecs_t when, bool* outHavePointerIds);
+ virtual void configureRawPointerAxes();
+ virtual bool hasStylus() const;
+
+private:
+ SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
+};
+
+
+class MultiTouchInputMapper : public TouchInputMapper {
+public:
+ MultiTouchInputMapper(InputDevice* device);
+ virtual ~MultiTouchInputMapper();
+
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+protected:
+ virtual void syncTouch(nsecs_t when, bool* outHavePointerIds);
+ virtual void configureRawPointerAxes();
+ virtual bool hasStylus() const;
+
+private:
+ MultiTouchMotionAccumulator mMultiTouchMotionAccumulator;
+
+ // Specifies the pointer id bits that are in use, and their associated tracking id.
+ BitSet32 mPointerIdBits;
+ int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
+};
+
+
+class JoystickInputMapper : public InputMapper {
+public:
+ JoystickInputMapper(InputDevice* device);
+ virtual ~JoystickInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+private:
+ struct Axis {
+ RawAbsoluteAxisInfo rawAxisInfo;
+ AxisInfo axisInfo;
+
+ bool explicitlyMapped; // true if the axis was explicitly assigned an axis id
+
+ float scale; // scale factor from raw to normalized values
+ float offset; // offset to add after scaling for normalization
+ float highScale; // scale factor from raw to normalized values of high split
+ float highOffset; // offset to add after scaling for normalization of high split
+
+ float min; // normalized inclusive minimum
+ float max; // normalized inclusive maximum
+ float flat; // normalized flat region size
+ float fuzz; // normalized error tolerance
+ float resolution; // normalized resolution in units/mm
+
+ float filter; // filter out small variations of this size
+ float currentValue; // current value
+ float newValue; // most recent value
+ float highCurrentValue; // current value of high split
+ float highNewValue; // most recent value of high split
+
+ void initialize(const RawAbsoluteAxisInfo& rawAxisInfo, const AxisInfo& axisInfo,
+ bool explicitlyMapped, float scale, float offset,
+ float highScale, float highOffset,
+ float min, float max, float flat, float fuzz, float resolution) {
+ this->rawAxisInfo = rawAxisInfo;
+ this->axisInfo = axisInfo;
+ this->explicitlyMapped = explicitlyMapped;
+ this->scale = scale;
+ this->offset = offset;
+ this->highScale = highScale;
+ this->highOffset = highOffset;
+ this->min = min;
+ this->max = max;
+ this->flat = flat;
+ this->fuzz = fuzz;
+ this->resolution = resolution;
+ this->filter = 0;
+ resetValue();
+ }
+
+ void resetValue() {
+ this->currentValue = 0;
+ this->newValue = 0;
+ this->highCurrentValue = 0;
+ this->highNewValue = 0;
+ }
+ };
+
+ // Axes indexed by raw ABS_* axis index.
+ KeyedVector<int32_t, Axis> mAxes;
+
+ void sync(nsecs_t when, bool force);
+
+ bool haveAxis(int32_t axisId);
+ void pruneAxes(bool ignoreExplicitlyMappedAxes);
+ bool filterAxes(bool force);
+
+ static bool hasValueChangedSignificantly(float filter,
+ float newValue, float currentValue, float min, float max);
+ static bool hasMovedNearerToValueWithinFilteredRange(float filter,
+ float newValue, float currentValue, float thresholdValue);
+
+ static bool isCenteredAxis(int32_t axis);
+ static int32_t getCompatAxis(int32_t axis);
+
+ static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info);
+ static void setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis,
+ float value);
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_READER_H
diff --git a/widget/gonk/libui/InputTransport.cpp b/widget/gonk/libui/InputTransport.cpp
new file mode 100644
index 000000000..3f0fcb047
--- /dev/null
+++ b/widget/gonk/libui/InputTransport.cpp
@@ -0,0 +1,957 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// Provides a shared memory transport for input events.
+//
+#define LOG_TAG "InputTransport"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about channel messages (send message, receive message)
+#define DEBUG_CHANNEL_MESSAGES 0
+
+// Log debug messages whenever InputChannel objects are created/destroyed
+#define DEBUG_CHANNEL_LIFECYCLE 0
+
+// Log debug messages about transport actions
+#define DEBUG_TRANSPORT_ACTIONS 0
+
+// Log debug messages about touch event resampling
+#define DEBUG_RESAMPLING 0
+
+
+#include "cutils_log.h"
+#include <cutils/properties.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "InputTransport.h"
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <math.h>
+
+
+namespace android {
+
+// Socket buffer size. The default is typically about 128KB, which is much larger than
+// we really need. So we make it smaller. It just needs to be big enough to hold
+// a few dozen large multi-finger motion events in the case where an application gets
+// behind processing touches.
+static const size_t SOCKET_BUFFER_SIZE = 32 * 1024;
+
+// Nanoseconds per milliseconds.
+static const nsecs_t NANOS_PER_MS = 1000000;
+
+// Latency added during resampling. A few milliseconds doesn't hurt much but
+// reduces the impact of mispredicted touch positions.
+static const nsecs_t RESAMPLE_LATENCY = 5 * NANOS_PER_MS;
+
+// Minimum time difference between consecutive samples before attempting to resample.
+static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
+
+// Maximum time to predict forward from the last known state, to avoid predicting too
+// far into the future. This time is further bounded by 50% of the last time delta.
+static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
+
+template<typename T>
+inline static T min(const T& a, const T& b) {
+ return a < b ? a : b;
+}
+
+inline static float lerp(float a, float b, float alpha) {
+ return a + alpha * (b - a);
+}
+
+// --- InputMessage ---
+
+bool InputMessage::isValid(size_t actualSize) const {
+ if (size() == actualSize) {
+ switch (header.type) {
+ case TYPE_KEY:
+ return true;
+ case TYPE_MOTION:
+ return body.motion.pointerCount > 0
+ && body.motion.pointerCount <= MAX_POINTERS;
+ case TYPE_FINISHED:
+ return true;
+ }
+ }
+ return false;
+}
+
+size_t InputMessage::size() const {
+ switch (header.type) {
+ case TYPE_KEY:
+ return sizeof(Header) + body.key.size();
+ case TYPE_MOTION:
+ return sizeof(Header) + body.motion.size();
+ case TYPE_FINISHED:
+ return sizeof(Header) + body.finished.size();
+ }
+ return sizeof(Header);
+}
+
+
+// --- InputChannel ---
+
+InputChannel::InputChannel(const String8& name, int fd) :
+ mName(name), mFd(fd) {
+#if DEBUG_CHANNEL_LIFECYCLE
+ ALOGD("Input channel constructed: name='%s', fd=%d",
+ mName.string(), fd);
+#endif
+
+ int result = fcntl(mFd, F_SETFL, O_NONBLOCK);
+ LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make socket "
+ "non-blocking. errno=%d", mName.string(), errno);
+}
+
+InputChannel::~InputChannel() {
+#if DEBUG_CHANNEL_LIFECYCLE
+ ALOGD("Input channel destroyed: name='%s', fd=%d",
+ mName.string(), mFd);
+#endif
+
+ ::close(mFd);
+}
+
+status_t InputChannel::openInputChannelPair(const String8& name,
+ sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
+ int sockets[2];
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
+ status_t result = -errno;
+ ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
+ name.string(), errno);
+ outServerChannel.clear();
+ outClientChannel.clear();
+ return result;
+ }
+
+ int bufferSize = SOCKET_BUFFER_SIZE;
+ setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
+ setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
+ setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
+ setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
+
+ String8 serverChannelName = name;
+ serverChannelName.append(" (server)");
+ outServerChannel = new InputChannel(serverChannelName, sockets[0]);
+
+ String8 clientChannelName = name;
+ clientChannelName.append(" (client)");
+ outClientChannel = new InputChannel(clientChannelName, sockets[1]);
+ return OK;
+}
+
+status_t InputChannel::sendMessage(const InputMessage* msg) {
+ size_t msgLength = msg->size();
+ ssize_t nWrite;
+ do {
+ nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
+ } while (nWrite == -1 && errno == EINTR);
+
+ if (nWrite < 0) {
+ int error = errno;
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ error sending message of type %d, errno=%d", mName.string(),
+ msg->header.type, error);
+#endif
+ if (error == EAGAIN || error == EWOULDBLOCK) {
+ return WOULD_BLOCK;
+ }
+ if (error == EPIPE || error == ENOTCONN) {
+ return DEAD_OBJECT;
+ }
+ return -error;
+ }
+
+ if (size_t(nWrite) != msgLength) {
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ error sending message type %d, send was incomplete",
+ mName.string(), msg->header.type);
+#endif
+ return DEAD_OBJECT;
+ }
+
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ sent message of type %d", mName.string(), msg->header.type);
+#endif
+ return OK;
+}
+
+status_t InputChannel::receiveMessage(InputMessage* msg) {
+ ssize_t nRead;
+ do {
+ nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
+ } while (nRead == -1 && errno == EINTR);
+
+ if (nRead < 0) {
+ int error = errno;
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ receive message failed, errno=%d", mName.string(), errno);
+#endif
+ if (error == EAGAIN || error == EWOULDBLOCK) {
+ return WOULD_BLOCK;
+ }
+ if (error == EPIPE || error == ENOTCONN) {
+ return DEAD_OBJECT;
+ }
+ return -error;
+ }
+
+ if (nRead == 0) { // check for EOF
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ receive message failed because peer was closed", mName.string());
+#endif
+ return DEAD_OBJECT;
+ }
+
+ if (!msg->isValid(nRead)) {
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ received invalid message", mName.string());
+#endif
+ return BAD_VALUE;
+ }
+
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ received message of type %d", mName.string(), msg->header.type);
+#endif
+ return OK;
+}
+
+sp<InputChannel> InputChannel::dup() const {
+ int fd = ::dup(getFd());
+ return fd >= 0 ? new InputChannel(getName(), fd) : NULL;
+}
+
+
+// --- InputPublisher ---
+
+InputPublisher::InputPublisher(const sp<InputChannel>& channel) :
+ mChannel(channel) {
+}
+
+InputPublisher::~InputPublisher() {
+}
+
+status_t InputPublisher::publishKeyEvent(
+ uint32_t seq,
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t keyCode,
+ int32_t scanCode,
+ int32_t metaState,
+ int32_t repeatCount,
+ nsecs_t downTime,
+ nsecs_t eventTime) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, "
+ "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
+ "downTime=%lld, eventTime=%lld",
+ mChannel->getName().string(), seq,
+ deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount,
+ downTime, eventTime);
+#endif
+
+ if (!seq) {
+ ALOGE("Attempted to publish a key event with sequence number 0.");
+ return BAD_VALUE;
+ }
+
+ InputMessage msg;
+ msg.header.type = InputMessage::TYPE_KEY;
+ msg.body.key.seq = seq;
+ msg.body.key.deviceId = deviceId;
+ msg.body.key.source = source;
+ msg.body.key.action = action;
+ msg.body.key.flags = flags;
+ msg.body.key.keyCode = keyCode;
+ msg.body.key.scanCode = scanCode;
+ msg.body.key.metaState = metaState;
+ msg.body.key.repeatCount = repeatCount;
+ msg.body.key.downTime = downTime;
+ msg.body.key.eventTime = eventTime;
+ return mChannel->sendMessage(&msg);
+}
+
+status_t InputPublisher::publishMotionEvent(
+ uint32_t seq,
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t edgeFlags,
+ int32_t metaState,
+ int32_t buttonState,
+ float xOffset,
+ float yOffset,
+ float xPrecision,
+ float yPrecision,
+ nsecs_t downTime,
+ nsecs_t eventTime,
+ size_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' publisher ~ publishMotionEvent: seq=%u, deviceId=%d, source=0x%x, "
+ "action=0x%x, flags=0x%x, edgeFlags=0x%x, metaState=0x%x, buttonState=0x%x, "
+ "xOffset=%f, yOffset=%f, "
+ "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, "
+ "pointerCount=%d",
+ mChannel->getName().string(), seq,
+ deviceId, source, action, flags, edgeFlags, metaState, buttonState,
+ xOffset, yOffset, xPrecision, yPrecision, downTime, eventTime, pointerCount);
+#endif
+
+ if (!seq) {
+ ALOGE("Attempted to publish a motion event with sequence number 0.");
+ return BAD_VALUE;
+ }
+
+ if (pointerCount > MAX_POINTERS || pointerCount < 1) {
+ ALOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.",
+ mChannel->getName().string(), pointerCount);
+ return BAD_VALUE;
+ }
+
+ InputMessage msg;
+ msg.header.type = InputMessage::TYPE_MOTION;
+ msg.body.motion.seq = seq;
+ msg.body.motion.deviceId = deviceId;
+ msg.body.motion.source = source;
+ msg.body.motion.action = action;
+ msg.body.motion.flags = flags;
+ msg.body.motion.edgeFlags = edgeFlags;
+ msg.body.motion.metaState = metaState;
+ msg.body.motion.buttonState = buttonState;
+ msg.body.motion.xOffset = xOffset;
+ msg.body.motion.yOffset = yOffset;
+ msg.body.motion.xPrecision = xPrecision;
+ msg.body.motion.yPrecision = yPrecision;
+ msg.body.motion.downTime = downTime;
+ msg.body.motion.eventTime = eventTime;
+ msg.body.motion.pointerCount = pointerCount;
+ for (size_t i = 0; i < pointerCount; i++) {
+ msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
+ msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
+ }
+ return mChannel->sendMessage(&msg);
+}
+
+status_t InputPublisher::receiveFinishedSignal(uint32_t* outSeq, bool* outHandled) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' publisher ~ receiveFinishedSignal",
+ mChannel->getName().string());
+#endif
+
+ InputMessage msg;
+ status_t result = mChannel->receiveMessage(&msg);
+ if (result) {
+ *outSeq = 0;
+ *outHandled = false;
+ return result;
+ }
+ if (msg.header.type != InputMessage::TYPE_FINISHED) {
+ ALOGE("channel '%s' publisher ~ Received unexpected message of type %d from consumer",
+ mChannel->getName().string(), msg.header.type);
+ return UNKNOWN_ERROR;
+ }
+ *outSeq = msg.body.finished.seq;
+ *outHandled = msg.body.finished.handled;
+ return OK;
+}
+
+// --- InputConsumer ---
+
+InputConsumer::InputConsumer(const sp<InputChannel>& channel) :
+ mResampleTouch(isTouchResamplingEnabled()),
+ mChannel(channel), mMsgDeferred(false) {
+}
+
+InputConsumer::~InputConsumer() {
+}
+
+bool InputConsumer::isTouchResamplingEnabled() {
+ char value[PROPERTY_VALUE_MAX];
+ int length = property_get("debug.inputconsumer.resample", value, NULL);
+ if (length > 0) {
+ if (!strcmp("0", value)) {
+ return false;
+ }
+ if (strcmp("1", value)) {
+ ALOGD("Unrecognized property value for 'debug.inputconsumer.resample'. "
+ "Use '1' or '0'.");
+ }
+ }
+ return true;
+}
+
+status_t InputConsumer::consume(InputEventFactoryInterface* factory,
+ bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%lld",
+ mChannel->getName().string(), consumeBatches ? "true" : "false", frameTime);
+#endif
+
+ *outSeq = 0;
+ *outEvent = NULL;
+
+ // Fetch the next input message.
+ // Loop until an event can be returned or no additional events are received.
+ while (!*outEvent) {
+ if (mMsgDeferred) {
+ // mMsg contains a valid input message from the previous call to consume
+ // that has not yet been processed.
+ mMsgDeferred = false;
+ } else {
+ // Receive a fresh message.
+ status_t result = mChannel->receiveMessage(&mMsg);
+ if (result) {
+ // Consume the next batched event unless batches are being held for later.
+ if (consumeBatches || result != WOULD_BLOCK) {
+ result = consumeBatch(factory, frameTime, outSeq, outEvent);
+ if (*outEvent) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
+ mChannel->getName().string(), *outSeq);
+#endif
+ break;
+ }
+ }
+ return result;
+ }
+ }
+
+ switch (mMsg.header.type) {
+ case InputMessage::TYPE_KEY: {
+ KeyEvent* keyEvent = factory->createKeyEvent();
+ if (!keyEvent) return NO_MEMORY;
+
+ initializeKeyEvent(keyEvent, &mMsg);
+ *outSeq = mMsg.body.key.seq;
+ *outEvent = keyEvent;
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
+ mChannel->getName().string(), *outSeq);
+#endif
+ break;
+ }
+
+ case AINPUT_EVENT_TYPE_MOTION: {
+ ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
+ if (batchIndex >= 0) {
+ Batch& batch = mBatches.editItemAt(batchIndex);
+ if (canAddSample(batch, &mMsg)) {
+ batch.samples.push(mMsg);
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ appended to batch event",
+ mChannel->getName().string());
+#endif
+ break;
+ } else {
+ // We cannot append to the batch in progress, so we need to consume
+ // the previous batch right now and defer the new message until later.
+ mMsgDeferred = true;
+ status_t result = consumeSamples(factory,
+ batch, batch.samples.size(), outSeq, outEvent);
+ mBatches.removeAt(batchIndex);
+ if (result) {
+ return result;
+ }
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consumed batch event and "
+ "deferred current event, seq=%u",
+ mChannel->getName().string(), *outSeq);
+#endif
+ break;
+ }
+ }
+
+ // Start a new batch if needed.
+ if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE
+ || mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+ mBatches.push();
+ Batch& batch = mBatches.editTop();
+ batch.samples.push(mMsg);
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ started batch event",
+ mChannel->getName().string());
+#endif
+ break;
+ }
+
+ MotionEvent* motionEvent = factory->createMotionEvent();
+ if (! motionEvent) return NO_MEMORY;
+
+ updateTouchState(&mMsg);
+ initializeMotionEvent(motionEvent, &mMsg);
+ *outSeq = mMsg.body.motion.seq;
+ *outEvent = motionEvent;
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u",
+ mChannel->getName().string(), *outSeq);
+#endif
+ break;
+ }
+
+ default:
+ ALOGE("channel '%s' consumer ~ Received unexpected message of type %d",
+ mChannel->getName().string(), mMsg.header.type);
+ return UNKNOWN_ERROR;
+ }
+ }
+ return OK;
+}
+
+status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory,
+ nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
+ status_t result;
+ for (size_t i = mBatches.size(); i-- > 0; ) {
+ Batch& batch = mBatches.editItemAt(i);
+ if (frameTime < 0) {
+ result = consumeSamples(factory, batch, batch.samples.size(),
+ outSeq, outEvent);
+ mBatches.removeAt(i);
+ return result;
+ }
+
+ nsecs_t sampleTime = frameTime - RESAMPLE_LATENCY;
+ ssize_t split = findSampleNoLaterThan(batch, sampleTime);
+ if (split < 0) {
+ continue;
+ }
+
+ result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
+ const InputMessage* next;
+ if (batch.samples.isEmpty()) {
+ mBatches.removeAt(i);
+ next = NULL;
+ } else {
+ next = &batch.samples.itemAt(0);
+ }
+ if (!result) {
+ resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
+ }
+ return result;
+ }
+
+ return WOULD_BLOCK;
+}
+
+status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory,
+ Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent) {
+ MotionEvent* motionEvent = factory->createMotionEvent();
+ if (! motionEvent) return NO_MEMORY;
+
+ uint32_t chain = 0;
+ for (size_t i = 0; i < count; i++) {
+ InputMessage& msg = batch.samples.editItemAt(i);
+ updateTouchState(&msg);
+ if (i) {
+ SeqChain seqChain;
+ seqChain.seq = msg.body.motion.seq;
+ seqChain.chain = chain;
+ mSeqChains.push(seqChain);
+ addSample(motionEvent, &msg);
+ } else {
+ initializeMotionEvent(motionEvent, &msg);
+ }
+ chain = msg.body.motion.seq;
+ }
+ batch.samples.removeItemsAt(0, count);
+
+ *outSeq = chain;
+ *outEvent = motionEvent;
+ return OK;
+}
+
+void InputConsumer::updateTouchState(InputMessage* msg) {
+ if (!mResampleTouch ||
+ !(msg->body.motion.source & AINPUT_SOURCE_CLASS_POINTER)) {
+ return;
+ }
+
+ int32_t deviceId = msg->body.motion.deviceId;
+ int32_t source = msg->body.motion.source;
+ nsecs_t eventTime = msg->body.motion.eventTime;
+
+ // Update the touch state history to incorporate the new input message.
+ // If the message is in the past relative to the most recently produced resampled
+ // touch, then use the resampled time and coordinates instead.
+ switch (msg->body.motion.action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_DOWN: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index < 0) {
+ mTouchStates.push();
+ index = mTouchStates.size() - 1;
+ }
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ touchState.initialize(deviceId, source);
+ touchState.addHistory(msg);
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_MOVE: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ touchState.addHistory(msg);
+ if (eventTime < touchState.lastResample.eventTime) {
+ rewriteMessage(touchState, msg);
+ } else {
+ touchState.lastResample.idBits.clear();
+ }
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId());
+ rewriteMessage(touchState, msg);
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_UP: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ rewriteMessage(touchState, msg);
+ touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId());
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_SCROLL: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ const TouchState& touchState = mTouchStates.itemAt(index);
+ rewriteMessage(touchState, msg);
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ const TouchState& touchState = mTouchStates.itemAt(index);
+ rewriteMessage(touchState, msg);
+ mTouchStates.removeAt(index);
+ }
+ break;
+ }
+ }
+}
+
+void InputConsumer::rewriteMessage(const TouchState& state, InputMessage* msg) {
+ for (size_t i = 0; i < msg->body.motion.pointerCount; i++) {
+ uint32_t id = msg->body.motion.pointers[i].properties.id;
+ if (state.lastResample.idBits.hasBit(id)) {
+ PointerCoords& msgCoords = msg->body.motion.pointers[i].coords;
+ const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
+#if DEBUG_RESAMPLING
+ ALOGD("[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
+ resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ msgCoords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ msgCoords.getAxisValue(AMOTION_EVENT_AXIS_Y));
+#endif
+ msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
+ msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
+ }
+ }
+}
+
+void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
+ const InputMessage* next) {
+ if (!mResampleTouch
+ || !(event->getSource() & AINPUT_SOURCE_CLASS_POINTER)
+ || event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
+ return;
+ }
+
+ ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
+ if (index < 0) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, no touch state for device.");
+#endif
+ return;
+ }
+
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ if (touchState.historySize < 1) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, no history for device.");
+#endif
+ return;
+ }
+
+ // Ensure that the current sample has all of the pointers that need to be reported.
+ const History* current = touchState.getHistory(0);
+ size_t pointerCount = event->getPointerCount();
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t id = event->getPointerId(i);
+ if (!current->idBits.hasBit(id)) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, missing id %d", id);
+#endif
+ return;
+ }
+ }
+
+ // Find the data to use for resampling.
+ const History* other;
+ History future;
+ float alpha;
+ if (next) {
+ // Interpolate between current sample and future sample.
+ // So current->eventTime <= sampleTime <= future.eventTime.
+ future.initializeFrom(next);
+ other = &future;
+ nsecs_t delta = future.eventTime - current->eventTime;
+ if (delta < RESAMPLE_MIN_DELTA) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, delta time is %lld ns.", delta);
+#endif
+ return;
+ }
+ alpha = float(sampleTime - current->eventTime) / delta;
+ } else if (touchState.historySize >= 2) {
+ // Extrapolate future sample using current sample and past sample.
+ // So other->eventTime <= current->eventTime <= sampleTime.
+ other = touchState.getHistory(1);
+ nsecs_t delta = current->eventTime - other->eventTime;
+ if (delta < RESAMPLE_MIN_DELTA) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, delta time is %lld ns.", delta);
+#endif
+ return;
+ }
+ nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
+ if (sampleTime > maxPredict) {
+#if DEBUG_RESAMPLING
+ ALOGD("Sample time is too far in the future, adjusting prediction "
+ "from %lld to %lld ns.",
+ sampleTime - current->eventTime, maxPredict - current->eventTime);
+#endif
+ sampleTime = maxPredict;
+ }
+ alpha = float(current->eventTime - sampleTime) / delta;
+ } else {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, insufficient data.");
+#endif
+ return;
+ }
+
+ // Resample touch coordinates.
+ touchState.lastResample.eventTime = sampleTime;
+ touchState.lastResample.idBits.clear();
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t id = event->getPointerId(i);
+ touchState.lastResample.idToIndex[id] = i;
+ touchState.lastResample.idBits.markBit(id);
+ PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
+ const PointerCoords& currentCoords = current->getPointerById(id);
+ if (other->idBits.hasBit(id)
+ && shouldResampleTool(event->getToolType(i))) {
+ const PointerCoords& otherCoords = other->getPointerById(id);
+ resampledCoords.copyFrom(currentCoords);
+ resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
+ lerp(currentCoords.getX(), otherCoords.getX(), alpha));
+ resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
+ lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+#if DEBUG_RESAMPLING
+ ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
+ "other (%0.3f, %0.3f), alpha %0.3f",
+ id, resampledCoords.getX(), resampledCoords.getY(),
+ currentCoords.getX(), currentCoords.getY(),
+ otherCoords.getX(), otherCoords.getY(),
+ alpha);
+#endif
+ } else {
+ resampledCoords.copyFrom(currentCoords);
+#if DEBUG_RESAMPLING
+ ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)",
+ id, resampledCoords.getX(), resampledCoords.getY(),
+ currentCoords.getX(), currentCoords.getY());
+#endif
+ }
+ }
+
+ event->addSample(sampleTime, touchState.lastResample.pointers);
+}
+
+bool InputConsumer::shouldResampleTool(int32_t toolType) {
+ return toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
+ || toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
+ mChannel->getName().string(), seq, handled ? "true" : "false");
+#endif
+
+ if (!seq) {
+ ALOGE("Attempted to send a finished signal with sequence number 0.");
+ return BAD_VALUE;
+ }
+
+ // Send finished signals for the batch sequence chain first.
+ size_t seqChainCount = mSeqChains.size();
+ if (seqChainCount) {
+ uint32_t currentSeq = seq;
+ uint32_t chainSeqs[seqChainCount];
+ size_t chainIndex = 0;
+ for (size_t i = seqChainCount; i-- > 0; ) {
+ const SeqChain& seqChain = mSeqChains.itemAt(i);
+ if (seqChain.seq == currentSeq) {
+ currentSeq = seqChain.chain;
+ chainSeqs[chainIndex++] = currentSeq;
+ mSeqChains.removeAt(i);
+ }
+ }
+ status_t status = OK;
+ while (!status && chainIndex-- > 0) {
+ status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
+ }
+ if (status) {
+ // An error occurred so at least one signal was not sent, reconstruct the chain.
+ do {
+ SeqChain seqChain;
+ seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
+ seqChain.chain = chainSeqs[chainIndex];
+ mSeqChains.push(seqChain);
+ } while (chainIndex-- > 0);
+ return status;
+ }
+ }
+
+ // Send finished signal for the last message in the batch.
+ return sendUnchainedFinishedSignal(seq, handled);
+}
+
+status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
+ InputMessage msg;
+ msg.header.type = InputMessage::TYPE_FINISHED;
+ msg.body.finished.seq = seq;
+ msg.body.finished.handled = handled;
+ return mChannel->sendMessage(&msg);
+}
+
+bool InputConsumer::hasDeferredEvent() const {
+ return mMsgDeferred;
+}
+
+bool InputConsumer::hasPendingBatch() const {
+ return !mBatches.isEmpty();
+}
+
+ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
+ for (size_t i = 0; i < mBatches.size(); i++) {
+ const Batch& batch = mBatches.itemAt(i);
+ const InputMessage& head = batch.samples.itemAt(0);
+ if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
+ for (size_t i = 0; i < mTouchStates.size(); i++) {
+ const TouchState& touchState = mTouchStates.itemAt(i);
+ if (touchState.deviceId == deviceId && touchState.source == source) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) {
+ event->initialize(
+ msg->body.key.deviceId,
+ msg->body.key.source,
+ msg->body.key.action,
+ msg->body.key.flags,
+ msg->body.key.keyCode,
+ msg->body.key.scanCode,
+ msg->body.key.metaState,
+ msg->body.key.repeatCount,
+ msg->body.key.downTime,
+ msg->body.key.eventTime);
+}
+
+void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
+ size_t pointerCount = msg->body.motion.pointerCount;
+ PointerProperties pointerProperties[pointerCount];
+ PointerCoords pointerCoords[pointerCount];
+ for (size_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].copyFrom(msg->body.motion.pointers[i].properties);
+ pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+ }
+
+ event->initialize(
+ msg->body.motion.deviceId,
+ msg->body.motion.source,
+ msg->body.motion.action,
+ msg->body.motion.flags,
+ msg->body.motion.edgeFlags,
+ msg->body.motion.metaState,
+ msg->body.motion.buttonState,
+ msg->body.motion.xOffset,
+ msg->body.motion.yOffset,
+ msg->body.motion.xPrecision,
+ msg->body.motion.yPrecision,
+ msg->body.motion.downTime,
+ msg->body.motion.eventTime,
+ pointerCount,
+ pointerProperties,
+ pointerCoords);
+}
+
+void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
+ size_t pointerCount = msg->body.motion.pointerCount;
+ PointerCoords pointerCoords[pointerCount];
+ for (size_t i = 0; i < pointerCount; i++) {
+ pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+ }
+
+ event->setMetaState(event->getMetaState() | msg->body.motion.metaState);
+ event->addSample(msg->body.motion.eventTime, pointerCoords);
+}
+
+bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) {
+ const InputMessage& head = batch.samples.itemAt(0);
+ size_t pointerCount = msg->body.motion.pointerCount;
+ if (head.body.motion.pointerCount != pointerCount
+ || head.body.motion.action != msg->body.motion.action) {
+ return false;
+ }
+ for (size_t i = 0; i < pointerCount; i++) {
+ if (head.body.motion.pointers[i].properties
+ != msg->body.motion.pointers[i].properties) {
+ return false;
+ }
+ }
+ return true;
+}
+
+ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
+ size_t numSamples = batch.samples.size();
+ size_t index = 0;
+ while (index < numSamples
+ && batch.samples.itemAt(index).body.motion.eventTime <= time) {
+ index += 1;
+ }
+ return ssize_t(index) - 1;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputTransport.h b/widget/gonk/libui/InputTransport.h
new file mode 100644
index 000000000..66ef2850a
--- /dev/null
+++ b/widget/gonk/libui/InputTransport.h
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_INPUT_TRANSPORT_H
+#define _ANDROIDFW_INPUT_TRANSPORT_H
+
+/**
+ * Native input transport.
+ *
+ * The InputChannel provides a mechanism for exchanging InputMessage structures across processes.
+ *
+ * The InputPublisher and InputConsumer each handle one end-point of an input channel.
+ * The InputPublisher is used by the input dispatcher to send events to the application.
+ * The InputConsumer is used by the application to receive events from the input dispatcher.
+ */
+
+#include "Input.h"
+#include <utils/Errors.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/BitSet.h>
+
+namespace android {
+
+/*
+ * Intermediate representation used to send input events and related signals.
+ */
+struct InputMessage {
+ enum {
+ TYPE_KEY = 1,
+ TYPE_MOTION = 2,
+ TYPE_FINISHED = 3,
+ };
+
+ struct Header {
+ uint32_t type;
+ uint32_t padding; // 8 byte alignment for the body that follows
+ } header;
+
+ union Body {
+ struct Key {
+ uint32_t seq;
+ nsecs_t eventTime;
+ int32_t deviceId;
+ int32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ int32_t repeatCount;
+ nsecs_t downTime;
+
+ inline size_t size() const {
+ return sizeof(Key);
+ }
+ } key;
+
+ struct Motion {
+ uint32_t seq;
+ nsecs_t eventTime;
+ int32_t deviceId;
+ int32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t buttonState;
+ int32_t edgeFlags;
+ nsecs_t downTime;
+ float xOffset;
+ float yOffset;
+ float xPrecision;
+ float yPrecision;
+ size_t pointerCount;
+ struct Pointer {
+ PointerProperties properties;
+ PointerCoords coords;
+ } pointers[MAX_POINTERS];
+
+ int32_t getActionId() const {
+ uint32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ return pointers[index].properties.id;
+ }
+
+ inline size_t size() const {
+ return sizeof(Motion) - sizeof(Pointer) * MAX_POINTERS
+ + sizeof(Pointer) * pointerCount;
+ }
+ } motion;
+
+ struct Finished {
+ uint32_t seq;
+ bool handled;
+
+ inline size_t size() const {
+ return sizeof(Finished);
+ }
+ } finished;
+ } body;
+
+ bool isValid(size_t actualSize) const;
+ size_t size() const;
+};
+
+/*
+ * An input channel consists of a local unix domain socket used to send and receive
+ * input messages across processes. Each channel has a descriptive name for debugging purposes.
+ *
+ * Each endpoint has its own InputChannel object that specifies its file descriptor.
+ *
+ * The input channel is closed when all references to it are released.
+ */
+class InputChannel : public RefBase {
+protected:
+ virtual ~InputChannel();
+
+public:
+ InputChannel(const String8& name, int fd);
+
+ /* Creates a pair of input channels.
+ *
+ * Returns OK on success.
+ */
+ static status_t openInputChannelPair(const String8& name,
+ sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel);
+
+ inline String8 getName() const { return mName; }
+ inline int getFd() const { return mFd; }
+
+ /* Sends a message to the other endpoint.
+ *
+ * If the channel is full then the message is guaranteed not to have been sent at all.
+ * Try again after the consumer has sent a finished signal indicating that it has
+ * consumed some of the pending messages from the channel.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if the channel is full.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t sendMessage(const InputMessage* msg);
+
+ /* Receives a message sent by the other endpoint.
+ *
+ * If there is no message present, try again after poll() indicates that the fd
+ * is readable.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if there is no message present.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t receiveMessage(InputMessage* msg);
+
+ /* Returns a new object that has a duplicate of this channel's fd. */
+ sp<InputChannel> dup() const;
+
+private:
+ String8 mName;
+ int mFd;
+};
+
+/*
+ * Publishes input events to an input channel.
+ */
+class InputPublisher {
+public:
+ /* Creates a publisher associated with an input channel. */
+ explicit InputPublisher(const sp<InputChannel>& channel);
+
+ /* Destroys the publisher and releases its input channel. */
+ ~InputPublisher();
+
+ /* Gets the underlying input channel. */
+ inline sp<InputChannel> getChannel() { return mChannel; }
+
+ /* Publishes a key event to the input channel.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if the channel is full.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Returns BAD_VALUE if seq is 0.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t publishKeyEvent(
+ uint32_t seq,
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t keyCode,
+ int32_t scanCode,
+ int32_t metaState,
+ int32_t repeatCount,
+ nsecs_t downTime,
+ nsecs_t eventTime);
+
+ /* Publishes a motion event to the input channel.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if the channel is full.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Returns BAD_VALUE if seq is 0 or if pointerCount is less than 1 or greater than MAX_POINTERS.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t publishMotionEvent(
+ uint32_t seq,
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t edgeFlags,
+ int32_t metaState,
+ int32_t buttonState,
+ float xOffset,
+ float yOffset,
+ float xPrecision,
+ float yPrecision,
+ nsecs_t downTime,
+ nsecs_t eventTime,
+ size_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords);
+
+ /* Receives the finished signal from the consumer in reply to the original dispatch signal.
+ * If a signal was received, returns the message sequence number,
+ * and whether the consumer handled the message.
+ *
+ * The returned sequence number is never 0 unless the operation failed.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if there is no signal present.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t receiveFinishedSignal(uint32_t* outSeq, bool* outHandled);
+
+private:
+ sp<InputChannel> mChannel;
+};
+
+/*
+ * Consumes input events from an input channel.
+ */
+class InputConsumer {
+public:
+ /* Creates a consumer associated with an input channel. */
+ explicit InputConsumer(const sp<InputChannel>& channel);
+
+ /* Destroys the consumer and releases its input channel. */
+ ~InputConsumer();
+
+ /* Gets the underlying input channel. */
+ inline sp<InputChannel> getChannel() { return mChannel; }
+
+ /* Consumes an input event from the input channel and copies its contents into
+ * an InputEvent object created using the specified factory.
+ *
+ * Tries to combine a series of move events into larger batches whenever possible.
+ *
+ * If consumeBatches is false, then defers consuming pending batched events if it
+ * is possible for additional samples to be added to them later. Call hasPendingBatch()
+ * to determine whether a pending batch is available to be consumed.
+ *
+ * If consumeBatches is true, then events are still batched but they are consumed
+ * immediately as soon as the input channel is exhausted.
+ *
+ * The frameTime parameter specifies the time when the current display frame started
+ * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
+ *
+ * The returned sequence number is never 0 unless the operation failed.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if there is no event present.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Returns NO_MEMORY if the event could not be created.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t consume(InputEventFactoryInterface* factory, bool consumeBatches,
+ nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
+
+ /* Sends a finished signal to the publisher to inform it that the message
+ * with the specified sequence number has finished being process and whether
+ * the message was handled by the consumer.
+ *
+ * Returns OK on success.
+ * Returns BAD_VALUE if seq is 0.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t sendFinishedSignal(uint32_t seq, bool handled);
+
+ /* Returns true if there is a deferred event waiting.
+ *
+ * Should be called after calling consume() to determine whether the consumer
+ * has a deferred event to be processed. Deferred events are somewhat special in
+ * that they have already been removed from the input channel. If the input channel
+ * becomes empty, the client may need to do extra work to ensure that it processes
+ * the deferred event despite the fact that the input channel's file descriptor
+ * is not readable.
+ *
+ * One option is simply to call consume() in a loop until it returns WOULD_BLOCK.
+ * This guarantees that all deferred events will be processed.
+ *
+ * Alternately, the caller can call hasDeferredEvent() to determine whether there is
+ * a deferred event waiting and then ensure that its event loop wakes up at least
+ * one more time to consume the deferred event.
+ */
+ bool hasDeferredEvent() const;
+
+ /* Returns true if there is a pending batch.
+ *
+ * Should be called after calling consume() with consumeBatches == false to determine
+ * whether consume() should be called again later on with consumeBatches == true.
+ */
+ bool hasPendingBatch() const;
+
+private:
+ // True if touch resampling is enabled.
+ const bool mResampleTouch;
+
+ // The input channel.
+ sp<InputChannel> mChannel;
+
+ // The current input message.
+ InputMessage mMsg;
+
+ // True if mMsg contains a valid input message that was deferred from the previous
+ // call to consume and that still needs to be handled.
+ bool mMsgDeferred;
+
+ // Batched motion events per device and source.
+ struct Batch {
+ Vector<InputMessage> samples;
+ };
+ Vector<Batch> mBatches;
+
+ // Touch state per device and source, only for sources of class pointer.
+ struct History {
+ nsecs_t eventTime;
+ BitSet32 idBits;
+ int32_t idToIndex[MAX_POINTER_ID + 1];
+ PointerCoords pointers[MAX_POINTERS];
+
+ void initializeFrom(const InputMessage* msg) {
+ eventTime = msg->body.motion.eventTime;
+ idBits.clear();
+ for (size_t i = 0; i < msg->body.motion.pointerCount; i++) {
+ uint32_t id = msg->body.motion.pointers[i].properties.id;
+ idBits.markBit(id);
+ idToIndex[id] = i;
+ pointers[i].copyFrom(msg->body.motion.pointers[i].coords);
+ }
+ }
+
+ const PointerCoords& getPointerById(uint32_t id) const {
+ return pointers[idToIndex[id]];
+ }
+ };
+ struct TouchState {
+ int32_t deviceId;
+ int32_t source;
+ size_t historyCurrent;
+ size_t historySize;
+ History history[2];
+ History lastResample;
+
+ void initialize(int32_t deviceId, int32_t source) {
+ this->deviceId = deviceId;
+ this->source = source;
+ historyCurrent = 0;
+ historySize = 0;
+ lastResample.eventTime = 0;
+ lastResample.idBits.clear();
+ }
+
+ void addHistory(const InputMessage* msg) {
+ historyCurrent ^= 1;
+ if (historySize < 2) {
+ historySize += 1;
+ }
+ history[historyCurrent].initializeFrom(msg);
+ }
+
+ const History* getHistory(size_t index) const {
+ return &history[(historyCurrent + index) & 1];
+ }
+ };
+ Vector<TouchState> mTouchStates;
+
+ // Chain of batched sequence numbers. When multiple input messages are combined into
+ // a batch, we append a record here that associates the last sequence number in the
+ // batch with the previous one. When the finished signal is sent, we traverse the
+ // chain to individually finish all input messages that were part of the batch.
+ struct SeqChain {
+ uint32_t seq; // sequence number of batched input message
+ uint32_t chain; // sequence number of previous batched input message
+ };
+ Vector<SeqChain> mSeqChains;
+
+ status_t consumeBatch(InputEventFactoryInterface* factory,
+ nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
+ status_t consumeSamples(InputEventFactoryInterface* factory,
+ Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent);
+
+ void updateTouchState(InputMessage* msg);
+ void rewriteMessage(const TouchState& state, InputMessage* msg);
+ void resampleTouchState(nsecs_t frameTime, MotionEvent* event,
+ const InputMessage *next);
+
+ ssize_t findBatch(int32_t deviceId, int32_t source) const;
+ ssize_t findTouchState(int32_t deviceId, int32_t source) const;
+
+ status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
+
+ static void initializeKeyEvent(KeyEvent* event, const InputMessage* msg);
+ static void initializeMotionEvent(MotionEvent* event, const InputMessage* msg);
+ static void addSample(MotionEvent* event, const InputMessage* msg);
+ static bool canAddSample(const Batch& batch, const InputMessage* msg);
+ static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
+ static bool shouldResampleTool(int32_t toolType);
+
+ static bool isTouchResamplingEnabled();
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_INPUT_TRANSPORT_H
diff --git a/widget/gonk/libui/InputWindow.cpp b/widget/gonk/libui/InputWindow.cpp
new file mode 100644
index 000000000..3aea445a0
--- /dev/null
+++ b/widget/gonk/libui/InputWindow.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputWindow"
+
+#include "InputWindow.h"
+
+#include "cutils_log.h"
+
+namespace android {
+
+// --- InputWindowInfo ---
+
+bool InputWindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
+ return touchableRegion.contains(x, y);
+}
+
+bool InputWindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
+ return x >= frameLeft && x <= frameRight
+ && y >= frameTop && y <= frameBottom;
+}
+
+bool InputWindowInfo::isTrustedOverlay() const {
+ return layoutParamsType == TYPE_INPUT_METHOD
+ || layoutParamsType == TYPE_INPUT_METHOD_DIALOG
+ || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY;
+}
+
+bool InputWindowInfo::supportsSplitTouch() const {
+ return layoutParamsFlags & FLAG_SPLIT_TOUCH;
+}
+
+
+// --- InputWindowHandle ---
+
+InputWindowHandle::InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle) :
+ inputApplicationHandle(inputApplicationHandle), mInfo(NULL) {
+}
+
+InputWindowHandle::~InputWindowHandle() {
+ delete mInfo;
+}
+
+void InputWindowHandle::releaseInfo() {
+ if (mInfo) {
+ delete mInfo;
+ mInfo = NULL;
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputWindow.h b/widget/gonk/libui/InputWindow.h
new file mode 100644
index 000000000..cce5fd4fe
--- /dev/null
+++ b/widget/gonk/libui/InputWindow.h
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_WINDOW_H
+#define _UI_INPUT_WINDOW_H
+
+#include "Input.h"
+#include "InputTransport.h"
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+#include <utils/String8.h>
+
+#include <SkRegion.h>
+
+#include "InputApplication.h"
+
+namespace android {
+
+/*
+ * Describes the properties of a window that can receive input.
+ */
+struct InputWindowInfo {
+ // Window flags from WindowManager.LayoutParams
+ enum {
+ FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
+ FLAG_DIM_BEHIND = 0x00000002,
+ FLAG_BLUR_BEHIND = 0x00000004,
+ FLAG_NOT_FOCUSABLE = 0x00000008,
+ FLAG_NOT_TOUCHABLE = 0x00000010,
+ FLAG_NOT_TOUCH_MODAL = 0x00000020,
+ FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
+ FLAG_KEEP_SCREEN_ON = 0x00000080,
+ FLAG_LAYOUT_IN_SCREEN = 0x00000100,
+ FLAG_LAYOUT_NO_LIMITS = 0x00000200,
+ FLAG_FULLSCREEN = 0x00000400,
+ FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
+ FLAG_DITHER = 0x00001000,
+ FLAG_SECURE = 0x00002000,
+ FLAG_SCALED = 0x00004000,
+ FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
+ FLAG_LAYOUT_INSET_DECOR = 0x00010000,
+ FLAG_ALT_FOCUSABLE_IM = 0x00020000,
+ FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
+ FLAG_SHOW_WHEN_LOCKED = 0x00080000,
+ FLAG_SHOW_WALLPAPER = 0x00100000,
+ FLAG_TURN_SCREEN_ON = 0x00200000,
+ FLAG_DISMISS_KEYGUARD = 0x00400000,
+ FLAG_SPLIT_TOUCH = 0x00800000,
+ FLAG_HARDWARE_ACCELERATED = 0x01000000,
+ FLAG_HARDWARE_ACCELERATED_SYSTEM = 0x02000000,
+ FLAG_SLIPPERY = 0x04000000,
+ FLAG_NEEDS_MENU_KEY = 0x08000000,
+ FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000,
+ FLAG_COMPATIBLE_WINDOW = 0x20000000,
+ FLAG_SYSTEM_ERROR = 0x40000000,
+ };
+
+ // Window types from WindowManager.LayoutParams
+ enum {
+ FIRST_APPLICATION_WINDOW = 1,
+ TYPE_BASE_APPLICATION = 1,
+ TYPE_APPLICATION = 2,
+ TYPE_APPLICATION_STARTING = 3,
+ LAST_APPLICATION_WINDOW = 99,
+ FIRST_SUB_WINDOW = 1000,
+ TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW,
+ TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1,
+ TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2,
+ TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3,
+ TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4,
+ LAST_SUB_WINDOW = 1999,
+ FIRST_SYSTEM_WINDOW = 2000,
+ TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW,
+ TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1,
+ TYPE_PHONE = FIRST_SYSTEM_WINDOW+2,
+ TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3,
+ TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4,
+ TYPE_TOAST = FIRST_SYSTEM_WINDOW+5,
+ TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6,
+ TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7,
+ TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8,
+ TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9,
+ TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10,
+ TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11,
+ TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12,
+ TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13,
+ TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14,
+ TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15,
+ TYPE_DRAG = FIRST_SYSTEM_WINDOW+16,
+ TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17,
+ TYPE_POINTER = FIRST_SYSTEM_WINDOW+18,
+ TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19,
+ TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20,
+ TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21,
+ LAST_SYSTEM_WINDOW = 2999,
+ };
+
+ enum {
+ INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES = 0x00000001,
+ INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002,
+ INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004,
+ };
+
+ sp<InputChannel> inputChannel;
+ String8 name;
+ int32_t layoutParamsFlags;
+ int32_t layoutParamsType;
+ nsecs_t dispatchingTimeout;
+ int32_t frameLeft;
+ int32_t frameTop;
+ int32_t frameRight;
+ int32_t frameBottom;
+ float scaleFactor;
+ SkRegion touchableRegion;
+ bool visible;
+ bool canReceiveKeys;
+ bool hasFocus;
+ bool hasWallpaper;
+ bool paused;
+ int32_t layer;
+ int32_t ownerPid;
+ int32_t ownerUid;
+ int32_t inputFeatures;
+ int32_t displayId;
+
+ bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
+ bool frameContainsPoint(int32_t x, int32_t y) const;
+
+ /* Returns true if the window is of a trusted type that is allowed to silently
+ * overlay other windows for the purpose of implementing the secure views feature.
+ * Trusted overlays, such as IME windows, can partly obscure other windows without causing
+ * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
+ */
+ bool isTrustedOverlay() const;
+
+ bool supportsSplitTouch() const;
+};
+
+
+/*
+ * Handle for a window that can receive input.
+ *
+ * Used by the native input dispatcher to indirectly refer to the window manager objects
+ * that describe a window.
+ */
+class InputWindowHandle : public RefBase {
+public:
+ const sp<InputApplicationHandle> inputApplicationHandle;
+
+ inline const InputWindowInfo* getInfo() const {
+ return mInfo;
+ }
+
+ inline sp<InputChannel> getInputChannel() const {
+ return mInfo ? mInfo->inputChannel : NULL;
+ }
+
+ inline String8 getName() const {
+ return mInfo ? mInfo->name : String8("<invalid>");
+ }
+
+ inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const {
+ return mInfo ? mInfo->dispatchingTimeout : defaultValue;
+ }
+
+ /**
+ * Requests that the state of this object be updated to reflect
+ * the most current available information about the application.
+ *
+ * This method should only be called from within the input dispatcher's
+ * critical section.
+ *
+ * Returns true on success, or false if the handle is no longer valid.
+ */
+ virtual bool updateInfo() = 0;
+
+ /**
+ * Releases the storage used by the associated information when it is
+ * no longer needed.
+ */
+ void releaseInfo();
+
+protected:
+ InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle);
+ virtual ~InputWindowHandle();
+
+ InputWindowInfo* mInfo;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_WINDOW_H
diff --git a/widget/gonk/libui/KeyCharacterMap.cpp b/widget/gonk/libui/KeyCharacterMap.cpp
new file mode 100644
index 000000000..cec0666ce
--- /dev/null
+++ b/widget/gonk/libui/KeyCharacterMap.cpp
@@ -0,0 +1,1153 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "KeyCharacterMap"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include "android_keycodes.h"
+#include "Keyboard.h"
+#include "KeyCharacterMap.h"
+
+#if HAVE_ANDROID_OS
+#include <binder/Parcel.h>
+#endif
+
+#include <utils/Errors.h>
+#include "Tokenizer.h"
+#include <utils/Timers.h>
+
+// Enables debug output for the parser.
+#define DEBUG_PARSER 0
+
+// Enables debug output for parser performance.
+#define DEBUG_PARSER_PERFORMANCE 0
+
+// Enables debug output for mapping.
+#define DEBUG_MAPPING 0
+
+
+namespace android {
+
+static const char* WHITESPACE = " \t\r";
+static const char* WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r,:";
+
+struct Modifier {
+ const char* label;
+ int32_t metaState;
+};
+static const Modifier modifiers[] = {
+ { "shift", AMETA_SHIFT_ON },
+ { "lshift", AMETA_SHIFT_LEFT_ON },
+ { "rshift", AMETA_SHIFT_RIGHT_ON },
+ { "alt", AMETA_ALT_ON },
+ { "lalt", AMETA_ALT_LEFT_ON },
+ { "ralt", AMETA_ALT_RIGHT_ON },
+ { "ctrl", AMETA_CTRL_ON },
+ { "lctrl", AMETA_CTRL_LEFT_ON },
+ { "rctrl", AMETA_CTRL_RIGHT_ON },
+ { "meta", AMETA_META_ON },
+ { "lmeta", AMETA_META_LEFT_ON },
+ { "rmeta", AMETA_META_RIGHT_ON },
+ { "sym", AMETA_SYM_ON },
+ { "fn", AMETA_FUNCTION_ON },
+ { "capslock", AMETA_CAPS_LOCK_ON },
+ { "numlock", AMETA_NUM_LOCK_ON },
+ { "scrolllock", AMETA_SCROLL_LOCK_ON },
+};
+
+#if DEBUG_MAPPING
+static String8 toString(const char16_t* chars, size_t numChars) {
+ String8 result;
+ for (size_t i = 0; i < numChars; i++) {
+ result.appendFormat(i == 0 ? "%d" : ", %d", chars[i]);
+ }
+ return result;
+}
+#endif
+
+
+// --- KeyCharacterMap ---
+
+sp<KeyCharacterMap> KeyCharacterMap::sEmpty = new KeyCharacterMap();
+
+KeyCharacterMap::KeyCharacterMap() :
+ mType(KEYBOARD_TYPE_UNKNOWN) {
+}
+
+KeyCharacterMap::KeyCharacterMap(const KeyCharacterMap& other) :
+ RefBase(), mType(other.mType), mKeysByScanCode(other.mKeysByScanCode),
+ mKeysByUsageCode(other.mKeysByUsageCode) {
+ for (size_t i = 0; i < other.mKeys.size(); i++) {
+ mKeys.add(other.mKeys.keyAt(i), new Key(*other.mKeys.valueAt(i)));
+ }
+}
+
+KeyCharacterMap::~KeyCharacterMap() {
+ for (size_t i = 0; i < mKeys.size(); i++) {
+ Key* key = mKeys.editValueAt(i);
+ delete key;
+ }
+}
+
+status_t KeyCharacterMap::load(const String8& filename,
+ Format format, sp<KeyCharacterMap>* outMap) {
+ outMap->clear();
+
+ Tokenizer* tokenizer;
+ status_t status = Tokenizer::open(filename, &tokenizer);
+ if (status) {
+ ALOGE("Error %d opening key character map file %s.", status, filename.string());
+ } else {
+ status = load(tokenizer, format, outMap);
+ delete tokenizer;
+ }
+ return status;
+}
+
+status_t KeyCharacterMap::loadContents(const String8& filename, const char* contents,
+ Format format, sp<KeyCharacterMap>* outMap) {
+ outMap->clear();
+
+ Tokenizer* tokenizer;
+ status_t status = Tokenizer::fromContents(filename, contents, &tokenizer);
+ if (status) {
+ ALOGE("Error %d opening key character map.", status);
+ } else {
+ status = load(tokenizer, format, outMap);
+ delete tokenizer;
+ }
+ return status;
+}
+
+status_t KeyCharacterMap::load(Tokenizer* tokenizer,
+ Format format, sp<KeyCharacterMap>* outMap) {
+ status_t status = OK;
+ sp<KeyCharacterMap> map = new KeyCharacterMap();
+ if (!map.get()) {
+ ALOGE("Error allocating key character map.");
+ status = NO_MEMORY;
+ } else {
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+#endif
+ Parser parser(map.get(), tokenizer, format);
+ status = parser.parse();
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
+ ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",
+ tokenizer->getFilename().string(), tokenizer->getLineNumber(),
+ elapsedTime / 1000000.0);
+#endif
+ if (!status) {
+ *outMap = map;
+ }
+ }
+ return status;
+}
+
+sp<KeyCharacterMap> KeyCharacterMap::combine(const sp<KeyCharacterMap>& base,
+ const sp<KeyCharacterMap>& overlay) {
+ if (overlay == NULL) {
+ return base;
+ }
+ if (base == NULL) {
+ return overlay;
+ }
+
+ sp<KeyCharacterMap> map = new KeyCharacterMap(*base.get());
+ for (size_t i = 0; i < overlay->mKeys.size(); i++) {
+ int32_t keyCode = overlay->mKeys.keyAt(i);
+ Key* key = overlay->mKeys.valueAt(i);
+ ssize_t oldIndex = map->mKeys.indexOfKey(keyCode);
+ if (oldIndex >= 0) {
+ delete map->mKeys.valueAt(oldIndex);
+ map->mKeys.editValueAt(oldIndex) = new Key(*key);
+ } else {
+ map->mKeys.add(keyCode, new Key(*key));
+ }
+ }
+
+ for (size_t i = 0; i < overlay->mKeysByScanCode.size(); i++) {
+ map->mKeysByScanCode.replaceValueFor(overlay->mKeysByScanCode.keyAt(i),
+ overlay->mKeysByScanCode.valueAt(i));
+ }
+
+ for (size_t i = 0; i < overlay->mKeysByUsageCode.size(); i++) {
+ map->mKeysByUsageCode.replaceValueFor(overlay->mKeysByUsageCode.keyAt(i),
+ overlay->mKeysByUsageCode.valueAt(i));
+ }
+ return map;
+}
+
+sp<KeyCharacterMap> KeyCharacterMap::empty() {
+ return sEmpty;
+}
+
+int32_t KeyCharacterMap::getKeyboardType() const {
+ return mType;
+}
+
+char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const {
+ char16_t result = 0;
+ const Key* key;
+ if (getKey(keyCode, &key)) {
+ result = key->label;
+ }
+#if DEBUG_MAPPING
+ ALOGD("getDisplayLabel: keyCode=%d ~ Result %d.", keyCode, result);
+#endif
+ return result;
+}
+
+char16_t KeyCharacterMap::getNumber(int32_t keyCode) const {
+ char16_t result = 0;
+ const Key* key;
+ if (getKey(keyCode, &key)) {
+ result = key->number;
+ }
+#if DEBUG_MAPPING
+ ALOGD("getNumber: keyCode=%d ~ Result %d.", keyCode, result);
+#endif
+ return result;
+}
+
+char16_t KeyCharacterMap::getCharacter(int32_t keyCode, int32_t metaState) const {
+ char16_t result = 0;
+ const Key* key;
+ const Behavior* behavior;
+ if (getKeyBehavior(keyCode, metaState, &key, &behavior)) {
+ result = behavior->character;
+ }
+#if DEBUG_MAPPING
+ ALOGD("getCharacter: keyCode=%d, metaState=0x%08x ~ Result %d.", keyCode, metaState, result);
+#endif
+ return result;
+}
+
+bool KeyCharacterMap::getFallbackAction(int32_t keyCode, int32_t metaState,
+ FallbackAction* outFallbackAction) const {
+ outFallbackAction->keyCode = 0;
+ outFallbackAction->metaState = 0;
+
+ bool result = false;
+ const Key* key;
+ const Behavior* behavior;
+ if (getKeyBehavior(keyCode, metaState, &key, &behavior)) {
+ if (behavior->fallbackKeyCode) {
+ outFallbackAction->keyCode = behavior->fallbackKeyCode;
+ outFallbackAction->metaState = metaState & ~behavior->metaState;
+ result = true;
+ }
+ }
+#if DEBUG_MAPPING
+ ALOGD("getFallbackKeyCode: keyCode=%d, metaState=0x%08x ~ Result %s, "
+ "fallback keyCode=%d, fallback metaState=0x%08x.",
+ keyCode, metaState, result ? "true" : "false",
+ outFallbackAction->keyCode, outFallbackAction->metaState);
+#endif
+ return result;
+}
+
+char16_t KeyCharacterMap::getMatch(int32_t keyCode, const char16_t* chars, size_t numChars,
+ int32_t metaState) const {
+ char16_t result = 0;
+ const Key* key;
+ if (getKey(keyCode, &key)) {
+ // Try to find the most general behavior that maps to this character.
+ // For example, the base key behavior will usually be last in the list.
+ // However, if we find a perfect meta state match for one behavior then use that one.
+ for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) {
+ if (behavior->character) {
+ for (size_t i = 0; i < numChars; i++) {
+ if (behavior->character == chars[i]) {
+ result = behavior->character;
+ if ((behavior->metaState & metaState) == behavior->metaState) {
+ goto ExactMatch;
+ }
+ break;
+ }
+ }
+ }
+ }
+ ExactMatch: ;
+ }
+#if DEBUG_MAPPING
+ ALOGD("getMatch: keyCode=%d, chars=[%s], metaState=0x%08x ~ Result %d.",
+ keyCode, toString(chars, numChars).string(), metaState, result);
+#endif
+ return result;
+}
+
+bool KeyCharacterMap::getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
+ Vector<KeyEvent>& outEvents) const {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ for (size_t i = 0; i < numChars; i++) {
+ int32_t keyCode, metaState;
+ char16_t ch = chars[i];
+ if (!findKey(ch, &keyCode, &metaState)) {
+#if DEBUG_MAPPING
+ ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Failed to find mapping for character %d.",
+ deviceId, toString(chars, numChars).string(), ch);
+#endif
+ return false;
+ }
+
+ int32_t currentMetaState = 0;
+ addMetaKeys(outEvents, deviceId, metaState, true, now, &currentMetaState);
+ addKey(outEvents, deviceId, keyCode, currentMetaState, true, now);
+ addKey(outEvents, deviceId, keyCode, currentMetaState, false, now);
+ addMetaKeys(outEvents, deviceId, metaState, false, now, &currentMetaState);
+ }
+#if DEBUG_MAPPING
+ ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Generated %d events.",
+ deviceId, toString(chars, numChars).string(), int32_t(outEvents.size()));
+ for (size_t i = 0; i < outEvents.size(); i++) {
+ ALOGD(" Key: keyCode=%d, metaState=0x%08x, %s.",
+ outEvents[i].getKeyCode(), outEvents[i].getMetaState(),
+ outEvents[i].getAction() == AKEY_EVENT_ACTION_DOWN ? "down" : "up");
+ }
+#endif
+ return true;
+}
+
+status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const {
+ if (usageCode) {
+ ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
+ if (index >= 0) {
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.",
+ scanCode, usageCode, *outKeyCode);
+#endif
+ *outKeyCode = mKeysByUsageCode.valueAt(index);
+ return OK;
+ }
+ }
+ if (scanCode) {
+ ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
+ if (index >= 0) {
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.",
+ scanCode, usageCode, *outKeyCode);
+#endif
+ *outKeyCode = mKeysByScanCode.valueAt(index);
+ return OK;
+ }
+ }
+
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode);
+#endif
+ *outKeyCode = AKEYCODE_UNKNOWN;
+ return NAME_NOT_FOUND;
+}
+
+bool KeyCharacterMap::getKey(int32_t keyCode, const Key** outKey) const {
+ ssize_t index = mKeys.indexOfKey(keyCode);
+ if (index >= 0) {
+ *outKey = mKeys.valueAt(index);
+ return true;
+ }
+ return false;
+}
+
+bool KeyCharacterMap::getKeyBehavior(int32_t keyCode, int32_t metaState,
+ const Key** outKey, const Behavior** outBehavior) const {
+ const Key* key;
+ if (getKey(keyCode, &key)) {
+ const Behavior* behavior = key->firstBehavior;
+ while (behavior) {
+ if (matchesMetaState(metaState, behavior->metaState)) {
+ *outKey = key;
+ *outBehavior = behavior;
+ return true;
+ }
+ behavior = behavior->next;
+ }
+ }
+ return false;
+}
+
+bool KeyCharacterMap::matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState) {
+ // Behavior must have at least the set of meta states specified.
+ // And if the key event has CTRL, ALT or META then the behavior must exactly
+ // match those, taking into account that a behavior can specify that it handles
+ // one, both or either of a left/right modifier pair.
+ if ((eventMetaState & behaviorMetaState) == behaviorMetaState) {
+ const int32_t EXACT_META_STATES =
+ AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON
+ | AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON
+ | AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON;
+ int32_t unmatchedMetaState = eventMetaState & ~behaviorMetaState & EXACT_META_STATES;
+ if (behaviorMetaState & AMETA_CTRL_ON) {
+ unmatchedMetaState &= ~(AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON);
+ } else if (behaviorMetaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
+ unmatchedMetaState &= ~AMETA_CTRL_ON;
+ }
+ if (behaviorMetaState & AMETA_ALT_ON) {
+ unmatchedMetaState &= ~(AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON);
+ } else if (behaviorMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
+ unmatchedMetaState &= ~AMETA_ALT_ON;
+ }
+ if (behaviorMetaState & AMETA_META_ON) {
+ unmatchedMetaState &= ~(AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
+ } else if (behaviorMetaState & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
+ unmatchedMetaState &= ~AMETA_META_ON;
+ }
+ return !unmatchedMetaState;
+ }
+ return false;
+}
+
+bool KeyCharacterMap::findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const {
+ if (!ch) {
+ return false;
+ }
+
+ for (size_t i = 0; i < mKeys.size(); i++) {
+ const Key* key = mKeys.valueAt(i);
+
+ // Try to find the most general behavior that maps to this character.
+ // For example, the base key behavior will usually be last in the list.
+ const Behavior* found = NULL;
+ for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) {
+ if (behavior->character == ch) {
+ found = behavior;
+ }
+ }
+ if (found) {
+ *outKeyCode = mKeys.keyAt(i);
+ *outMetaState = found->metaState;
+ return true;
+ }
+ }
+ return false;
+}
+
+void KeyCharacterMap::addKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time) {
+ outEvents.push();
+ KeyEvent& event = outEvents.editTop();
+ event.initialize(deviceId, AINPUT_SOURCE_KEYBOARD,
+ down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+ 0, keyCode, 0, metaState, 0, time, time);
+}
+
+void KeyCharacterMap::addMetaKeys(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t* currentMetaState) {
+ // Add and remove meta keys symmetrically.
+ if (down) {
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState);
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState);
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState);
+
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON,
+ AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON,
+ AMETA_SHIFT_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON,
+ AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON,
+ AMETA_ALT_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON,
+ AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON,
+ AMETA_CTRL_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_META_LEFT, AMETA_META_LEFT_ON,
+ AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON,
+ AMETA_META_ON, currentMetaState);
+
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState);
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState);
+ } else {
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState);
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState);
+
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_META_LEFT, AMETA_META_LEFT_ON,
+ AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON,
+ AMETA_META_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON,
+ AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON,
+ AMETA_CTRL_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON,
+ AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON,
+ AMETA_ALT_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON,
+ AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON,
+ AMETA_SHIFT_ON, currentMetaState);
+
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState);
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState);
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState);
+ }
+}
+
+bool KeyCharacterMap::addSingleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t keyCode, int32_t keyMetaState,
+ int32_t* currentMetaState) {
+ if ((metaState & keyMetaState) == keyMetaState) {
+ *currentMetaState = updateMetaState(keyCode, down, *currentMetaState);
+ addKey(outEvents, deviceId, keyCode, *currentMetaState, down, time);
+ return true;
+ }
+ return false;
+}
+
+void KeyCharacterMap::addDoubleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t leftKeyCode, int32_t leftKeyMetaState,
+ int32_t rightKeyCode, int32_t rightKeyMetaState,
+ int32_t eitherKeyMetaState,
+ int32_t* currentMetaState) {
+ bool specific = false;
+ specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time,
+ leftKeyCode, leftKeyMetaState, currentMetaState);
+ specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time,
+ rightKeyCode, rightKeyMetaState, currentMetaState);
+
+ if (!specific) {
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time,
+ leftKeyCode, eitherKeyMetaState, currentMetaState);
+ }
+}
+
+void KeyCharacterMap::addLockedMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, nsecs_t time,
+ int32_t keyCode, int32_t keyMetaState,
+ int32_t* currentMetaState) {
+ if ((metaState & keyMetaState) == keyMetaState) {
+ *currentMetaState = updateMetaState(keyCode, true, *currentMetaState);
+ addKey(outEvents, deviceId, keyCode, *currentMetaState, true, time);
+ *currentMetaState = updateMetaState(keyCode, false, *currentMetaState);
+ addKey(outEvents, deviceId, keyCode, *currentMetaState, false, time);
+ }
+}
+
+#if HAVE_ANDROID_OS
+sp<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
+ sp<KeyCharacterMap> map = new KeyCharacterMap();
+ map->mType = parcel->readInt32();
+ size_t numKeys = parcel->readInt32();
+ if (parcel->errorCheck()) {
+ return NULL;
+ }
+
+ for (size_t i = 0; i < numKeys; i++) {
+ int32_t keyCode = parcel->readInt32();
+ char16_t label = parcel->readInt32();
+ char16_t number = parcel->readInt32();
+ if (parcel->errorCheck()) {
+ return NULL;
+ }
+
+ Key* key = new Key();
+ key->label = label;
+ key->number = number;
+ map->mKeys.add(keyCode, key);
+
+ Behavior* lastBehavior = NULL;
+ while (parcel->readInt32()) {
+ int32_t metaState = parcel->readInt32();
+ char16_t character = parcel->readInt32();
+ int32_t fallbackKeyCode = parcel->readInt32();
+ if (parcel->errorCheck()) {
+ return NULL;
+ }
+
+ Behavior* behavior = new Behavior();
+ behavior->metaState = metaState;
+ behavior->character = character;
+ behavior->fallbackKeyCode = fallbackKeyCode;
+ if (lastBehavior) {
+ lastBehavior->next = behavior;
+ } else {
+ key->firstBehavior = behavior;
+ }
+ lastBehavior = behavior;
+ }
+
+ if (parcel->errorCheck()) {
+ return NULL;
+ }
+ }
+ return map;
+}
+
+void KeyCharacterMap::writeToParcel(Parcel* parcel) const {
+ parcel->writeInt32(mType);
+
+ size_t numKeys = mKeys.size();
+ parcel->writeInt32(numKeys);
+ for (size_t i = 0; i < numKeys; i++) {
+ int32_t keyCode = mKeys.keyAt(i);
+ const Key* key = mKeys.valueAt(i);
+ parcel->writeInt32(keyCode);
+ parcel->writeInt32(key->label);
+ parcel->writeInt32(key->number);
+ for (const Behavior* behavior = key->firstBehavior; behavior != NULL;
+ behavior = behavior->next) {
+ parcel->writeInt32(1);
+ parcel->writeInt32(behavior->metaState);
+ parcel->writeInt32(behavior->character);
+ parcel->writeInt32(behavior->fallbackKeyCode);
+ }
+ parcel->writeInt32(0);
+ }
+}
+#endif
+
+
+// --- KeyCharacterMap::Key ---
+
+KeyCharacterMap::Key::Key() :
+ label(0), number(0), firstBehavior(NULL) {
+}
+
+KeyCharacterMap::Key::Key(const Key& other) :
+ label(other.label), number(other.number),
+ firstBehavior(other.firstBehavior ? new Behavior(*other.firstBehavior) : NULL) {
+}
+
+KeyCharacterMap::Key::~Key() {
+ Behavior* behavior = firstBehavior;
+ while (behavior) {
+ Behavior* next = behavior->next;
+ delete behavior;
+ behavior = next;
+ }
+}
+
+
+// --- KeyCharacterMap::Behavior ---
+
+KeyCharacterMap::Behavior::Behavior() :
+ next(NULL), metaState(0), character(0), fallbackKeyCode(0) {
+}
+
+KeyCharacterMap::Behavior::Behavior(const Behavior& other) :
+ next(other.next ? new Behavior(*other.next) : NULL),
+ metaState(other.metaState), character(other.character),
+ fallbackKeyCode(other.fallbackKeyCode) {
+}
+
+
+// --- KeyCharacterMap::Parser ---
+
+KeyCharacterMap::Parser::Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format) :
+ mMap(map), mTokenizer(tokenizer), mFormat(format), mState(STATE_TOP) {
+}
+
+KeyCharacterMap::Parser::~Parser() {
+}
+
+status_t KeyCharacterMap::Parser::parse() {
+ while (!mTokenizer->isEof()) {
+#if DEBUG_PARSER
+ ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+#endif
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ switch (mState) {
+ case STATE_TOP: {
+ String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
+ if (keywordToken == "type") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseType();
+ if (status) return status;
+ } else if (keywordToken == "map") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseMap();
+ if (status) return status;
+ } else if (keywordToken == "key") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseKey();
+ if (status) return status;
+ } else {
+ ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
+ keywordToken.string());
+ return BAD_VALUE;
+ }
+ break;
+ }
+
+ case STATE_KEY: {
+ status_t status = parseKeyProperty();
+ if (status) return status;
+ break;
+ }
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
+ mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+ return BAD_VALUE;
+ }
+ }
+
+ mTokenizer->nextLine();
+ }
+
+ if (mState != STATE_TOP) {
+ ALOGE("%s: Unterminated key description at end of file.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ if (mMap->mType == KEYBOARD_TYPE_UNKNOWN) {
+ ALOGE("%s: Keyboard layout missing required keyboard 'type' declaration.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ if (mFormat == FORMAT_BASE) {
+ if (mMap->mType == KEYBOARD_TYPE_OVERLAY) {
+ ALOGE("%s: Base keyboard layout must specify a keyboard 'type' other than 'OVERLAY'.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ } else if (mFormat == FORMAT_OVERLAY) {
+ if (mMap->mType != KEYBOARD_TYPE_OVERLAY) {
+ ALOGE("%s: Overlay keyboard layout missing required keyboard "
+ "'type OVERLAY' declaration.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseType() {
+ if (mMap->mType != KEYBOARD_TYPE_UNKNOWN) {
+ ALOGE("%s: Duplicate keyboard 'type' declaration.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ KeyboardType type;
+ String8 typeToken = mTokenizer->nextToken(WHITESPACE);
+ if (typeToken == "NUMERIC") {
+ type = KEYBOARD_TYPE_NUMERIC;
+ } else if (typeToken == "PREDICTIVE") {
+ type = KEYBOARD_TYPE_PREDICTIVE;
+ } else if (typeToken == "ALPHA") {
+ type = KEYBOARD_TYPE_ALPHA;
+ } else if (typeToken == "FULL") {
+ type = KEYBOARD_TYPE_FULL;
+ } else if (typeToken == "SPECIAL_FUNCTION") {
+ type = KEYBOARD_TYPE_SPECIAL_FUNCTION;
+ } else if (typeToken == "OVERLAY") {
+ type = KEYBOARD_TYPE_OVERLAY;
+ } else {
+ ALOGE("%s: Expected keyboard type label, got '%s'.", mTokenizer->getLocation().string(),
+ typeToken.string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed type: type=%d.", type);
+#endif
+ mMap->mType = type;
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseMap() {
+ String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
+ if (keywordToken == "key") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ return parseMapKey();
+ }
+ ALOGE("%s: Expected keyword after 'map', got '%s'.", mTokenizer->getLocation().string(),
+ keywordToken.string());
+ return BAD_VALUE;
+}
+
+status_t KeyCharacterMap::Parser::parseMapKey() {
+ String8 codeToken = mTokenizer->nextToken(WHITESPACE);
+ bool mapUsage = false;
+ if (codeToken == "usage") {
+ mapUsage = true;
+ mTokenizer->skipDelimiters(WHITESPACE);
+ codeToken = mTokenizer->nextToken(WHITESPACE);
+ }
+
+ char* end;
+ int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+ KeyedVector<int32_t, int32_t>& map =
+ mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
+ if (map.indexOfKey(code) >= 0) {
+ ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
+ int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
+ if (!keyCode) {
+ ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
+ keyCodeToken.string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed map key %s: code=%d, keyCode=%d.",
+ mapUsage ? "usage" : "scan code", code, keyCode);
+#endif
+ map.add(code, keyCode);
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseKey() {
+ String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
+ int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
+ if (!keyCode) {
+ ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
+ keyCodeToken.string());
+ return BAD_VALUE;
+ }
+ if (mMap->mKeys.indexOfKey(keyCode) >= 0) {
+ ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(),
+ keyCodeToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 openBraceToken = mTokenizer->nextToken(WHITESPACE);
+ if (openBraceToken != "{") {
+ ALOGE("%s: Expected '{' after key code label, got '%s'.",
+ mTokenizer->getLocation().string(), openBraceToken.string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed beginning of key: keyCode=%d.", keyCode);
+#endif
+ mKeyCode = keyCode;
+ mMap->mKeys.add(keyCode, new Key());
+ mState = STATE_KEY;
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseKeyProperty() {
+ Key* key = mMap->mKeys.valueFor(mKeyCode);
+ String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
+ if (token == "}") {
+ mState = STATE_TOP;
+ return finishKey(key);
+ }
+
+ Vector<Property> properties;
+
+ // Parse all comma-delimited property names up to the first colon.
+ for (;;) {
+ if (token == "label") {
+ properties.add(Property(PROPERTY_LABEL));
+ } else if (token == "number") {
+ properties.add(Property(PROPERTY_NUMBER));
+ } else {
+ int32_t metaState;
+ status_t status = parseModifier(token, &metaState);
+ if (status) {
+ ALOGE("%s: Expected a property name or modifier, got '%s'.",
+ mTokenizer->getLocation().string(), token.string());
+ return status;
+ }
+ properties.add(Property(PROPERTY_META, metaState));
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (!mTokenizer->isEol()) {
+ char ch = mTokenizer->nextChar();
+ if (ch == ':') {
+ break;
+ } else if (ch == ',') {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
+ continue;
+ }
+ }
+
+ ALOGE("%s: Expected ',' or ':' after property name.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ // Parse behavior after the colon.
+ mTokenizer->skipDelimiters(WHITESPACE);
+
+ Behavior behavior;
+ bool haveCharacter = false;
+ bool haveFallback = false;
+
+ do {
+ char ch = mTokenizer->peekChar();
+ if (ch == '\'') {
+ char16_t character;
+ status_t status = parseCharacterLiteral(&character);
+ if (status || !character) {
+ ALOGE("%s: Invalid character literal for key.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ if (haveCharacter) {
+ ALOGE("%s: Cannot combine multiple character literals or 'none'.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ behavior.character = character;
+ haveCharacter = true;
+ } else {
+ token = mTokenizer->nextToken(WHITESPACE);
+ if (token == "none") {
+ if (haveCharacter) {
+ ALOGE("%s: Cannot combine multiple character literals or 'none'.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ haveCharacter = true;
+ } else if (token == "fallback") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ token = mTokenizer->nextToken(WHITESPACE);
+ int32_t keyCode = getKeyCodeByLabel(token.string());
+ if (!keyCode) {
+ ALOGE("%s: Invalid key code label for fallback behavior, got '%s'.",
+ mTokenizer->getLocation().string(),
+ token.string());
+ return BAD_VALUE;
+ }
+ if (haveFallback) {
+ ALOGE("%s: Cannot combine multiple fallback key codes.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ behavior.fallbackKeyCode = keyCode;
+ haveFallback = true;
+ } else {
+ ALOGE("%s: Expected a key behavior after ':'.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ } while (!mTokenizer->isEol() && mTokenizer->peekChar() != '#');
+
+ // Add the behavior.
+ for (size_t i = 0; i < properties.size(); i++) {
+ const Property& property = properties.itemAt(i);
+ switch (property.property) {
+ case PROPERTY_LABEL:
+ if (key->label) {
+ ALOGE("%s: Duplicate label for key.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ key->label = behavior.character;
+#if DEBUG_PARSER
+ ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label);
+#endif
+ break;
+ case PROPERTY_NUMBER:
+ if (key->number) {
+ ALOGE("%s: Duplicate number for key.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ key->number = behavior.character;
+#if DEBUG_PARSER
+ ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number);
+#endif
+ break;
+ case PROPERTY_META: {
+ for (Behavior* b = key->firstBehavior; b; b = b->next) {
+ if (b->metaState == property.metaState) {
+ ALOGE("%s: Duplicate key behavior for modifier.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ }
+ Behavior* newBehavior = new Behavior(behavior);
+ newBehavior->metaState = property.metaState;
+ newBehavior->next = key->firstBehavior;
+ key->firstBehavior = newBehavior;
+#if DEBUG_PARSER
+ ALOGD("Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d.", mKeyCode,
+ newBehavior->metaState, newBehavior->character, newBehavior->fallbackKeyCode);
+#endif
+ break;
+ }
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::finishKey(Key* key) {
+ // Fill in default number property.
+ if (!key->number) {
+ char16_t digit = 0;
+ char16_t symbol = 0;
+ for (Behavior* b = key->firstBehavior; b; b = b->next) {
+ char16_t ch = b->character;
+ if (ch) {
+ if (ch >= '0' && ch <= '9') {
+ digit = ch;
+ } else if (ch == '(' || ch == ')' || ch == '#' || ch == '*'
+ || ch == '-' || ch == '+' || ch == ',' || ch == '.'
+ || ch == '\'' || ch == ':' || ch == ';' || ch == '/') {
+ symbol = ch;
+ }
+ }
+ }
+ key->number = digit ? digit : symbol;
+ }
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseModifier(const String8& token, int32_t* outMetaState) {
+ if (token == "base") {
+ *outMetaState = 0;
+ return NO_ERROR;
+ }
+
+ int32_t combinedMeta = 0;
+
+ const char* str = token.string();
+ const char* start = str;
+ for (const char* cur = str; ; cur++) {
+ char ch = *cur;
+ if (ch == '+' || ch == '\0') {
+ size_t len = cur - start;
+ int32_t metaState = 0;
+ for (size_t i = 0; i < sizeof(modifiers) / sizeof(Modifier); i++) {
+ if (strlen(modifiers[i].label) == len
+ && strncmp(modifiers[i].label, start, len) == 0) {
+ metaState = modifiers[i].metaState;
+ break;
+ }
+ }
+ if (!metaState) {
+ return BAD_VALUE;
+ }
+ if (combinedMeta & metaState) {
+ ALOGE("%s: Duplicate modifier combination '%s'.",
+ mTokenizer->getLocation().string(), token.string());
+ return BAD_VALUE;
+ }
+
+ combinedMeta |= metaState;
+ start = cur + 1;
+
+ if (ch == '\0') {
+ break;
+ }
+ }
+ }
+ *outMetaState = combinedMeta;
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseCharacterLiteral(char16_t* outCharacter) {
+ char ch = mTokenizer->nextChar();
+ if (ch != '\'') {
+ goto Error;
+ }
+
+ ch = mTokenizer->nextChar();
+ if (ch == '\\') {
+ // Escape sequence.
+ ch = mTokenizer->nextChar();
+ if (ch == 'n') {
+ *outCharacter = '\n';
+ } else if (ch == 't') {
+ *outCharacter = '\t';
+ } else if (ch == '\\') {
+ *outCharacter = '\\';
+ } else if (ch == '\'') {
+ *outCharacter = '\'';
+ } else if (ch == '"') {
+ *outCharacter = '"';
+ } else if (ch == 'u') {
+ *outCharacter = 0;
+ for (int i = 0; i < 4; i++) {
+ ch = mTokenizer->nextChar();
+ int digit;
+ if (ch >= '0' && ch <= '9') {
+ digit = ch - '0';
+ } else if (ch >= 'A' && ch <= 'F') {
+ digit = ch - 'A' + 10;
+ } else if (ch >= 'a' && ch <= 'f') {
+ digit = ch - 'a' + 10;
+ } else {
+ goto Error;
+ }
+ *outCharacter = (*outCharacter << 4) | digit;
+ }
+ } else {
+ goto Error;
+ }
+ } else if (ch >= 32 && ch <= 126 && ch != '\'') {
+ // ASCII literal character.
+ *outCharacter = ch;
+ } else {
+ goto Error;
+ }
+
+ ch = mTokenizer->nextChar();
+ if (ch != '\'') {
+ goto Error;
+ }
+
+ // Ensure that we consumed the entire token.
+ if (mTokenizer->nextToken(WHITESPACE).isEmpty()) {
+ return NO_ERROR;
+ }
+
+Error:
+ ALOGE("%s: Malformed character literal.", mTokenizer->getLocation().string());
+ return BAD_VALUE;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/KeyCharacterMap.h b/widget/gonk/libui/KeyCharacterMap.h
new file mode 100644
index 000000000..c7a684105
--- /dev/null
+++ b/widget/gonk/libui/KeyCharacterMap.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_KEY_CHARACTER_MAP_H
+#define _ANDROIDFW_KEY_CHARACTER_MAP_H
+
+#include <stdint.h>
+
+#if HAVE_ANDROID_OS
+#include <binder/IBinder.h>
+#endif
+
+#include "Input.h"
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include "Tokenizer.h"
+#include <utils/String8.h>
+#include <utils/Unicode.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+/**
+ * Describes a mapping from Android key codes to characters.
+ * Also specifies other functions of the keyboard such as the keyboard type
+ * and key modifier semantics.
+ *
+ * This object is immutable after it has been loaded.
+ */
+class KeyCharacterMap : public RefBase {
+public:
+ enum KeyboardType {
+ KEYBOARD_TYPE_UNKNOWN = 0,
+ KEYBOARD_TYPE_NUMERIC = 1,
+ KEYBOARD_TYPE_PREDICTIVE = 2,
+ KEYBOARD_TYPE_ALPHA = 3,
+ KEYBOARD_TYPE_FULL = 4,
+ KEYBOARD_TYPE_SPECIAL_FUNCTION = 5,
+ KEYBOARD_TYPE_OVERLAY = 6,
+ };
+
+ enum Format {
+ // Base keyboard layout, may contain device-specific options, such as "type" declaration.
+ FORMAT_BASE = 0,
+ // Overlay keyboard layout, more restrictive, may be published by applications,
+ // cannot override device-specific options.
+ FORMAT_OVERLAY = 1,
+ // Either base or overlay layout ok.
+ FORMAT_ANY = 2,
+ };
+
+ // Substitute key code and meta state for fallback action.
+ struct FallbackAction {
+ int32_t keyCode;
+ int32_t metaState;
+ };
+
+ /* Loads a key character map from a file. */
+ static status_t load(const String8& filename, Format format, sp<KeyCharacterMap>* outMap);
+
+ /* Loads a key character map from its string contents. */
+ static status_t loadContents(const String8& filename,
+ const char* contents, Format format, sp<KeyCharacterMap>* outMap);
+
+ /* Combines a base key character map and an overlay. */
+ static sp<KeyCharacterMap> combine(const sp<KeyCharacterMap>& base,
+ const sp<KeyCharacterMap>& overlay);
+
+ /* Returns an empty key character map. */
+ static sp<KeyCharacterMap> empty();
+
+ /* Gets the keyboard type. */
+ int32_t getKeyboardType() const;
+
+ /* Gets the primary character for this key as in the label physically printed on it.
+ * Returns 0 if none (eg. for non-printing keys). */
+ char16_t getDisplayLabel(int32_t keyCode) const;
+
+ /* Gets the Unicode character for the number or symbol generated by the key
+ * when the keyboard is used as a dialing pad.
+ * Returns 0 if no number or symbol is generated.
+ */
+ char16_t getNumber(int32_t keyCode) const;
+
+ /* Gets the Unicode character generated by the key and meta key modifiers.
+ * Returns 0 if no character is generated.
+ */
+ char16_t getCharacter(int32_t keyCode, int32_t metaState) const;
+
+ /* Gets the fallback action to use by default if the application does not
+ * handle the specified key.
+ * Returns true if an action was available, false if none.
+ */
+ bool getFallbackAction(int32_t keyCode, int32_t metaState,
+ FallbackAction* outFallbackAction) const;
+
+ /* Gets the first matching Unicode character that can be generated by the key,
+ * preferring the one with the specified meta key modifiers.
+ * Returns 0 if no matching character is generated.
+ */
+ char16_t getMatch(int32_t keyCode, const char16_t* chars,
+ size_t numChars, int32_t metaState) const;
+
+ /* Gets a sequence of key events that could plausibly generate the specified
+ * character sequence. Returns false if some of the characters cannot be generated.
+ */
+ bool getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
+ Vector<KeyEvent>& outEvents) const;
+
+ /* Maps a scan code and usage code to a key code, in case this key map overrides
+ * the mapping in some way. */
+ status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const;
+
+#if HAVE_ANDROID_OS
+ /* Reads a key map from a parcel. */
+ static sp<KeyCharacterMap> readFromParcel(Parcel* parcel);
+
+ /* Writes a key map to a parcel. */
+ void writeToParcel(Parcel* parcel) const;
+#endif
+
+protected:
+ virtual ~KeyCharacterMap();
+
+private:
+ struct Behavior {
+ Behavior();
+ Behavior(const Behavior& other);
+
+ /* The next behavior in the list, or NULL if none. */
+ Behavior* next;
+
+ /* The meta key modifiers for this behavior. */
+ int32_t metaState;
+
+ /* The character to insert. */
+ char16_t character;
+
+ /* The fallback keycode if the key is not handled. */
+ int32_t fallbackKeyCode;
+ };
+
+ struct Key {
+ Key();
+ Key(const Key& other);
+ ~Key();
+
+ /* The single character label printed on the key, or 0 if none. */
+ char16_t label;
+
+ /* The number or symbol character generated by the key, or 0 if none. */
+ char16_t number;
+
+ /* The list of key behaviors sorted from most specific to least specific
+ * meta key binding. */
+ Behavior* firstBehavior;
+ };
+
+ class Parser {
+ enum State {
+ STATE_TOP = 0,
+ STATE_KEY = 1,
+ };
+
+ enum {
+ PROPERTY_LABEL = 1,
+ PROPERTY_NUMBER = 2,
+ PROPERTY_META = 3,
+ };
+
+ struct Property {
+ inline Property(int32_t property = 0, int32_t metaState = 0) :
+ property(property), metaState(metaState) { }
+
+ int32_t property;
+ int32_t metaState;
+ };
+
+ KeyCharacterMap* mMap;
+ Tokenizer* mTokenizer;
+ Format mFormat;
+ State mState;
+ int32_t mKeyCode;
+
+ public:
+ Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format);
+ ~Parser();
+ status_t parse();
+
+ private:
+ status_t parseType();
+ status_t parseMap();
+ status_t parseMapKey();
+ status_t parseKey();
+ status_t parseKeyProperty();
+ status_t finishKey(Key* key);
+ status_t parseModifier(const String8& token, int32_t* outMetaState);
+ status_t parseCharacterLiteral(char16_t* outCharacter);
+ };
+
+ static sp<KeyCharacterMap> sEmpty;
+
+ KeyedVector<int32_t, Key*> mKeys;
+ int mType;
+
+ KeyedVector<int32_t, int32_t> mKeysByScanCode;
+ KeyedVector<int32_t, int32_t> mKeysByUsageCode;
+
+ KeyCharacterMap();
+ KeyCharacterMap(const KeyCharacterMap& other);
+
+ bool getKey(int32_t keyCode, const Key** outKey) const;
+ bool getKeyBehavior(int32_t keyCode, int32_t metaState,
+ const Key** outKey, const Behavior** outBehavior) const;
+ static bool matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState);
+
+ bool findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const;
+
+ static status_t load(Tokenizer* tokenizer, Format format, sp<KeyCharacterMap>* outMap);
+
+ static void addKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time);
+ static void addMetaKeys(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t* currentMetaState);
+ static bool addSingleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t keyCode, int32_t keyMetaState,
+ int32_t* currentMetaState);
+ static void addDoubleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t leftKeyCode, int32_t leftKeyMetaState,
+ int32_t rightKeyCode, int32_t rightKeyMetaState,
+ int32_t eitherKeyMetaState,
+ int32_t* currentMetaState);
+ static void addLockedMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, nsecs_t time,
+ int32_t keyCode, int32_t keyMetaState,
+ int32_t* currentMetaState);
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_KEY_CHARACTER_MAP_H
diff --git a/widget/gonk/libui/KeyLayoutMap.cpp b/widget/gonk/libui/KeyLayoutMap.cpp
new file mode 100644
index 000000000..8af4b84e0
--- /dev/null
+++ b/widget/gonk/libui/KeyLayoutMap.cpp
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "KeyLayoutMap"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include "android_keycodes.h"
+#include "Keyboard.h"
+#include "KeyLayoutMap.h"
+#include <utils/Errors.h>
+#include "Tokenizer.h"
+#include <utils/Timers.h>
+
+// Enables debug output for the parser.
+#define DEBUG_PARSER 0
+
+// Enables debug output for parser performance.
+#define DEBUG_PARSER_PERFORMANCE 0
+
+// Enables debug output for mapping.
+#define DEBUG_MAPPING 0
+
+
+namespace android {
+
+static const char* WHITESPACE = " \t\r";
+
+// --- KeyLayoutMap ---
+
+KeyLayoutMap::KeyLayoutMap() {
+}
+
+KeyLayoutMap::~KeyLayoutMap() {
+}
+
+status_t KeyLayoutMap::load(const String8& filename, sp<KeyLayoutMap>* outMap) {
+ outMap->clear();
+
+ Tokenizer* tokenizer;
+ status_t status = Tokenizer::open(filename, &tokenizer);
+ if (status) {
+ ALOGE("Error %d opening key layout map file %s.", status, filename.string());
+ } else {
+ sp<KeyLayoutMap> map = new KeyLayoutMap();
+ if (!map.get()) {
+ ALOGE("Error allocating key layout map.");
+ status = NO_MEMORY;
+ } else {
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+#endif
+ Parser parser(map.get(), tokenizer);
+ status = parser.parse();
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
+ ALOGD("Parsed key layout map file '%s' %d lines in %0.3fms.",
+ tokenizer->getFilename().string(), tokenizer->getLineNumber(),
+ elapsedTime / 1000000.0);
+#endif
+ if (!status) {
+ *outMap = map;
+ }
+ }
+ delete tokenizer;
+ }
+ return status;
+}
+
+status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t usageCode,
+ int32_t* outKeyCode, uint32_t* outFlags) const {
+ const Key* key = getKey(scanCode, usageCode);
+ if (!key) {
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode);
+#endif
+ *outKeyCode = AKEYCODE_UNKNOWN;
+ *outFlags = 0;
+ return NAME_NOT_FOUND;
+ }
+
+ *outKeyCode = key->keyCode;
+ *outFlags = key->flags;
+
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d, outFlags=0x%08x.",
+ scanCode, usageCode, *outKeyCode, *outFlags);
+#endif
+ return NO_ERROR;
+}
+
+const KeyLayoutMap::Key* KeyLayoutMap::getKey(int32_t scanCode, int32_t usageCode) const {
+ if (usageCode) {
+ ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
+ if (index >= 0) {
+ return &mKeysByUsageCode.valueAt(index);
+ }
+ }
+ if (scanCode) {
+ ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
+ if (index >= 0) {
+ return &mKeysByScanCode.valueAt(index);
+ }
+ }
+ return NULL;
+}
+
+status_t KeyLayoutMap::findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const {
+ const size_t N = mKeysByScanCode.size();
+ for (size_t i=0; i<N; i++) {
+ if (mKeysByScanCode.valueAt(i).keyCode == keyCode) {
+ outScanCodes->add(mKeysByScanCode.keyAt(i));
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const {
+ ssize_t index = mAxes.indexOfKey(scanCode);
+ if (index < 0) {
+#if DEBUG_MAPPING
+ ALOGD("mapAxis: scanCode=%d ~ Failed.", scanCode);
+#endif
+ return NAME_NOT_FOUND;
+ }
+
+ *outAxisInfo = mAxes.valueAt(index);
+
+#if DEBUG_MAPPING
+ ALOGD("mapAxis: scanCode=%d ~ Result mode=%d, axis=%d, highAxis=%d, "
+ "splitValue=%d, flatOverride=%d.",
+ scanCode,
+ outAxisInfo->mode, outAxisInfo->axis, outAxisInfo->highAxis,
+ outAxisInfo->splitValue, outAxisInfo->flatOverride);
+#endif
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::findScanCodeForLed(int32_t ledCode, int32_t* outScanCode) const {
+ const size_t N = mLedsByScanCode.size();
+ for (size_t i = 0; i < N; i++) {
+ if (mLedsByScanCode.valueAt(i).ledCode == ledCode) {
+ *outScanCode = mLedsByScanCode.keyAt(i);
+#if DEBUG_MAPPING
+ ALOGD("findScanCodeForLed: ledCode=%d, scanCode=%d.", ledCode, *outScanCode);
+#endif
+ return NO_ERROR;
+ }
+ }
+#if DEBUG_MAPPING
+ ALOGD("findScanCodeForLed: ledCode=%d ~ Not found.", ledCode);
+#endif
+ return NAME_NOT_FOUND;
+}
+
+status_t KeyLayoutMap::findUsageCodeForLed(int32_t ledCode, int32_t* outUsageCode) const {
+ const size_t N = mLedsByUsageCode.size();
+ for (size_t i = 0; i < N; i++) {
+ if (mLedsByUsageCode.valueAt(i).ledCode == ledCode) {
+ *outUsageCode = mLedsByUsageCode.keyAt(i);
+#if DEBUG_MAPPING
+ ALOGD("findUsageForLed: ledCode=%d, usage=%x.", ledCode, *outUsageCode);
+#endif
+ return NO_ERROR;
+ }
+ }
+#if DEBUG_MAPPING
+ ALOGD("findUsageForLed: ledCode=%d ~ Not found.", ledCode);
+#endif
+ return NAME_NOT_FOUND;
+}
+
+
+// --- KeyLayoutMap::Parser ---
+
+KeyLayoutMap::Parser::Parser(KeyLayoutMap* map, Tokenizer* tokenizer) :
+ mMap(map), mTokenizer(tokenizer) {
+}
+
+KeyLayoutMap::Parser::~Parser() {
+}
+
+status_t KeyLayoutMap::Parser::parse() {
+ while (!mTokenizer->isEof()) {
+#if DEBUG_PARSER
+ ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+#endif
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
+ if (keywordToken == "key") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseKey();
+ if (status) return status;
+ } else if (keywordToken == "axis") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseAxis();
+ if (status) return status;
+ } else if (keywordToken == "led") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseLed();
+ if (status) return status;
+ } else {
+ ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
+ keywordToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
+ mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+ return BAD_VALUE;
+ }
+ }
+
+ mTokenizer->nextLine();
+ }
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::Parser::parseKey() {
+ String8 codeToken = mTokenizer->nextToken(WHITESPACE);
+ bool mapUsage = false;
+ if (codeToken == "usage") {
+ mapUsage = true;
+ mTokenizer->skipDelimiters(WHITESPACE);
+ codeToken = mTokenizer->nextToken(WHITESPACE);
+ }
+
+ char* end;
+ int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+ KeyedVector<int32_t, Key>& map =
+ mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
+ if (map.indexOfKey(code) >= 0) {
+ ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
+ int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
+ if (!keyCode) {
+ ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
+ keyCodeToken.string());
+ return BAD_VALUE;
+ }
+
+ uint32_t flags = 0;
+ for (;;) {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;
+
+ String8 flagToken = mTokenizer->nextToken(WHITESPACE);
+ uint32_t flag = getKeyFlagByLabel(flagToken.string());
+ if (!flag) {
+ ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(),
+ flagToken.string());
+ return BAD_VALUE;
+ }
+ if (flags & flag) {
+ ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(),
+ flagToken.string());
+ return BAD_VALUE;
+ }
+ flags |= flag;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.",
+ mapUsage ? "usage" : "scan code", code, keyCode, flags);
+#endif
+ Key key;
+ key.keyCode = keyCode;
+ key.flags = flags;
+ map.add(code, key);
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::Parser::parseAxis() {
+ String8 scanCodeToken = mTokenizer->nextToken(WHITESPACE);
+ char* end;
+ int32_t scanCode = int32_t(strtol(scanCodeToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected axis scan code number, got '%s'.", mTokenizer->getLocation().string(),
+ scanCodeToken.string());
+ return BAD_VALUE;
+ }
+ if (mMap->mAxes.indexOfKey(scanCode) >= 0) {
+ ALOGE("%s: Duplicate entry for axis scan code '%s'.", mTokenizer->getLocation().string(),
+ scanCodeToken.string());
+ return BAD_VALUE;
+ }
+
+ AxisInfo axisInfo;
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 token = mTokenizer->nextToken(WHITESPACE);
+ if (token == "invert") {
+ axisInfo.mode = AxisInfo::MODE_INVERT;
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 axisToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.axis = getAxisByLabel(axisToken.string());
+ if (axisInfo.axis < 0) {
+ ALOGE("%s: Expected inverted axis label, got '%s'.",
+ mTokenizer->getLocation().string(), axisToken.string());
+ return BAD_VALUE;
+ }
+ } else if (token == "split") {
+ axisInfo.mode = AxisInfo::MODE_SPLIT;
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 splitToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.splitValue = int32_t(strtol(splitToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected split value, got '%s'.",
+ mTokenizer->getLocation().string(), splitToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 lowAxisToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.axis = getAxisByLabel(lowAxisToken.string());
+ if (axisInfo.axis < 0) {
+ ALOGE("%s: Expected low axis label, got '%s'.",
+ mTokenizer->getLocation().string(), lowAxisToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 highAxisToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.highAxis = getAxisByLabel(highAxisToken.string());
+ if (axisInfo.highAxis < 0) {
+ ALOGE("%s: Expected high axis label, got '%s'.",
+ mTokenizer->getLocation().string(), highAxisToken.string());
+ return BAD_VALUE;
+ }
+ } else {
+ axisInfo.axis = getAxisByLabel(token.string());
+ if (axisInfo.axis < 0) {
+ ALOGE("%s: Expected axis label, 'split' or 'invert', got '%s'.",
+ mTokenizer->getLocation().string(), token.string());
+ return BAD_VALUE;
+ }
+ }
+
+ for (;;) {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') {
+ break;
+ }
+ String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
+ if (keywordToken == "flat") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 flatToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.flatOverride = int32_t(strtol(flatToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected flat value, got '%s'.",
+ mTokenizer->getLocation().string(), flatToken.string());
+ return BAD_VALUE;
+ }
+ } else {
+ ALOGE("%s: Expected keyword 'flat', got '%s'.",
+ mTokenizer->getLocation().string(), keywordToken.string());
+ return BAD_VALUE;
+ }
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, "
+ "splitValue=%d, flatOverride=%d.",
+ scanCode,
+ axisInfo.mode, axisInfo.axis, axisInfo.highAxis,
+ axisInfo.splitValue, axisInfo.flatOverride);
+#endif
+ mMap->mAxes.add(scanCode, axisInfo);
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::Parser::parseLed() {
+ String8 codeToken = mTokenizer->nextToken(WHITESPACE);
+ bool mapUsage = false;
+ if (codeToken == "usage") {
+ mapUsage = true;
+ mTokenizer->skipDelimiters(WHITESPACE);
+ codeToken = mTokenizer->nextToken(WHITESPACE);
+ }
+ char* end;
+ int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected led %s number, got '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ KeyedVector<int32_t, Led>& map = mapUsage ? mMap->mLedsByUsageCode : mMap->mLedsByScanCode;
+ if (map.indexOfKey(code) >= 0) {
+ ALOGE("%s: Duplicate entry for led %s '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 ledCodeToken = mTokenizer->nextToken(WHITESPACE);
+ int32_t ledCode = getLedByLabel(ledCodeToken.string());
+ if (ledCode < 0) {
+ ALOGE("%s: Expected LED code label, got '%s'.", mTokenizer->getLocation().string(),
+ ledCodeToken.string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed led %s: code=%d, ledCode=%d.",
+ mapUsage ? "usage" : "scan code", code, ledCode);
+#endif
+
+ Led led;
+ led.ledCode = ledCode;
+ map.add(code, led);
+ return NO_ERROR;
+}
+};
diff --git a/widget/gonk/libui/KeyLayoutMap.h b/widget/gonk/libui/KeyLayoutMap.h
new file mode 100644
index 000000000..8a6113447
--- /dev/null
+++ b/widget/gonk/libui/KeyLayoutMap.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_KEY_LAYOUT_MAP_H
+#define _ANDROIDFW_KEY_LAYOUT_MAP_H
+
+#include <stdint.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include "Tokenizer.h"
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct AxisInfo {
+ enum Mode {
+ // Axis value is reported directly.
+ MODE_NORMAL = 0,
+ // Axis value should be inverted before reporting.
+ MODE_INVERT = 1,
+ // Axis value should be split into two axes
+ MODE_SPLIT = 2,
+ };
+
+ // Axis mode.
+ Mode mode;
+
+ // Axis id.
+ // When split, this is the axis used for values smaller than the split position.
+ int32_t axis;
+
+ // When split, this is the axis used for values after higher than the split position.
+ int32_t highAxis;
+
+ // The split value, or 0 if not split.
+ int32_t splitValue;
+
+ // The flat value, or -1 if none.
+ int32_t flatOverride;
+
+ AxisInfo() : mode(MODE_NORMAL), axis(-1), highAxis(-1), splitValue(0), flatOverride(-1) {
+ }
+};
+
+/**
+ * Describes a mapping from keyboard scan codes and joystick axes to Android key codes and axes.
+ *
+ * This object is immutable after it has been loaded.
+ */
+class KeyLayoutMap : public RefBase {
+public:
+ static status_t load(const String8& filename, sp<KeyLayoutMap>* outMap);
+
+ status_t mapKey(int32_t scanCode, int32_t usageCode,
+ int32_t* outKeyCode, uint32_t* outFlags) const;
+ status_t findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const;
+ status_t findScanCodeForLed(int32_t ledCode, int32_t* outScanCode) const;
+ status_t findUsageCodeForLed(int32_t ledCode, int32_t* outUsageCode) const;
+
+ status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const;
+
+protected:
+ virtual ~KeyLayoutMap();
+
+private:
+ struct Key {
+ int32_t keyCode;
+ uint32_t flags;
+ };
+
+ struct Led {
+ int32_t ledCode;
+ };
+
+
+ KeyedVector<int32_t, Key> mKeysByScanCode;
+ KeyedVector<int32_t, Key> mKeysByUsageCode;
+ KeyedVector<int32_t, AxisInfo> mAxes;
+ KeyedVector<int32_t, Led> mLedsByScanCode;
+ KeyedVector<int32_t, Led> mLedsByUsageCode;
+
+ KeyLayoutMap();
+
+ const Key* getKey(int32_t scanCode, int32_t usageCode) const;
+
+ class Parser {
+ KeyLayoutMap* mMap;
+ Tokenizer* mTokenizer;
+
+ public:
+ Parser(KeyLayoutMap* map, Tokenizer* tokenizer);
+ ~Parser();
+ status_t parse();
+
+ private:
+ status_t parseKey();
+ status_t parseAxis();
+ status_t parseLed();
+ };
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_KEY_LAYOUT_MAP_H
diff --git a/widget/gonk/libui/Keyboard.cpp b/widget/gonk/libui/Keyboard.cpp
new file mode 100644
index 000000000..62bb53b7b
--- /dev/null
+++ b/widget/gonk/libui/Keyboard.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Keyboard"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "Keyboard.h"
+#include "KeycodeLabels.h"
+#include "KeyLayoutMap.h"
+#include "KeyCharacterMap.h"
+#include "InputDevice.h"
+#include <utils/Errors.h>
+#include <cutils/properties.h>
+
+namespace android {
+
+// --- KeyMap ---
+
+KeyMap::KeyMap() {
+}
+
+KeyMap::~KeyMap() {
+}
+
+status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
+ const PropertyMap* deviceConfiguration) {
+ // Use the configured key layout if available.
+ if (deviceConfiguration) {
+ String8 keyLayoutName;
+ if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
+ keyLayoutName)) {
+ status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
+ if (status == NAME_NOT_FOUND) {
+ ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
+ "it was not found.",
+ deviceIdenfifier.name.string(), keyLayoutName.string());
+ }
+ }
+
+ String8 keyCharacterMapName;
+ if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
+ keyCharacterMapName)) {
+ status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
+ if (status == NAME_NOT_FOUND) {
+ ALOGE("Configuration for keyboard device '%s' requested keyboard character "
+ "map '%s' but it was not found.",
+ deviceIdenfifier.name.string(), keyLayoutName.string());
+ }
+ }
+
+ if (isComplete()) {
+ return OK;
+ }
+ }
+
+ // Try searching by device identifier.
+ if (probeKeyMap(deviceIdenfifier, String8::empty())) {
+ return OK;
+ }
+
+ // Fall back on the Generic key map.
+ // TODO Apply some additional heuristics here to figure out what kind of
+ // generic key map to use (US English, etc.) for typical external keyboards.
+ if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {
+ return OK;
+ }
+
+ // Try the Virtual key map as a last resort.
+ if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {
+ return OK;
+ }
+
+ // Give up!
+ ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
+ deviceIdenfifier.name.string());
+ return NAME_NOT_FOUND;
+}
+
+bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& keyMapName) {
+ if (!haveKeyLayout()) {
+ loadKeyLayout(deviceIdentifier, keyMapName);
+ }
+ if (!haveKeyCharacterMap()) {
+ loadKeyCharacterMap(deviceIdentifier, keyMapName);
+ }
+ return isComplete();
+}
+
+status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name) {
+ String8 path(getPath(deviceIdentifier, name,
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
+ if (path.isEmpty()) {
+ return NAME_NOT_FOUND;
+ }
+
+ status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
+ if (status) {
+ return status;
+ }
+
+ keyLayoutFile.setTo(path);
+ return OK;
+}
+
+status_t KeyMap::loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name) {
+ String8 path(getPath(deviceIdentifier, name,
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP));
+ if (path.isEmpty()) {
+ return NAME_NOT_FOUND;
+ }
+
+ status_t status = KeyCharacterMap::load(path,
+ KeyCharacterMap::FORMAT_BASE, &keyCharacterMap);
+ if (status) {
+ return status;
+ }
+
+ keyCharacterMapFile.setTo(path);
+ return OK;
+}
+
+String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name, InputDeviceConfigurationFileType type) {
+ return name.isEmpty()
+ ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
+ : getInputDeviceConfigurationFilePathByName(name, type);
+}
+
+
+// --- Global functions ---
+
+bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
+ const PropertyMap* deviceConfiguration, const KeyMap* keyMap) {
+ if (!keyMap->haveKeyCharacterMap()
+ || keyMap->keyCharacterMap->getKeyboardType()
+ == KeyCharacterMap::KEYBOARD_TYPE_SPECIAL_FUNCTION) {
+ return false;
+ }
+
+ if (deviceConfiguration) {
+ bool builtIn = false;
+ if (deviceConfiguration->tryGetProperty(String8("keyboard.builtIn"), builtIn)
+ && builtIn) {
+ return true;
+ }
+ }
+
+ return strstr(deviceIdentifier.name.string(), "-keypad");
+}
+
+static int lookupValueByLabel(const char* literal, const KeycodeLabel *list) {
+ while (list->literal) {
+ if (strcmp(literal, list->literal) == 0) {
+ return list->value;
+ }
+ list++;
+ }
+ return list->value;
+}
+
+static const char* lookupLabelByValue(int value, const KeycodeLabel *list) {
+ while (list->literal) {
+ if (list->value == value) {
+ return list->literal;
+ }
+ list++;
+ }
+ return NULL;
+}
+
+int32_t getKeyCodeByLabel(const char* label) {
+ return int32_t(lookupValueByLabel(label, KEYCODES));
+}
+
+uint32_t getKeyFlagByLabel(const char* label) {
+ return uint32_t(lookupValueByLabel(label, FLAGS));
+}
+
+int32_t getAxisByLabel(const char* label) {
+ return int32_t(lookupValueByLabel(label, AXES));
+}
+
+const char* getAxisLabel(int32_t axisId) {
+ return lookupLabelByValue(axisId, AXES);
+}
+
+int32_t getLedByLabel(const char* label) {
+ return int32_t(lookupValueByLabel(label, LEDS));
+}
+static int32_t setEphemeralMetaState(int32_t mask, bool down, int32_t oldMetaState) {
+ int32_t newMetaState;
+ if (down) {
+ newMetaState = oldMetaState | mask;
+ } else {
+ newMetaState = oldMetaState &
+ ~(mask | AMETA_ALT_ON | AMETA_SHIFT_ON | AMETA_CTRL_ON | AMETA_META_ON);
+ }
+
+ if (newMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
+ newMetaState |= AMETA_ALT_ON;
+ }
+
+ if (newMetaState & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
+ newMetaState |= AMETA_SHIFT_ON;
+ }
+
+ if (newMetaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
+ newMetaState |= AMETA_CTRL_ON;
+ }
+
+ if (newMetaState & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
+ newMetaState |= AMETA_META_ON;
+ }
+ return newMetaState;
+}
+
+static int32_t toggleLockedMetaState(int32_t mask, bool down, int32_t oldMetaState) {
+ if (down) {
+ return oldMetaState;
+ } else {
+ return oldMetaState ^ mask;
+ }
+}
+
+int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
+ switch (keyCode) {
+ case AKEYCODE_ALT_LEFT:
+ return setEphemeralMetaState(AMETA_ALT_LEFT_ON, down, oldMetaState);
+ case AKEYCODE_ALT_RIGHT:
+ return setEphemeralMetaState(AMETA_ALT_RIGHT_ON, down, oldMetaState);
+ case AKEYCODE_SHIFT_LEFT:
+ return setEphemeralMetaState(AMETA_SHIFT_LEFT_ON, down, oldMetaState);
+ case AKEYCODE_SHIFT_RIGHT:
+ return setEphemeralMetaState(AMETA_SHIFT_RIGHT_ON, down, oldMetaState);
+ case AKEYCODE_SYM:
+ return setEphemeralMetaState(AMETA_SYM_ON, down, oldMetaState);
+ case AKEYCODE_FUNCTION:
+ return setEphemeralMetaState(AMETA_FUNCTION_ON, down, oldMetaState);
+ case AKEYCODE_CTRL_LEFT:
+ return setEphemeralMetaState(AMETA_CTRL_LEFT_ON, down, oldMetaState);
+ case AKEYCODE_CTRL_RIGHT:
+ return setEphemeralMetaState(AMETA_CTRL_RIGHT_ON, down, oldMetaState);
+ case AKEYCODE_META_LEFT:
+ return setEphemeralMetaState(AMETA_META_LEFT_ON, down, oldMetaState);
+ case AKEYCODE_META_RIGHT:
+ return setEphemeralMetaState(AMETA_META_RIGHT_ON, down, oldMetaState);
+ case AKEYCODE_CAPS_LOCK:
+ return toggleLockedMetaState(AMETA_CAPS_LOCK_ON, down, oldMetaState);
+ case AKEYCODE_NUM_LOCK:
+ return toggleLockedMetaState(AMETA_NUM_LOCK_ON, down, oldMetaState);
+ case AKEYCODE_SCROLL_LOCK:
+ return toggleLockedMetaState(AMETA_SCROLL_LOCK_ON, down, oldMetaState);
+ default:
+ return oldMetaState;
+ }
+}
+
+bool isMetaKey(int32_t keyCode) {
+ switch (keyCode) {
+ case AKEYCODE_ALT_LEFT:
+ case AKEYCODE_ALT_RIGHT:
+ case AKEYCODE_SHIFT_LEFT:
+ case AKEYCODE_SHIFT_RIGHT:
+ case AKEYCODE_SYM:
+ case AKEYCODE_FUNCTION:
+ case AKEYCODE_CTRL_LEFT:
+ case AKEYCODE_CTRL_RIGHT:
+ case AKEYCODE_META_LEFT:
+ case AKEYCODE_META_RIGHT:
+ case AKEYCODE_CAPS_LOCK:
+ case AKEYCODE_NUM_LOCK:
+ case AKEYCODE_SCROLL_LOCK:
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+} // namespace android
diff --git a/widget/gonk/libui/Keyboard.h b/widget/gonk/libui/Keyboard.h
new file mode 100644
index 000000000..65921b25b
--- /dev/null
+++ b/widget/gonk/libui/Keyboard.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_KEYBOARD_H
+#define _ANDROIDFW_KEYBOARD_H
+
+#include "Input.h"
+#include "InputDevice.h"
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/PropertyMap.h>
+
+namespace android {
+
+enum {
+ /* Device id of the built in keyboard. */
+ DEVICE_ID_BUILT_IN_KEYBOARD = 0,
+
+ /* Device id of a generic virtual keyboard with a full layout that can be used
+ * to synthesize key events. */
+ DEVICE_ID_VIRTUAL_KEYBOARD = -1,
+};
+
+class KeyLayoutMap;
+class KeyCharacterMap;
+
+/**
+ * Loads the key layout map and key character map for a keyboard device.
+ */
+class KeyMap {
+public:
+ String8 keyLayoutFile;
+ sp<KeyLayoutMap> keyLayoutMap;
+
+ String8 keyCharacterMapFile;
+ sp<KeyCharacterMap> keyCharacterMap;
+
+ KeyMap();
+ ~KeyMap();
+
+ status_t load(const InputDeviceIdentifier& deviceIdenfier,
+ const PropertyMap* deviceConfiguration);
+
+ inline bool haveKeyLayout() const {
+ return !keyLayoutFile.isEmpty();
+ }
+
+ inline bool haveKeyCharacterMap() const {
+ return !keyCharacterMapFile.isEmpty();
+ }
+
+ inline bool isComplete() const {
+ return haveKeyLayout() && haveKeyCharacterMap();
+ }
+
+private:
+ bool probeKeyMap(const InputDeviceIdentifier& deviceIdentifier, const String8& name);
+ status_t loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier, const String8& name);
+ status_t loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name);
+ String8 getPath(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name, InputDeviceConfigurationFileType type);
+};
+
+/**
+ * Returns true if the keyboard is eligible for use as a built-in keyboard.
+ */
+extern bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
+ const PropertyMap* deviceConfiguration, const KeyMap* keyMap);
+
+/**
+ * Gets a key code by its short form label, eg. "HOME".
+ * Returns 0 if unknown.
+ */
+extern int32_t getKeyCodeByLabel(const char* label);
+
+/**
+ * Gets a key flag by its short form label, eg. "WAKE".
+ * Returns 0 if unknown.
+ */
+extern uint32_t getKeyFlagByLabel(const char* label);
+
+/**
+ * Gets a axis by its short form label, eg. "X".
+ * Returns -1 if unknown.
+ */
+extern int32_t getAxisByLabel(const char* label);
+
+/**
+ * Gets a axis label by its id.
+ * Returns NULL if unknown.
+ */
+extern const char* getAxisLabel(int32_t axisId);
+
+extern int32_t getLedByLabel(const char* label);
+
+/**
+ * Updates a meta state field when a key is pressed or released.
+ */
+extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState);
+
+/**
+ * Returns true if a key is a meta key like ALT or CAPS_LOCK.
+ */
+extern bool isMetaKey(int32_t keyCode);
+
+} // namespace android
+
+#endif // _ANDROIDFW_KEYBOARD_H
diff --git a/widget/gonk/libui/KeycodeLabels.h b/widget/gonk/libui/KeycodeLabels.h
new file mode 100644
index 000000000..9f994597b
--- /dev/null
+++ b/widget/gonk/libui/KeycodeLabels.h
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_KEYCODE_LABELS_H
+#define _ANDROIDFW_KEYCODE_LABELS_H
+
+#include "android_keycodes.h"
+
+struct KeycodeLabel {
+ const char *literal;
+ int value;
+};
+
+static const KeycodeLabel KEYCODES[] = {
+ { "SOFT_LEFT", 1 },
+ { "SOFT_RIGHT", 2 },
+ { "HOME", 3 },
+ { "BACK", 4 },
+ { "CALL", 5 },
+ { "ENDCALL", 6 },
+ { "0", 7 },
+ { "1", 8 },
+ { "2", 9 },
+ { "3", 10 },
+ { "4", 11 },
+ { "5", 12 },
+ { "6", 13 },
+ { "7", 14 },
+ { "8", 15 },
+ { "9", 16 },
+ { "STAR", 17 },
+ { "POUND", 18 },
+ { "DPAD_UP", 19 },
+ { "DPAD_DOWN", 20 },
+ { "DPAD_LEFT", 21 },
+ { "DPAD_RIGHT", 22 },
+ { "DPAD_CENTER", 23 },
+ { "VOLUME_UP", 24 },
+ { "VOLUME_DOWN", 25 },
+ { "POWER", 26 },
+ { "CAMERA", 27 },
+ { "CLEAR", 28 },
+ { "A", 29 },
+ { "B", 30 },
+ { "C", 31 },
+ { "D", 32 },
+ { "E", 33 },
+ { "F", 34 },
+ { "G", 35 },
+ { "H", 36 },
+ { "I", 37 },
+ { "J", 38 },
+ { "K", 39 },
+ { "L", 40 },
+ { "M", 41 },
+ { "N", 42 },
+ { "O", 43 },
+ { "P", 44 },
+ { "Q", 45 },
+ { "R", 46 },
+ { "S", 47 },
+ { "T", 48 },
+ { "U", 49 },
+ { "V", 50 },
+ { "W", 51 },
+ { "X", 52 },
+ { "Y", 53 },
+ { "Z", 54 },
+ { "COMMA", 55 },
+ { "PERIOD", 56 },
+ { "ALT_LEFT", 57 },
+ { "ALT_RIGHT", 58 },
+ { "SHIFT_LEFT", 59 },
+ { "SHIFT_RIGHT", 60 },
+ { "TAB", 61 },
+ { "SPACE", 62 },
+ { "SYM", 63 },
+ { "EXPLORER", 64 },
+ { "ENVELOPE", 65 },
+ { "ENTER", 66 },
+ { "DEL", 67 },
+ { "GRAVE", 68 },
+ { "MINUS", 69 },
+ { "EQUALS", 70 },
+ { "LEFT_BRACKET", 71 },
+ { "RIGHT_BRACKET", 72 },
+ { "BACKSLASH", 73 },
+ { "SEMICOLON", 74 },
+ { "APOSTROPHE", 75 },
+ { "SLASH", 76 },
+ { "AT", 77 },
+ { "NUM", 78 },
+ { "HEADSETHOOK", 79 },
+ { "FOCUS", 80 },
+ { "PLUS", 81 },
+ { "MENU", 82 },
+ { "NOTIFICATION", 83 },
+ { "SEARCH", 84 },
+ { "MEDIA_PLAY_PAUSE", 85 },
+ { "MEDIA_STOP", 86 },
+ { "MEDIA_NEXT", 87 },
+ { "MEDIA_PREVIOUS", 88 },
+ { "MEDIA_REWIND", 89 },
+ { "MEDIA_FAST_FORWARD", 90 },
+ { "MUTE", 91 },
+ { "PAGE_UP", 92 },
+ { "PAGE_DOWN", 93 },
+ { "PICTSYMBOLS", 94 },
+ { "SWITCH_CHARSET", 95 },
+ { "BUTTON_A", 96 },
+ { "BUTTON_B", 97 },
+ { "BUTTON_C", 98 },
+ { "BUTTON_X", 99 },
+ { "BUTTON_Y", 100 },
+ { "BUTTON_Z", 101 },
+ { "BUTTON_L1", 102 },
+ { "BUTTON_R1", 103 },
+ { "BUTTON_L2", 104 },
+ { "BUTTON_R2", 105 },
+ { "BUTTON_THUMBL", 106 },
+ { "BUTTON_THUMBR", 107 },
+ { "BUTTON_START", 108 },
+ { "BUTTON_SELECT", 109 },
+ { "BUTTON_MODE", 110 },
+ { "ESCAPE", 111 },
+ { "FORWARD_DEL", 112 },
+ { "CTRL_LEFT", 113 },
+ { "CTRL_RIGHT", 114 },
+ { "CAPS_LOCK", 115 },
+ { "SCROLL_LOCK", 116 },
+ { "META_LEFT", 117 },
+ { "META_RIGHT", 118 },
+ { "FUNCTION", 119 },
+ { "SYSRQ", 120 },
+ { "BREAK", 121 },
+ { "MOVE_HOME", 122 },
+ { "MOVE_END", 123 },
+ { "INSERT", 124 },
+ { "FORWARD", 125 },
+ { "MEDIA_PLAY", 126 },
+ { "MEDIA_PAUSE", 127 },
+ { "MEDIA_CLOSE", 128 },
+ { "MEDIA_EJECT", 129 },
+ { "MEDIA_RECORD", 130 },
+ { "F1", 131 },
+ { "F2", 132 },
+ { "F3", 133 },
+ { "F4", 134 },
+ { "F5", 135 },
+ { "F6", 136 },
+ { "F7", 137 },
+ { "F8", 138 },
+ { "F9", 139 },
+ { "F10", 140 },
+ { "F11", 141 },
+ { "F12", 142 },
+ { "NUM_LOCK", 143 },
+ { "NUMPAD_0", 144 },
+ { "NUMPAD_1", 145 },
+ { "NUMPAD_2", 146 },
+ { "NUMPAD_3", 147 },
+ { "NUMPAD_4", 148 },
+ { "NUMPAD_5", 149 },
+ { "NUMPAD_6", 150 },
+ { "NUMPAD_7", 151 },
+ { "NUMPAD_8", 152 },
+ { "NUMPAD_9", 153 },
+ { "NUMPAD_DIVIDE", 154 },
+ { "NUMPAD_MULTIPLY", 155 },
+ { "NUMPAD_SUBTRACT", 156 },
+ { "NUMPAD_ADD", 157 },
+ { "NUMPAD_DOT", 158 },
+ { "NUMPAD_COMMA", 159 },
+ { "NUMPAD_ENTER", 160 },
+ { "NUMPAD_EQUALS", 161 },
+ { "NUMPAD_LEFT_PAREN", 162 },
+ { "NUMPAD_RIGHT_PAREN", 163 },
+ { "VOLUME_MUTE", 164 },
+ { "INFO", 165 },
+ { "CHANNEL_UP", 166 },
+ { "CHANNEL_DOWN", 167 },
+ { "ZOOM_IN", 168 },
+ { "ZOOM_OUT", 169 },
+ { "TV", 170 },
+ { "WINDOW", 171 },
+ { "GUIDE", 172 },
+ { "DVR", 173 },
+ { "BOOKMARK", 174 },
+ { "CAPTIONS", 175 },
+ { "SETTINGS", 176 },
+ { "TV_POWER", 177 },
+ { "TV_INPUT", 178 },
+ { "STB_POWER", 179 },
+ { "STB_INPUT", 180 },
+ { "AVR_POWER", 181 },
+ { "AVR_INPUT", 182 },
+ { "PROG_RED", 183 },
+ { "PROG_GREEN", 184 },
+ { "PROG_YELLOW", 185 },
+ { "PROG_BLUE", 186 },
+ { "APP_SWITCH", 187 },
+ { "BUTTON_1", 188 },
+ { "BUTTON_2", 189 },
+ { "BUTTON_3", 190 },
+ { "BUTTON_4", 191 },
+ { "BUTTON_5", 192 },
+ { "BUTTON_6", 193 },
+ { "BUTTON_7", 194 },
+ { "BUTTON_8", 195 },
+ { "BUTTON_9", 196 },
+ { "BUTTON_10", 197 },
+ { "BUTTON_11", 198 },
+ { "BUTTON_12", 199 },
+ { "BUTTON_13", 200 },
+ { "BUTTON_14", 201 },
+ { "BUTTON_15", 202 },
+ { "BUTTON_16", 203 },
+ { "LANGUAGE_SWITCH", 204 },
+ { "MANNER_MODE", 205 },
+ { "3D_MODE", 206 },
+ { "CONTACTS", 207 },
+ { "CALENDAR", 208 },
+ { "MUSIC", 209 },
+ { "CALCULATOR", 210 },
+ { "ZENKAKU_HANKAKU", 211 },
+ { "EISU", 212 },
+ { "MUHENKAN", 213 },
+ { "HENKAN", 214 },
+ { "KATAKANA_HIRAGANA", 215 },
+ { "YEN", 216 },
+ { "RO", 217 },
+ { "KANA", 218 },
+ { "ASSIST", 219 },
+ { "BRIGHTNESS_DOWN", 220 },
+ { "BRIGHTNESS_UP", 221 },
+ { "MEDIA_AUDIO_TRACK", 222 },
+ { "SLEEP", 223 },
+ { "WAKEUP", 224 },
+ { "PAIRING", 225 },
+ { "MEDIA_TOP_MENU", 226 },
+ { "11", 227 },
+ { "12", 228 },
+ { "LAST_CHANNEL", 229 },
+ { "TV_DATA_SERVICE", 230 },
+ { "VOICE_ASSIST", 231 },
+ { "TV_RADIO_SERVICE", 232 },
+ { "TV_TELETEXT", 233 },
+ { "TV_NUMBER_ENTRY", 234 },
+ { "TV_TERRESTRIAL_ANALOG", 235 },
+ { "TV_TERRESTRIAL_DIGITAL", 236 },
+ { "TV_SATELLITE", 237 },
+ { "TV_SATELLITE_BS", 238 },
+ { "TV_SATELLITE_CS", 239 },
+ { "TV_SATELLITE_SERVICE", 240 },
+ { "TV_NETWORK", 241 },
+ { "TV_ANTENNA_CABLE", 242 },
+ { "TV_INPUT_HDMI_1", 243 },
+ { "TV_INPUT_HDMI_2", 244 },
+ { "TV_INPUT_HDMI_3", 245 },
+ { "TV_INPUT_HDMI_4", 246 },
+ { "TV_INPUT_COMPOSITE_1", 247 },
+ { "TV_INPUT_COMPOSITE_2", 248 },
+ { "TV_INPUT_COMPONENT_1", 249 },
+ { "TV_INPUT_COMPONENT_2", 250 },
+ { "TV_INPUT_VGA_1", 251 },
+ { "TV_AUDIO_DESCRIPTION", 252 },
+ { "TV_AUDIO_DESCRIPTION_MIX_UP", 253 },
+ { "TV_AUDIO_DESCRIPTION_MIX_DOWN", 254 },
+ { "TV_ZOOM_MODE", 255 },
+ { "TV_CONTENTS_MENU", 256 },
+ { "TV_MEDIA_CONTEXT_MENU", 257 },
+ { "TV_TIMER_PROGRAMMING", 258 },
+ { "HELP", 259 },
+
+ // NOTE: If you add a new keycode here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
+
+ { NULL, 0 }
+};
+
+// NOTE: If you edit these flags, also edit policy flags in Input.h.
+static const KeycodeLabel FLAGS[] = {
+ { "WAKE", 0x00000001 },
+ { "WAKE_DROPPED", 0x00000002 },
+ { "SHIFT", 0x00000004 },
+ { "CAPS_LOCK", 0x00000008 },
+ { "ALT", 0x00000010 },
+ { "ALT_GR", 0x00000020 },
+ { "MENU", 0x00000040 },
+ { "LAUNCHER", 0x00000080 },
+ { "VIRTUAL", 0x00000100 },
+ { "FUNCTION", 0x00000200 },
+ { NULL, 0 }
+};
+
+static const KeycodeLabel AXES[] = {
+ { "X", 0 },
+ { "Y", 1 },
+ { "PRESSURE", 2 },
+ { "SIZE", 3 },
+ { "TOUCH_MAJOR", 4 },
+ { "TOUCH_MINOR", 5 },
+ { "TOOL_MAJOR", 6 },
+ { "TOOL_MINOR", 7 },
+ { "ORIENTATION", 8 },
+ { "VSCROLL", 9 },
+ { "HSCROLL", 10 },
+ { "Z", 11 },
+ { "RX", 12 },
+ { "RY", 13 },
+ { "RZ", 14 },
+ { "HAT_X", 15 },
+ { "HAT_Y", 16 },
+ { "LTRIGGER", 17 },
+ { "RTRIGGER", 18 },
+ { "THROTTLE", 19 },
+ { "RUDDER", 20 },
+ { "WHEEL", 21 },
+ { "GAS", 22 },
+ { "BRAKE", 23 },
+ { "DISTANCE", 24 },
+ { "TILT", 25 },
+ { "GENERIC_1", 32 },
+ { "GENERIC_2", 33 },
+ { "GENERIC_3", 34 },
+ { "GENERIC_4", 35 },
+ { "GENERIC_5", 36 },
+ { "GENERIC_6", 37 },
+ { "GENERIC_7", 38 },
+ { "GENERIC_8", 39 },
+ { "GENERIC_9", 40 },
+ { "GENERIC_10", 41 },
+ { "GENERIC_11", 42 },
+ { "GENERIC_12", 43 },
+ { "GENERIC_13", 44 },
+ { "GENERIC_14", 45 },
+ { "GENERIC_15", 46 },
+ { "GENERIC_16", 47 },
+
+ // NOTE: If you add a new axis here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
+
+ { NULL, -1 }
+};
+
+static const KeycodeLabel LEDS[] = {
+ { "NUM_LOCK", 1},
+ { "CAPS_LOCK", 2},
+ { "SCROLL_LOCK", 3},
+ { "COMPOSE", 4},
+ { "KANA", 5},
+ { "SLEEP", 6},
+ { "SUSPEND", 7},
+ { "MUTE", 8},
+ { "MISC", 9},
+ { "MAIL", 10},
+ { "CHARGING", 11},
+ { "CONTROLLER_1", 12},
+ { "CONTROLLER_2", 13},
+ { "CONTROLLER_3", 14},
+ { "CONTROLLER_4", 15},
+
+ // NOTE: If you add new LEDs here, you must also add them to Input.h
+ { NULL, 0 }
+};
+
+#endif // _ANDROIDFW_KEYCODE_LABELS_H
diff --git a/widget/gonk/libui/PointerController.cpp b/widget/gonk/libui/PointerController.cpp
new file mode 100644
index 000000000..ff80a0a9f
--- /dev/null
+++ b/widget/gonk/libui/PointerController.cpp
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "PointerController"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about pointer updates
+#define DEBUG_POINTER_UPDATES 0
+
+#include "PointerController.h"
+
+#include "cutils_log.h"
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+
+namespace android {
+
+// --- PointerController ---
+
+// Time to wait before starting the fade when the pointer is inactive.
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+
+// Time to wait between animation frames.
+static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
+
+// Time to spend fading out the spot completely.
+static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
+
+// Time to spend fading out the pointer completely.
+static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
+
+
+// --- PointerController ---
+
+PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController) :
+ mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
+ mHandler = new WeakMessageHandler(this);
+
+ AutoMutex _l(mLock);
+
+ mLocked.animationPending = false;
+
+ mLocked.displayWidth = -1;
+ mLocked.displayHeight = -1;
+ mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
+
+ mLocked.presentation = PRESENTATION_POINTER;
+ mLocked.presentationChanged = false;
+
+ mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL;
+
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ mLocked.pointerAlpha = 0.0f; // pointer is initially faded
+ mLocked.pointerSprite = mSpriteController->createSprite();
+ mLocked.pointerIconChanged = false;
+
+ mLocked.buttonState = 0;
+
+ loadResources();
+}
+
+PointerController::~PointerController() {
+ mLooper->removeMessages(mHandler);
+
+ AutoMutex _l(mLock);
+
+ mLocked.pointerSprite.clear();
+
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ delete mLocked.spots.itemAt(i);
+ }
+ mLocked.spots.clear();
+ mLocked.recycledSprites.clear();
+}
+
+bool PointerController::getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const {
+ AutoMutex _l(mLock);
+
+ return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+}
+
+bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const {
+ if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
+ return false;
+ }
+
+ *outMinX = 0;
+ *outMinY = 0;
+ switch (mLocked.displayOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ case DISPLAY_ORIENTATION_270:
+ *outMaxX = mLocked.displayHeight - 1;
+ *outMaxY = mLocked.displayWidth - 1;
+ break;
+ default:
+ *outMaxX = mLocked.displayWidth - 1;
+ *outMaxY = mLocked.displayHeight - 1;
+ break;
+ }
+ return true;
+}
+
+void PointerController::move(float deltaX, float deltaY) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
+#endif
+ if (deltaX == 0.0f && deltaY == 0.0f) {
+ return;
+ }
+
+ AutoMutex _l(mLock);
+
+ setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+}
+
+void PointerController::setButtonState(int32_t buttonState) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("Set button state 0x%08x", buttonState);
+#endif
+ AutoMutex _l(mLock);
+
+ if (mLocked.buttonState != buttonState) {
+ mLocked.buttonState = buttonState;
+ }
+}
+
+int32_t PointerController::getButtonState() const {
+ AutoMutex _l(mLock);
+
+ return mLocked.buttonState;
+}
+
+void PointerController::setPosition(float x, float y) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
+#endif
+ AutoMutex _l(mLock);
+
+ setPositionLocked(x, y);
+}
+
+void PointerController::setPositionLocked(float x, float y) {
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ if (x <= minX) {
+ mLocked.pointerX = minX;
+ } else if (x >= maxX) {
+ mLocked.pointerX = maxX;
+ } else {
+ mLocked.pointerX = x;
+ }
+ if (y <= minY) {
+ mLocked.pointerY = minY;
+ } else if (y >= maxY) {
+ mLocked.pointerY = maxY;
+ } else {
+ mLocked.pointerY = y;
+ }
+ updatePointerLocked();
+ }
+}
+
+void PointerController::getPosition(float* outX, float* outY) const {
+ AutoMutex _l(mLock);
+
+ *outX = mLocked.pointerX;
+ *outY = mLocked.pointerY;
+}
+
+void PointerController::fade(Transition transition) {
+ AutoMutex _l(mLock);
+
+ // Remove the inactivity timeout, since we are fading now.
+ removeInactivityTimeoutLocked();
+
+ // Start fading.
+ if (transition == TRANSITION_IMMEDIATE) {
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerAlpha = 0.0f;
+ updatePointerLocked();
+ } else {
+ mLocked.pointerFadeDirection = -1;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::unfade(Transition transition) {
+ AutoMutex _l(mLock);
+
+ // Always reset the inactivity timer.
+ resetInactivityTimeoutLocked();
+
+ // Start unfading.
+ if (transition == TRANSITION_IMMEDIATE) {
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerAlpha = 1.0f;
+ updatePointerLocked();
+ } else {
+ mLocked.pointerFadeDirection = 1;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::setPresentation(Presentation presentation) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.presentation != presentation) {
+ mLocked.presentation = presentation;
+ mLocked.presentationChanged = true;
+
+ if (presentation != PRESENTATION_SPOT) {
+ fadeOutAndReleaseAllSpotsLocked();
+ }
+
+ updatePointerLocked();
+ }
+}
+
+void PointerController::setSpots(const PointerCoords* spotCoords,
+ const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("setSpots: idBits=%08x", spotIdBits.value);
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id,
+ c.getAxisValue(AMOTION_EVENT_AXIS_X),
+ c.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+#endif
+
+ AutoMutex _l(mLock);
+
+ mSpriteController->openTransaction();
+
+ // Add or move spots for fingers that are down.
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
+ ? mResources.spotTouch : mResources.spotHover;
+ float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+ float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+ Spot* spot = getSpotLocked(id);
+ if (!spot) {
+ spot = createAndAddSpotLocked(id);
+ }
+
+ spot->updateSprite(&icon, x, y);
+ }
+
+ // Remove spots for fingers that went up.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id != Spot::INVALID_ID
+ && !spotIdBits.hasBit(spot->id)) {
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+ }
+
+ mSpriteController->closeTransaction();
+}
+
+void PointerController::clearSpots() {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("clearSpots");
+#endif
+
+ AutoMutex _l(mLock);
+
+ fadeOutAndReleaseAllSpotsLocked();
+}
+
+void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.inactivityTimeout != inactivityTimeout) {
+ mLocked.inactivityTimeout = inactivityTimeout;
+ resetInactivityTimeoutLocked();
+ }
+}
+
+void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
+ AutoMutex _l(mLock);
+
+ // Adjust to use the display's unrotated coordinate frame.
+ if (orientation == DISPLAY_ORIENTATION_90
+ || orientation == DISPLAY_ORIENTATION_270) {
+ int32_t temp = height;
+ height = width;
+ width = temp;
+ }
+
+ if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+ mLocked.displayWidth = width;
+ mLocked.displayHeight = height;
+
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ mLocked.pointerX = (minX + maxX) * 0.5f;
+ mLocked.pointerY = (minY + maxY) * 0.5f;
+ } else {
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ }
+
+ fadeOutAndReleaseAllSpotsLocked();
+ }
+
+ if (mLocked.displayOrientation != orientation) {
+ // Apply offsets to convert from the pixel top-left corner position to the pixel center.
+ // This creates an invariant frame of reference that we can easily rotate when
+ // taking into account that the pointer may be located at fractional pixel offsets.
+ float x = mLocked.pointerX + 0.5f;
+ float y = mLocked.pointerY + 0.5f;
+ float temp;
+
+ // Undo the previous rotation.
+ switch (mLocked.displayOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = x;
+ x = mLocked.displayWidth - y;
+ y = temp;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = mLocked.displayWidth - x;
+ y = mLocked.displayHeight - y;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ temp = x;
+ x = y;
+ y = mLocked.displayHeight - temp;
+ break;
+ }
+
+ // Perform the new rotation.
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = x;
+ x = y;
+ y = mLocked.displayWidth - temp;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = mLocked.displayWidth - x;
+ y = mLocked.displayHeight - y;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ temp = x;
+ x = mLocked.displayHeight - y;
+ y = temp;
+ break;
+ }
+
+ // Apply offsets to convert from the pixel center to the pixel top-left corner position
+ // and save the results.
+ mLocked.pointerX = x - 0.5f;
+ mLocked.pointerY = y - 0.5f;
+ mLocked.displayOrientation = orientation;
+ }
+
+ updatePointerLocked();
+}
+
+void PointerController::setPointerIcon(const SpriteIcon& icon) {
+ AutoMutex _l(mLock);
+
+ mLocked.pointerIcon = icon.copy();
+ mLocked.pointerIconChanged = true;
+
+ updatePointerLocked();
+}
+
+void PointerController::handleMessage(const Message& message) {
+ switch (message.what) {
+ case MSG_ANIMATE:
+ doAnimate();
+ break;
+ case MSG_INACTIVITY_TIMEOUT:
+ doInactivityTimeout();
+ break;
+ }
+}
+
+void PointerController::doAnimate() {
+ AutoMutex _l(mLock);
+
+ bool keepAnimating = false;
+ mLocked.animationPending = false;
+ nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
+
+ // Animate pointer fade.
+ if (mLocked.pointerFadeDirection < 0) {
+ mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha <= 0.0f) {
+ mLocked.pointerAlpha = 0.0f;
+ mLocked.pointerFadeDirection = 0;
+ } else {
+ keepAnimating = true;
+ }
+ updatePointerLocked();
+ } else if (mLocked.pointerFadeDirection > 0) {
+ mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha >= 1.0f) {
+ mLocked.pointerAlpha = 1.0f;
+ mLocked.pointerFadeDirection = 0;
+ } else {
+ keepAnimating = true;
+ }
+ updatePointerLocked();
+ }
+
+ // Animate spots that are fading out and being removed.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
+ if (spot->alpha <= 0) {
+ mLocked.spots.removeAt(i--);
+ releaseSpotLocked(spot);
+ } else {
+ spot->sprite->setAlpha(spot->alpha);
+ keepAnimating = true;
+ }
+ }
+ }
+
+ if (keepAnimating) {
+ startAnimationLocked();
+ }
+}
+
+void PointerController::doInactivityTimeout() {
+ fade(TRANSITION_GRADUAL);
+}
+
+void PointerController::startAnimationLocked() {
+ if (!mLocked.animationPending) {
+ mLocked.animationPending = true;
+ mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+ }
+}
+
+void PointerController::resetInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+
+ nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT
+ ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
+ mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::removeInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::updatePointerLocked() {
+ mSpriteController->openTransaction();
+
+ mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
+ mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+
+ if (mLocked.pointerAlpha > 0) {
+ mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
+ mLocked.pointerSprite->setVisible(true);
+ } else {
+ mLocked.pointerSprite->setVisible(false);
+ }
+
+ if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
+ mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER
+ ? mLocked.pointerIcon : mResources.spotAnchor);
+ mLocked.pointerIconChanged = false;
+ mLocked.presentationChanged = false;
+ }
+
+ mSpriteController->closeTransaction();
+}
+
+PointerController::Spot* PointerController::getSpotLocked(uint32_t id) {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == id) {
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) {
+ // Remove spots until we have fewer than MAX_SPOTS remaining.
+ while (mLocked.spots.size() >= MAX_SPOTS) {
+ Spot* spot = removeFirstFadingSpotLocked();
+ if (!spot) {
+ spot = mLocked.spots.itemAt(0);
+ mLocked.spots.removeAt(0);
+ }
+ releaseSpotLocked(spot);
+ }
+
+ // Obtain a sprite from the recycled pool.
+ sp<Sprite> sprite;
+ if (! mLocked.recycledSprites.isEmpty()) {
+ sprite = mLocked.recycledSprites.top();
+ mLocked.recycledSprites.pop();
+ } else {
+ sprite = mSpriteController->createSprite();
+ }
+
+ // Return the new spot.
+ Spot* spot = new Spot(id, sprite);
+ mLocked.spots.push(spot);
+ return spot;
+}
+
+PointerController::Spot* PointerController::removeFirstFadingSpotLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ mLocked.spots.removeAt(i);
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+void PointerController::releaseSpotLocked(Spot* spot) {
+ spot->sprite->clearIcon();
+
+ if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
+ mLocked.recycledSprites.push(spot->sprite);
+ }
+
+ delete spot;
+}
+
+void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
+ if (spot->id != Spot::INVALID_ID) {
+ spot->id = Spot::INVALID_ID;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::fadeOutAndReleaseAllSpotsLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+}
+
+void PointerController::loadResources() {
+ mPolicy->loadPointerResources(&mResources);
+}
+
+
+// --- PointerController::Spot ---
+
+void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) {
+ sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
+ sprite->setAlpha(alpha);
+ sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
+ sprite->setPosition(x, y);
+
+ this->x = x;
+ this->y = y;
+
+ if (icon != lastIcon) {
+ lastIcon = icon;
+ if (icon) {
+ sprite->setIcon(*icon);
+ sprite->setVisible(true);
+ } else {
+ sprite->setVisible(false);
+ }
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/PointerController.h b/widget/gonk/libui/PointerController.h
new file mode 100644
index 000000000..eb48d9a1f
--- /dev/null
+++ b/widget/gonk/libui/PointerController.h
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_POINTER_CONTROLLER_H
+#define _UI_POINTER_CONTROLLER_H
+
+#include "SpriteController.h"
+
+#include <ui/DisplayInfo.h>
+#include "Input.h"
+#include <utils/BitSet.h>
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+#include <utils/String8.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/**
+ * Interface for tracking a mouse / touch pad pointer and touch pad spots.
+ *
+ * The spots are sprites on screen that visually represent the positions of
+ * fingers
+ *
+ * The pointer controller is responsible for providing synchronization and for tracking
+ * display orientation changes if needed.
+ */
+class PointerControllerInterface : public virtual RefBase {
+protected:
+ PointerControllerInterface() { }
+ virtual ~PointerControllerInterface() { }
+
+public:
+ /* Gets the bounds of the region that the pointer can traverse.
+ * Returns true if the bounds are available. */
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const = 0;
+
+ /* Move the pointer. */
+ virtual void move(float deltaX, float deltaY) = 0;
+
+ /* Sets a mask that indicates which buttons are pressed. */
+ virtual void setButtonState(int32_t buttonState) = 0;
+
+ /* Gets a mask that indicates which buttons are pressed. */
+ virtual int32_t getButtonState() const = 0;
+
+ /* Sets the absolute location of the pointer. */
+ virtual void setPosition(float x, float y) = 0;
+
+ /* Gets the absolute location of the pointer. */
+ virtual void getPosition(float* outX, float* outY) const = 0;
+
+ enum Transition {
+ // Fade/unfade immediately.
+ TRANSITION_IMMEDIATE,
+ // Fade/unfade gradually.
+ TRANSITION_GRADUAL,
+ };
+
+ /* Fades the pointer out now. */
+ virtual void fade(Transition transition) = 0;
+
+ /* Makes the pointer visible if it has faded out.
+ * The pointer never unfades itself automatically. This method must be called
+ * by the client whenever the pointer is moved or a button is pressed and it
+ * wants to ensure that the pointer becomes visible again. */
+ virtual void unfade(Transition transition) = 0;
+
+ enum Presentation {
+ // Show the mouse pointer.
+ PRESENTATION_POINTER,
+ // Show spots and a spot anchor in place of the mouse pointer.
+ PRESENTATION_SPOT,
+ };
+
+ /* Sets the mode of the pointer controller. */
+ virtual void setPresentation(Presentation presentation) = 0;
+
+ /* Sets the spots for the current gesture.
+ * The spots are not subject to the inactivity timeout like the pointer
+ * itself it since they are expected to remain visible for so long as
+ * the fingers are on the touch pad.
+ *
+ * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant.
+ * For spotCoords, pressure != 0 indicates that the spot's location is being
+ * pressed (not hovering).
+ */
+ virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits) = 0;
+
+ /* Removes all spots. */
+ virtual void clearSpots() = 0;
+};
+
+
+/*
+ * Pointer resources.
+ */
+struct PointerResources {
+ SpriteIcon spotHover;
+ SpriteIcon spotTouch;
+ SpriteIcon spotAnchor;
+};
+
+
+/*
+ * Pointer controller policy interface.
+ *
+ * The pointer controller policy is used by the pointer controller to interact with
+ * the Window Manager and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class PointerControllerPolicyInterface : public virtual RefBase {
+protected:
+ PointerControllerPolicyInterface() { }
+ virtual ~PointerControllerPolicyInterface() { }
+
+public:
+ virtual void loadPointerResources(PointerResources* outResources) = 0;
+};
+
+
+/*
+ * Tracks pointer movements and draws the pointer sprite to a surface.
+ *
+ * Handles pointer acceleration and animation.
+ */
+class PointerController : public PointerControllerInterface, public MessageHandler {
+protected:
+ virtual ~PointerController();
+
+public:
+ enum InactivityTimeout {
+ INACTIVITY_TIMEOUT_NORMAL = 0,
+ INACTIVITY_TIMEOUT_SHORT = 1,
+ };
+
+ PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController);
+
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const;
+ virtual void move(float deltaX, float deltaY);
+ virtual void setButtonState(int32_t buttonState);
+ virtual int32_t getButtonState() const;
+ virtual void setPosition(float x, float y);
+ virtual void getPosition(float* outX, float* outY) const;
+ virtual void fade(Transition transition);
+ virtual void unfade(Transition transition);
+
+ virtual void setPresentation(Presentation presentation);
+ virtual void setSpots(const PointerCoords* spotCoords,
+ const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
+ virtual void clearSpots();
+
+ void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
+ void setPointerIcon(const SpriteIcon& icon);
+ void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+
+private:
+ static const size_t MAX_RECYCLED_SPRITES = 12;
+ static const size_t MAX_SPOTS = 12;
+
+ enum {
+ MSG_ANIMATE,
+ MSG_INACTIVITY_TIMEOUT,
+ };
+
+ struct Spot {
+ static const uint32_t INVALID_ID = 0xffffffff;
+
+ uint32_t id;
+ sp<Sprite> sprite;
+ float alpha;
+ float scale;
+ float x, y;
+
+ inline Spot(uint32_t id, const sp<Sprite>& sprite)
+ : id(id), sprite(sprite), alpha(1.0f), scale(1.0f),
+ x(0.0f), y(0.0f), lastIcon(NULL) { }
+
+ void updateSprite(const SpriteIcon* icon, float x, float y);
+
+ private:
+ const SpriteIcon* lastIcon;
+ };
+
+ mutable Mutex mLock;
+
+ sp<PointerControllerPolicyInterface> mPolicy;
+ sp<Looper> mLooper;
+ sp<SpriteController> mSpriteController;
+ sp<WeakMessageHandler> mHandler;
+
+ PointerResources mResources;
+
+ struct Locked {
+ bool animationPending;
+ nsecs_t animationTime;
+
+ int32_t displayWidth;
+ int32_t displayHeight;
+ int32_t displayOrientation;
+
+ InactivityTimeout inactivityTimeout;
+
+ Presentation presentation;
+ bool presentationChanged;
+
+ int32_t pointerFadeDirection;
+ float pointerX;
+ float pointerY;
+ float pointerAlpha;
+ sp<Sprite> pointerSprite;
+ SpriteIcon pointerIcon;
+ bool pointerIconChanged;
+
+ int32_t buttonState;
+
+ Vector<Spot*> spots;
+ Vector<sp<Sprite> > recycledSprites;
+ } mLocked;
+
+ bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ void setPositionLocked(float x, float y);
+
+ void handleMessage(const Message& message);
+ void doAnimate();
+ void doInactivityTimeout();
+
+ void startAnimationLocked();
+
+ void resetInactivityTimeoutLocked();
+ void removeInactivityTimeoutLocked();
+ void updatePointerLocked();
+
+ Spot* getSpotLocked(uint32_t id);
+ Spot* createAndAddSpotLocked(uint32_t id);
+ Spot* removeFirstFadingSpotLocked();
+ void releaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseAllSpotsLocked();
+
+ void loadResources();
+};
+
+} // namespace android
+
+#endif // _UI_POINTER_CONTROLLER_H
diff --git a/widget/gonk/libui/PowerManager.h b/widget/gonk/libui/PowerManager.h
new file mode 100644
index 000000000..ba98db07c
--- /dev/null
+++ b/widget/gonk/libui/PowerManager.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_POWER_MANAGER_H
+#define _ANDROIDFW_POWER_MANAGER_H
+
+
+namespace android {
+
+enum {
+ USER_ACTIVITY_EVENT_OTHER = 0,
+ USER_ACTIVITY_EVENT_BUTTON = 1,
+ USER_ACTIVITY_EVENT_TOUCH = 2,
+
+ USER_ACTIVITY_EVENT_LAST = USER_ACTIVITY_EVENT_TOUCH, // Last valid event code.
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_POWER_MANAGER_H
diff --git a/widget/gonk/libui/SpriteController.cpp b/widget/gonk/libui/SpriteController.cpp
new file mode 100644
index 000000000..8677476b1
--- /dev/null
+++ b/widget/gonk/libui/SpriteController.cpp
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Sprites"
+
+//#define LOG_NDEBUG 0
+
+#include "SpriteController.h"
+
+#include "cutils_log.h"
+#include <utils/String8.h>
+#ifdef HAVE_ANDROID_OS
+#include <gui/Surface.h>
+#endif
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+#include <android/native_window.h>
+
+namespace android {
+
+// --- SpriteController ---
+
+SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
+ mLooper(looper), mOverlayLayer(overlayLayer) {
+#ifdef HAVE_ANDROID_OS
+ mHandler = new WeakMessageHandler(this);
+#endif
+
+ mLocked.transactionNestingCount = 0;
+ mLocked.deferredSpriteUpdate = false;
+}
+
+SpriteController::~SpriteController() {
+#ifdef HAVE_ANDROID_OS
+ mLooper->removeMessages(mHandler);
+
+ if (mSurfaceComposerClient != NULL) {
+ mSurfaceComposerClient->dispose();
+ mSurfaceComposerClient.clear();
+ }
+#endif
+}
+
+sp<Sprite> SpriteController::createSprite() {
+ return new SpriteImpl(this);
+}
+
+void SpriteController::openTransaction() {
+ AutoMutex _l(mLock);
+
+ mLocked.transactionNestingCount += 1;
+}
+
+void SpriteController::closeTransaction() {
+ AutoMutex _l(mLock);
+
+ LOG_ALWAYS_FATAL_IF(mLocked.transactionNestingCount == 0,
+ "Sprite closeTransaction() called but there is no open sprite transaction");
+
+ mLocked.transactionNestingCount -= 1;
+ if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) {
+ mLocked.deferredSpriteUpdate = false;
+#ifdef HAVE_ANDROID_OS
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+#endif
+ }
+}
+
+void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
+ bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
+ mLocked.invalidatedSprites.push(sprite);
+ if (wasEmpty) {
+ if (mLocked.transactionNestingCount != 0) {
+ mLocked.deferredSpriteUpdate = true;
+ } else {
+#ifdef HAVE_ANDROID_OS
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+#endif
+ }
+ }
+}
+
+#ifdef HAVE_ANDROID_OS
+void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
+ bool wasEmpty = mLocked.disposedSurfaces.isEmpty();
+ mLocked.disposedSurfaces.push(surfaceControl);
+ if (wasEmpty) {
+ mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
+ }
+}
+
+void SpriteController::handleMessage(const Message& message) {
+ switch (message.what) {
+ case MSG_UPDATE_SPRITES:
+ doUpdateSprites();
+ break;
+ case MSG_DISPOSE_SURFACES:
+ doDisposeSurfaces();
+ break;
+ }
+}
+#endif
+
+void SpriteController::doUpdateSprites() {
+ // Collect information about sprite updates.
+ // Each sprite update record includes a reference to its associated sprite so we can
+ // be certain the sprites will not be deleted while this function runs. Sprites
+ // may invalidate themselves again during this time but we will handle those changes
+ // in the next iteration.
+ Vector<SpriteUpdate> updates;
+ size_t numSprites;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ numSprites = mLocked.invalidatedSprites.size();
+ for (size_t i = 0; i < numSprites; i++) {
+ const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
+
+ updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
+ sprite->resetDirtyLocked();
+ }
+ mLocked.invalidatedSprites.clear();
+ } // release lock
+
+ // Create missing surfaces.
+ bool surfaceChanged = false;
+#ifdef HAVE_ANDROID_OS
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+ if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
+ update.state.surfaceWidth = update.state.icon.bitmap.width();
+ update.state.surfaceHeight = update.state.icon.bitmap.height();
+ update.state.surfaceDrawn = false;
+ update.state.surfaceVisible = false;
+ update.state.surfaceControl = obtainSurface(
+ update.state.surfaceWidth, update.state.surfaceHeight);
+ if (update.state.surfaceControl != NULL) {
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+#endif
+
+ // Resize sprites if needed, inside a global transaction.
+#ifdef HAVE_ANDROID_OS
+ bool haveGlobalTransaction = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+ if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
+ int32_t desiredWidth = update.state.icon.bitmap.width();
+ int32_t desiredHeight = update.state.icon.bitmap.height();
+ if (update.state.surfaceWidth < desiredWidth
+ || update.state.surfaceHeight < desiredHeight) {
+ if (!haveGlobalTransaction) {
+ SurfaceComposerClient::openGlobalTransaction();
+ haveGlobalTransaction = true;
+ }
+
+ status_t status = update.state.surfaceControl->setSize(desiredWidth, desiredHeight);
+ if (status) {
+ ALOGE("Error %d resizing sprite surface from %dx%d to %dx%d",
+ status, update.state.surfaceWidth, update.state.surfaceHeight,
+ desiredWidth, desiredHeight);
+ } else {
+ update.state.surfaceWidth = desiredWidth;
+ update.state.surfaceHeight = desiredHeight;
+ update.state.surfaceDrawn = false;
+ update.surfaceChanged = surfaceChanged = true;
+
+ if (update.state.surfaceVisible) {
+ status = update.state.surfaceControl->hide();
+ if (status) {
+ ALOGE("Error %d hiding sprite surface after resize.", status);
+ } else {
+ update.state.surfaceVisible = false;
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+#ifdef HAVE_ANDROID_OS
+ if (haveGlobalTransaction) {
+ SurfaceComposerClient::closeGlobalTransaction();
+ }
+#endif
+
+ // Redraw sprites if needed.
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) {
+ update.state.surfaceDrawn = false;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+
+#ifdef HAVE_ANDROID_OS
+ if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn
+ && update.state.wantSurfaceVisible()) {
+ sp<Surface> surface = update.state.surfaceControl->getSurface();
+ ANativeWindow_Buffer outBuffer;
+ status_t status = surface->lock(&outBuffer, NULL);
+ if (status) {
+ ALOGE("Error %d locking sprite surface before drawing.", status);
+ } else {
+ SkBitmap surfaceBitmap;
+ ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
+ surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ outBuffer.width, outBuffer.height, bpr);
+ surfaceBitmap.setPixels(outBuffer.bits);
+
+ SkCanvas surfaceCanvas(surfaceBitmap);
+
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
+
+ if (outBuffer.width > uint32_t(update.state.icon.bitmap.width())) {
+ paint.setColor(0); // transparent fill color
+ surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
+ outBuffer.width, update.state.icon.bitmap.height(), paint);
+ }
+ if (outBuffer.height > uint32_t(update.state.icon.bitmap.height())) {
+ paint.setColor(0); // transparent fill color
+ surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
+ outBuffer.width, outBuffer.height, paint);
+ }
+
+ status = surface->unlockAndPost();
+ if (status) {
+ ALOGE("Error %d unlocking and posting sprite surface after drawing.", status);
+ } else {
+ update.state.surfaceDrawn = true;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+#endif
+ }
+
+#ifdef HAVE_ANDROID_OS
+ // Set sprite surface properties and make them visible.
+ bool haveTransaction = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+ bool wantSurfaceVisibleAndDrawn = update.state.wantSurfaceVisible()
+ && update.state.surfaceDrawn;
+ bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible;
+ bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible;
+ if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
+ || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
+ | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
+ | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
+ status_t status;
+ if (!haveTransaction) {
+ SurfaceComposerClient::openGlobalTransaction();
+ haveTransaction = true;
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & DIRTY_ALPHA))) {
+ status = update.state.surfaceControl->setAlpha(update.state.alpha);
+ if (status) {
+ ALOGE("Error %d setting sprite surface alpha.", status);
+ }
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & (DIRTY_POSITION
+ | DIRTY_HOTSPOT)))) {
+ status = update.state.surfaceControl->setPosition(
+ update.state.positionX - update.state.icon.hotSpotX,
+ update.state.positionY - update.state.icon.hotSpotY);
+ if (status) {
+ ALOGE("Error %d setting sprite surface position.", status);
+ }
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible
+ || (update.state.dirty & DIRTY_TRANSFORMATION_MATRIX))) {
+ status = update.state.surfaceControl->setMatrix(
+ update.state.transformationMatrix.dsdx,
+ update.state.transformationMatrix.dtdx,
+ update.state.transformationMatrix.dsdy,
+ update.state.transformationMatrix.dtdy);
+ if (status) {
+ ALOGE("Error %d setting sprite surface transformation matrix.", status);
+ }
+ }
+
+ int32_t surfaceLayer = mOverlayLayer + update.state.layer;
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) {
+ status = update.state.surfaceControl->setLayer(surfaceLayer);
+ if (status) {
+ ALOGE("Error %d setting sprite surface layer.", status);
+ }
+ }
+
+ if (becomingVisible) {
+ status = update.state.surfaceControl->show();
+ if (status) {
+ ALOGE("Error %d showing sprite surface.", status);
+ } else {
+ update.state.surfaceVisible = true;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ } else if (becomingHidden) {
+ status = update.state.surfaceControl->hide();
+ if (status) {
+ ALOGE("Error %d hiding sprite surface.", status);
+ } else {
+ update.state.surfaceVisible = false;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+ }
+#endif
+
+#ifdef HAVE_ANDROID_OS
+ if (haveTransaction) {
+ SurfaceComposerClient::closeGlobalTransaction();
+ }
+#endif
+
+#ifdef HAVE_ANDROID_OS
+ // If any surfaces were changed, write back the new surface properties to the sprites.
+ if (surfaceChanged) { // acquire lock
+ AutoMutex _l(mLock);
+
+ for (size_t i = 0; i < numSprites; i++) {
+ const SpriteUpdate& update = updates.itemAt(i);
+
+ if (update.surfaceChanged) {
+ update.sprite->setSurfaceLocked(update.state.surfaceControl,
+ update.state.surfaceWidth, update.state.surfaceHeight,
+ update.state.surfaceDrawn, update.state.surfaceVisible);
+ }
+ }
+ } // release lock
+#endif
+
+ // Clear the sprite update vector outside the lock. It is very important that
+ // we do not clear sprite references inside the lock since we could be releasing
+ // the last remaining reference to the sprite here which would result in the
+ // sprite being deleted and the lock being reacquired by the sprite destructor
+ // while already held.
+ updates.clear();
+}
+
+void SpriteController::doDisposeSurfaces() {
+#ifdef HAVE_ANDROID_OS
+ // Collect disposed surfaces.
+ Vector<sp<SurfaceControl> > disposedSurfaces;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ disposedSurfaces = mLocked.disposedSurfaces;
+ mLocked.disposedSurfaces.clear();
+ } // release lock
+
+ // Release the last reference to each surface outside of the lock.
+ // We don't want the surfaces to be deleted while we are holding our lock.
+ disposedSurfaces.clear();
+#endif
+}
+
+void SpriteController::ensureSurfaceComposerClient() {
+#ifdef HAVE_ANDROID_OS
+ if (mSurfaceComposerClient == NULL) {
+ mSurfaceComposerClient = new SurfaceComposerClient();
+ }
+#endif
+}
+
+#ifdef HAVE_ANDROID_OS
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) {
+ ensureSurfaceComposerClient();
+
+ sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
+ String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eHidden);
+ if (surfaceControl == NULL || !surfaceControl->isValid()) {
+ ALOGE("Error creating sprite surface.");
+ return NULL;
+ }
+ return surfaceControl;
+}
+#endif
+
+
+// --- SpriteController::SpriteImpl ---
+
+SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
+ mController(controller) {
+}
+
+SpriteController::SpriteImpl::~SpriteImpl() {
+ AutoMutex _m(mController->mLock);
+
+#ifdef HAVE_ANDROID_OS
+ // Let the controller take care of deleting the last reference to sprite
+ // surfaces so that we do not block the caller on an IPC here.
+ if (mLocked.state.surfaceControl != NULL) {
+ mController->disposeSurfaceLocked(mLocked.state.surfaceControl);
+ mLocked.state.surfaceControl.clear();
+ }
+#endif
+}
+
+void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
+ AutoMutex _l(mController->mLock);
+
+#ifdef HAVE_ANDROID_OS
+ uint32_t dirty;
+ if (icon.isValid()) {
+ icon.bitmap.copyTo(&mLocked.state.icon.bitmap, SkBitmap::kARGB_8888_Config);
+
+ if (!mLocked.state.icon.isValid()
+ || mLocked.state.icon.hotSpotX != icon.hotSpotX
+ || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+ mLocked.state.icon.hotSpotX = icon.hotSpotX;
+ mLocked.state.icon.hotSpotY = icon.hotSpotY;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ } else {
+ dirty = DIRTY_BITMAP;
+ }
+ } else if (mLocked.state.icon.isValid()) {
+ mLocked.state.icon.bitmap.reset();
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ } else {
+ return; // setting to invalid icon and already invalid so nothing to do
+ }
+
+ invalidateLocked(dirty);
+#endif
+}
+
+void SpriteController::SpriteImpl::setVisible(bool visible) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.visible != visible) {
+ mLocked.state.visible = visible;
+ invalidateLocked(DIRTY_VISIBILITY);
+ }
+}
+
+void SpriteController::SpriteImpl::setPosition(float x, float y) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.positionX != x || mLocked.state.positionY != y) {
+ mLocked.state.positionX = x;
+ mLocked.state.positionY = y;
+ invalidateLocked(DIRTY_POSITION);
+ }
+}
+
+void SpriteController::SpriteImpl::setLayer(int32_t layer) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.layer != layer) {
+ mLocked.state.layer = layer;
+ invalidateLocked(DIRTY_LAYER);
+ }
+}
+
+void SpriteController::SpriteImpl::setAlpha(float alpha) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.alpha != alpha) {
+ mLocked.state.alpha = alpha;
+ invalidateLocked(DIRTY_ALPHA);
+ }
+}
+
+void SpriteController::SpriteImpl::setTransformationMatrix(
+ const SpriteTransformationMatrix& matrix) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.transformationMatrix != matrix) {
+ mLocked.state.transformationMatrix = matrix;
+ invalidateLocked(DIRTY_TRANSFORMATION_MATRIX);
+ }
+}
+
+void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
+ bool wasDirty = mLocked.state.dirty;
+ mLocked.state.dirty |= dirty;
+
+ if (!wasDirty) {
+ mController->invalidateSpriteLocked(this);
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/SpriteController.h b/widget/gonk/libui/SpriteController.h
new file mode 100644
index 000000000..4926095ec
--- /dev/null
+++ b/widget/gonk/libui/SpriteController.h
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_SPRITES_H
+#define _UI_SPRITES_H
+
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+
+#ifdef HAVE_ANDROID_OS
+#include <gui/SurfaceComposerClient.h>
+#endif
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/*
+ * Transformation matrix for a sprite.
+ */
+struct SpriteTransformationMatrix {
+ inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { }
+ inline SpriteTransformationMatrix(float dsdx, float dtdx, float dsdy, float dtdy) :
+ dsdx(dsdx), dtdx(dtdx), dsdy(dsdy), dtdy(dtdy) { }
+
+ float dsdx;
+ float dtdx;
+ float dsdy;
+ float dtdy;
+
+ inline bool operator== (const SpriteTransformationMatrix& other) {
+ return dsdx == other.dsdx
+ && dtdx == other.dtdx
+ && dsdy == other.dsdy
+ && dtdy == other.dtdy;
+ }
+
+ inline bool operator!= (const SpriteTransformationMatrix& other) {
+ return !(*this == other);
+ }
+};
+
+/*
+ * Icon that a sprite displays, including its hotspot.
+ */
+struct SpriteIcon {
+ inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { }
+#ifdef HAVE_ANDROID_OS
+ inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) :
+ bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
+
+ SkBitmap bitmap;
+#endif
+ float hotSpotX;
+ float hotSpotY;
+
+ inline SpriteIcon copy() const {
+#ifdef HAVE_ANDROID_OS
+ SkBitmap bitmapCopy;
+ bitmap.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config);
+ return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY);
+#else
+ return SpriteIcon();
+#endif
+ }
+
+ inline void reset() {
+#ifdef HAVE_ANDROID_OS
+ bitmap.reset();
+ hotSpotX = 0;
+ hotSpotY = 0;
+#endif
+ }
+
+ inline bool isValid() const {
+#ifdef HAVE_ANDROID_OS
+ return !bitmap.isNull() && !bitmap.empty();
+#else
+ return false;
+#endif
+ }
+};
+
+/*
+ * A sprite is a simple graphical object that is displayed on-screen above other layers.
+ * The basic sprite class is an interface.
+ * The implementation is provided by the sprite controller.
+ */
+class Sprite : public RefBase {
+protected:
+ Sprite() { }
+ virtual ~Sprite() { }
+
+public:
+ enum {
+ // The base layer for pointer sprites.
+ BASE_LAYER_POINTER = 0, // reserve space for 1 pointer
+
+ // The base layer for spot sprites.
+ BASE_LAYER_SPOT = 1, // reserve space for MAX_POINTER_ID spots
+ };
+
+ /* Sets the bitmap that is drawn by the sprite.
+ * The sprite retains a copy of the bitmap for subsequent rendering. */
+ virtual void setIcon(const SpriteIcon& icon) = 0;
+
+ inline void clearIcon() {
+ setIcon(SpriteIcon());
+ }
+
+ /* Sets whether the sprite is visible. */
+ virtual void setVisible(bool visible) = 0;
+
+ /* Sets the sprite position on screen, relative to the sprite's hot spot. */
+ virtual void setPosition(float x, float y) = 0;
+
+ /* Sets the layer of the sprite, relative to the system sprite overlay layer.
+ * Layer 0 is the overlay layer, > 0 appear above this layer. */
+ virtual void setLayer(int32_t layer) = 0;
+
+ /* Sets the sprite alpha blend ratio between 0.0 and 1.0. */
+ virtual void setAlpha(float alpha) = 0;
+
+ /* Sets the sprite transformation matrix. */
+ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
+};
+
+/*
+ * Displays sprites on the screen.
+ *
+ * This interface is used by PointerController and SpotController to draw pointers or
+ * spot representations of fingers. It is not intended for general purpose use
+ * by other components.
+ *
+ * All sprite position updates and rendering is performed asynchronously.
+ *
+ * Clients are responsible for animating sprites by periodically updating their properties.
+ */
+class SpriteController : public MessageHandler {
+protected:
+ virtual ~SpriteController();
+
+public:
+ SpriteController(const sp<Looper>& looper, int32_t overlayLayer);
+
+ /* Creates a new sprite, initially invisible. */
+ sp<Sprite> createSprite();
+
+ /* Opens or closes a transaction to perform a batch of sprite updates as part of
+ * a single operation such as setPosition and setAlpha. It is not necessary to
+ * open a transaction when updating a single property.
+ * Calls to openTransaction() nest and must be matched by an equal number
+ * of calls to closeTransaction(). */
+ void openTransaction();
+ void closeTransaction();
+
+private:
+ enum {
+ MSG_UPDATE_SPRITES,
+ MSG_DISPOSE_SURFACES,
+ };
+
+ enum {
+ DIRTY_BITMAP = 1 << 0,
+ DIRTY_ALPHA = 1 << 1,
+ DIRTY_POSITION = 1 << 2,
+ DIRTY_TRANSFORMATION_MATRIX = 1 << 3,
+ DIRTY_LAYER = 1 << 4,
+ DIRTY_VISIBILITY = 1 << 5,
+ DIRTY_HOTSPOT = 1 << 6,
+ };
+
+ /* Describes the state of a sprite.
+ * This structure is designed so that it can be copied during updates so that
+ * surfaces can be resized and redrawn without blocking the client by holding a lock
+ * on the sprites for a long time.
+ * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
+ struct SpriteState {
+ inline SpriteState() :
+ dirty(0), visible(false),
+ positionX(0), positionY(0), layer(0), alpha(1.0f),
+ surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
+ }
+
+ uint32_t dirty;
+
+ SpriteIcon icon;
+ bool visible;
+ float positionX;
+ float positionY;
+ int32_t layer;
+ float alpha;
+ SpriteTransformationMatrix transformationMatrix;
+
+#ifdef HAVE_ANDROID_OS
+ sp<SurfaceControl> surfaceControl;
+#endif
+ int32_t surfaceWidth;
+ int32_t surfaceHeight;
+ bool surfaceDrawn;
+ bool surfaceVisible;
+
+ inline bool wantSurfaceVisible() const {
+ return visible && alpha > 0.0f && icon.isValid();
+ }
+ };
+
+ /* Client interface for a sprite.
+ * Requests acquire a lock on the controller, update local state and request the
+ * controller to invalidate the sprite.
+ * The real heavy lifting of creating, resizing and redrawing surfaces happens
+ * asynchronously with no locks held except in short critical section to copy
+ * the sprite state before the work and update the sprite surface control afterwards.
+ */
+ class SpriteImpl : public Sprite {
+ protected:
+ virtual ~SpriteImpl();
+
+ public:
+ SpriteImpl(const sp<SpriteController> controller);
+
+ virtual void setIcon(const SpriteIcon& icon);
+ virtual void setVisible(bool visible);
+ virtual void setPosition(float x, float y);
+ virtual void setLayer(int32_t layer);
+ virtual void setAlpha(float alpha);
+ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
+
+ inline const SpriteState& getStateLocked() const {
+ return mLocked.state;
+ }
+
+ inline void resetDirtyLocked() {
+ mLocked.state.dirty = 0;
+ }
+
+#ifdef HAVE_ANDROID_OS
+ inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
+ int32_t width, int32_t height, bool drawn, bool visible) {
+ mLocked.state.surfaceControl = surfaceControl;
+ mLocked.state.surfaceWidth = width;
+ mLocked.state.surfaceHeight = height;
+ mLocked.state.surfaceDrawn = drawn;
+ mLocked.state.surfaceVisible = visible;
+ }
+#endif
+
+ private:
+ sp<SpriteController> mController;
+
+ struct Locked {
+ SpriteState state;
+ } mLocked; // guarded by mController->mLock
+
+ void invalidateLocked(uint32_t dirty);
+ };
+
+ /* Stores temporary information collected during the sprite update cycle. */
+ struct SpriteUpdate {
+ inline SpriteUpdate() : surfaceChanged(false) { }
+ inline SpriteUpdate(const sp<SpriteImpl> sprite, const SpriteState& state) :
+ sprite(sprite), state(state), surfaceChanged(false) {
+ }
+
+ sp<SpriteImpl> sprite;
+ SpriteState state;
+ bool surfaceChanged;
+ };
+
+ mutable Mutex mLock;
+
+ sp<Looper> mLooper;
+ const int32_t mOverlayLayer;
+#ifdef HAVE_ANDROID_OS
+ sp<WeakMessageHandler> mHandler;
+
+ sp<SurfaceComposerClient> mSurfaceComposerClient;
+#endif
+
+ struct Locked {
+ Vector<sp<SpriteImpl> > invalidatedSprites;
+#ifdef HAVE_ANDROID_OS
+ Vector<sp<SurfaceControl> > disposedSurfaces;
+#endif
+ uint32_t transactionNestingCount;
+ bool deferredSpriteUpdate;
+ } mLocked; // guarded by mLock
+
+ void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
+#ifdef HAVE_ANDROID_OS
+ void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
+
+ void handleMessage(const Message& message);
+#endif
+ void doUpdateSprites();
+ void doDisposeSurfaces();
+
+ void ensureSurfaceComposerClient();
+#ifdef HAVE_ANDROID_OS
+ sp<SurfaceControl> obtainSurface(int32_t width, int32_t height);
+#endif
+};
+
+} // namespace android
+
+#endif // _UI_SPRITES_H
diff --git a/widget/gonk/libui/Tokenizer.cpp b/widget/gonk/libui/Tokenizer.cpp
new file mode 100644
index 000000000..2f585cb4e
--- /dev/null
+++ b/widget/gonk/libui/Tokenizer.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Tokenizer"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "Tokenizer.h"
+
+// Enables debug output for the tokenizer.
+#define DEBUG_TOKENIZER 0
+
+
+namespace android {
+
+static inline bool isDelimiter(char ch, const char* delimiters) {
+ return strchr(delimiters, ch) != NULL;
+}
+
+Tokenizer::Tokenizer(const String8& filename, FileMap* fileMap, char* buffer,
+ bool ownBuffer, size_t length) :
+ mFilename(filename), mFileMap(fileMap),
+ mBuffer(buffer), mOwnBuffer(ownBuffer), mLength(length),
+ mCurrent(buffer), mLineNumber(1) {
+}
+
+Tokenizer::~Tokenizer() {
+ if (mFileMap) {
+ mFileMap->release();
+ }
+ if (mOwnBuffer) {
+ delete[] mBuffer;
+ }
+}
+
+status_t Tokenizer::open(const String8& filename, Tokenizer** outTokenizer) {
+ *outTokenizer = NULL;
+
+ int result = NO_ERROR;
+ int fd = ::open(filename.string(), O_RDONLY);
+ if (fd < 0) {
+ result = -errno;
+ ALOGE("Error opening file '%s', %s.", filename.string(), strerror(errno));
+ } else {
+ struct stat stat;
+ if (fstat(fd, &stat)) {
+ result = -errno;
+ ALOGE("Error getting size of file '%s', %s.", filename.string(), strerror(errno));
+ } else {
+ size_t length = size_t(stat.st_size);
+
+ FileMap* fileMap = new FileMap();
+ bool ownBuffer = false;
+ char* buffer;
+ if (fileMap->create(NULL, fd, 0, length, true)) {
+ fileMap->advise(FileMap::SEQUENTIAL);
+ buffer = static_cast<char*>(fileMap->getDataPtr());
+ } else {
+ fileMap->release();
+ fileMap = NULL;
+
+ // Fall back to reading into a buffer since we can't mmap files in sysfs.
+ // The length we obtained from stat is wrong too (it will always be 4096)
+ // so we must trust that read will read the entire file.
+ buffer = new char[length];
+ ownBuffer = true;
+ ssize_t nrd = read(fd, buffer, length);
+ if (nrd < 0) {
+ result = -errno;
+ ALOGE("Error reading file '%s', %s.", filename.string(), strerror(errno));
+ delete[] buffer;
+ buffer = NULL;
+ } else {
+ length = size_t(nrd);
+ }
+ }
+
+ if (!result) {
+ *outTokenizer = new Tokenizer(filename, fileMap, buffer, ownBuffer, length);
+ }
+ }
+ close(fd);
+ }
+ return result;
+}
+
+status_t Tokenizer::fromContents(const String8& filename,
+ const char* contents, Tokenizer** outTokenizer) {
+ *outTokenizer = new Tokenizer(filename, NULL,
+ const_cast<char*>(contents), false, strlen(contents));
+ return OK;
+}
+
+String8 Tokenizer::getLocation() const {
+ String8 result;
+ result.appendFormat("%s:%d", mFilename.string(), mLineNumber);
+ return result;
+}
+
+String8 Tokenizer::peekRemainderOfLine() const {
+ const char* end = getEnd();
+ const char* eol = mCurrent;
+ while (eol != end) {
+ char ch = *eol;
+ if (ch == '\n') {
+ break;
+ }
+ eol += 1;
+ }
+ return String8(mCurrent, eol - mCurrent);
+}
+
+String8 Tokenizer::nextToken(const char* delimiters) {
+#if DEBUG_TOKENIZER
+ ALOGD("nextToken");
+#endif
+ const char* end = getEnd();
+ const char* tokenStart = mCurrent;
+ while (mCurrent != end) {
+ char ch = *mCurrent;
+ if (ch == '\n' || isDelimiter(ch, delimiters)) {
+ break;
+ }
+ mCurrent += 1;
+ }
+ return String8(tokenStart, mCurrent - tokenStart);
+}
+
+void Tokenizer::nextLine() {
+#if DEBUG_TOKENIZER
+ ALOGD("nextLine");
+#endif
+ const char* end = getEnd();
+ while (mCurrent != end) {
+ char ch = *(mCurrent++);
+ if (ch == '\n') {
+ mLineNumber += 1;
+ break;
+ }
+ }
+}
+
+void Tokenizer::skipDelimiters(const char* delimiters) {
+#if DEBUG_TOKENIZER
+ ALOGD("skipDelimiters");
+#endif
+ const char* end = getEnd();
+ while (mCurrent != end) {
+ char ch = *mCurrent;
+ if (ch == '\n' || !isDelimiter(ch, delimiters)) {
+ break;
+ }
+ mCurrent += 1;
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/Tokenizer.h b/widget/gonk/libui/Tokenizer.h
new file mode 100644
index 000000000..bb25f374c
--- /dev/null
+++ b/widget/gonk/libui/Tokenizer.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UTILS_TOKENIZER_H
+#define _UTILS_TOKENIZER_H
+
+#include <assert.h>
+#include <utils/Errors.h>
+#include <utils/FileMap.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/**
+ * A simple tokenizer for loading and parsing ASCII text files line by line.
+ */
+class Tokenizer {
+ Tokenizer(const String8& filename, FileMap* fileMap, char* buffer,
+ bool ownBuffer, size_t length);
+
+public:
+ ~Tokenizer();
+
+ /**
+ * Opens a file and maps it into memory.
+ *
+ * Returns NO_ERROR and a tokenizer for the file, if successful.
+ * Otherwise returns an error and sets outTokenizer to NULL.
+ */
+ static status_t open(const String8& filename, Tokenizer** outTokenizer);
+
+ /**
+ * Prepares to tokenize the contents of a string.
+ *
+ * Returns NO_ERROR and a tokenizer for the string, if successful.
+ * Otherwise returns an error and sets outTokenizer to NULL.
+ */
+ static status_t fromContents(const String8& filename,
+ const char* contents, Tokenizer** outTokenizer);
+
+ /**
+ * Returns true if at the end of the file.
+ */
+ inline bool isEof() const { return mCurrent == getEnd(); }
+
+ /**
+ * Returns true if at the end of the line or end of the file.
+ */
+ inline bool isEol() const { return isEof() || *mCurrent == '\n'; }
+
+ /**
+ * Gets the name of the file.
+ */
+ inline String8 getFilename() const { return mFilename; }
+
+ /**
+ * Gets a 1-based line number index for the current position.
+ */
+ inline int32_t getLineNumber() const { return mLineNumber; }
+
+ /**
+ * Formats a location string consisting of the filename and current line number.
+ * Returns a string like "MyFile.txt:33".
+ */
+ String8 getLocation() const;
+
+ /**
+ * Gets the character at the current position.
+ * Returns null at end of file.
+ */
+ inline char peekChar() const { return isEof() ? '\0' : *mCurrent; }
+
+ /**
+ * Gets the remainder of the current line as a string, excluding the newline character.
+ */
+ String8 peekRemainderOfLine() const;
+
+ /**
+ * Gets the character at the current position and advances past it.
+ * Returns null at end of file.
+ */
+ inline char nextChar() { return isEof() ? '\0' : *(mCurrent++); }
+
+ /**
+ * Gets the next token on this line stopping at the specified delimiters
+ * or the end of the line whichever comes first and advances past it.
+ * Also stops at embedded nulls.
+ * Returns the token or an empty string if the current character is a delimiter
+ * or is at the end of the line.
+ */
+ String8 nextToken(const char* delimiters);
+
+ /**
+ * Advances to the next line.
+ * Does nothing if already at the end of the file.
+ */
+ void nextLine();
+
+ /**
+ * Skips over the specified delimiters in the line.
+ * Also skips embedded nulls.
+ */
+ void skipDelimiters(const char* delimiters);
+
+private:
+ Tokenizer(const Tokenizer& other); // not copyable
+
+ String8 mFilename;
+ FileMap* mFileMap;
+ char* mBuffer;
+ bool mOwnBuffer;
+ size_t mLength;
+
+ const char* mCurrent;
+ int32_t mLineNumber;
+
+ inline const char* getEnd() const { return mBuffer + mLength; }
+
+};
+
+} // namespace android
+
+#endif // _UTILS_TOKENIZER_H
diff --git a/widget/gonk/libui/Trace.h b/widget/gonk/libui/Trace.h
new file mode 100644
index 000000000..24fbfb602
--- /dev/null
+++ b/widget/gonk/libui/Trace.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_TRACE_H
+#define ANDROID_TRACE_H
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cutils/compiler.h>
+#include <utils/threads.h>
+#include "cutils_trace.h"
+
+// See <cutils/trace.h> for more ATRACE_* macros.
+
+// ATRACE_NAME traces the beginning and end of the current scope. To trace
+// the correct start and end times this macro should be declared first in the
+// scope body.
+#define ATRACE_NAME(name) android::ScopedTrace ___tracer(ATRACE_TAG, name)
+// ATRACE_CALL is an ATRACE_NAME that uses the current function name.
+#define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)
+
+namespace android {
+
+class ScopedTrace {
+public:
+inline ScopedTrace(uint64_t tag, const char* name)
+ : mTag(tag) {
+#ifdef HAVE_ANDROID_OS
+ atrace_begin(mTag,name);
+#endif
+}
+
+inline ~ScopedTrace() {
+#ifdef HAVE_ANDROID_OS
+ atrace_end(mTag);
+#endif
+}
+
+private:
+ uint64_t mTag;
+};
+
+}; // namespace android
+
+#endif // ANDROID_TRACE_H
diff --git a/widget/gonk/libui/VelocityControl.cpp b/widget/gonk/libui/VelocityControl.cpp
new file mode 100644
index 000000000..31365a220
--- /dev/null
+++ b/widget/gonk/libui/VelocityControl.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VelocityControl"
+//#define LOG_NDEBUG 0
+
+// Log debug messages about acceleration.
+#define DEBUG_ACCELERATION 0
+
+#include <math.h>
+#include <limits.h>
+
+#include "VelocityControl.h"
+#include <utils/BitSet.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+// --- VelocityControl ---
+
+const nsecs_t VelocityControl::STOP_TIME;
+
+VelocityControl::VelocityControl() {
+ reset();
+}
+
+void VelocityControl::setParameters(const VelocityControlParameters& parameters) {
+ mParameters = parameters;
+ reset();
+}
+
+void VelocityControl::reset() {
+ mLastMovementTime = LLONG_MIN;
+ mRawPosition.x = 0;
+ mRawPosition.y = 0;
+ mVelocityTracker.clear();
+}
+
+void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) {
+ if ((deltaX && *deltaX) || (deltaY && *deltaY)) {
+ if (eventTime >= mLastMovementTime + STOP_TIME) {
+#if DEBUG_ACCELERATION
+ ALOGD("VelocityControl: stopped, last movement was %0.3fms ago",
+ (eventTime - mLastMovementTime) * 0.000001f);
+#endif
+ reset();
+ }
+
+ mLastMovementTime = eventTime;
+ if (deltaX) {
+ mRawPosition.x += *deltaX;
+ }
+ if (deltaY) {
+ mRawPosition.y += *deltaY;
+ }
+ mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), &mRawPosition);
+
+ float vx, vy;
+ float scale = mParameters.scale;
+ if (mVelocityTracker.getVelocity(0, &vx, &vy)) {
+ float speed = hypotf(vx, vy) * scale;
+ if (speed >= mParameters.highThreshold) {
+ // Apply full acceleration above the high speed threshold.
+ scale *= mParameters.acceleration;
+ } else if (speed > mParameters.lowThreshold) {
+ // Linearly interpolate the acceleration to apply between the low and high
+ // speed thresholds.
+ scale *= 1 + (speed - mParameters.lowThreshold)
+ / (mParameters.highThreshold - mParameters.lowThreshold)
+ * (mParameters.acceleration - 1);
+ }
+
+#if DEBUG_ACCELERATION
+ ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
+ "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
+ mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
+ mParameters.acceleration,
+ vx, vy, speed, scale / mParameters.scale);
+#endif
+ } else {
+#if DEBUG_ACCELERATION
+ ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity",
+ mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
+ mParameters.acceleration);
+#endif
+ }
+
+ if (deltaX) {
+ *deltaX *= scale;
+ }
+ if (deltaY) {
+ *deltaY *= scale;
+ }
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/VelocityControl.h b/widget/gonk/libui/VelocityControl.h
new file mode 100644
index 000000000..8a2c695d6
--- /dev/null
+++ b/widget/gonk/libui/VelocityControl.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_VELOCITY_CONTROL_H
+#define _ANDROIDFW_VELOCITY_CONTROL_H
+
+#include "Input.h"
+#include "VelocityTracker.h"
+#include <utils/Timers.h>
+
+namespace android {
+
+/*
+ * Specifies parameters that govern pointer or wheel acceleration.
+ */
+struct VelocityControlParameters {
+ // A scale factor that is multiplied with the raw velocity deltas
+ // prior to applying any other velocity control factors. The scale
+ // factor should be used to adapt the input device resolution
+ // (eg. counts per inch) to the output device resolution (eg. pixels per inch).
+ //
+ // Must be a positive value.
+ // Default is 1.0 (no scaling).
+ float scale;
+
+ // The scaled speed at which acceleration begins to be applied.
+ // This value establishes the upper bound of a low speed regime for
+ // small precise motions that are performed without any acceleration.
+ //
+ // Must be a non-negative value.
+ // Default is 0.0 (no low threshold).
+ float lowThreshold;
+
+ // The scaled speed at which maximum acceleration is applied.
+ // The difference between highThreshold and lowThreshold controls
+ // the range of speeds over which the acceleration factor is interpolated.
+ // The wider the range, the smoother the acceleration.
+ //
+ // Must be a non-negative value greater than or equal to lowThreshold.
+ // Default is 0.0 (no high threshold).
+ float highThreshold;
+
+ // The acceleration factor.
+ // When the speed is above the low speed threshold, the velocity will scaled
+ // by an interpolated value between 1.0 and this amount.
+ //
+ // Must be a positive greater than or equal to 1.0.
+ // Default is 1.0 (no acceleration).
+ float acceleration;
+
+ VelocityControlParameters() :
+ scale(1.0f), lowThreshold(0.0f), highThreshold(0.0f), acceleration(1.0f) {
+ }
+
+ VelocityControlParameters(float scale, float lowThreshold,
+ float highThreshold, float acceleration) :
+ scale(scale), lowThreshold(lowThreshold),
+ highThreshold(highThreshold), acceleration(acceleration) {
+ }
+};
+
+/*
+ * Implements mouse pointer and wheel speed control and acceleration.
+ */
+class VelocityControl {
+public:
+ VelocityControl();
+
+ /* Sets the various parameters. */
+ void setParameters(const VelocityControlParameters& parameters);
+
+ /* Resets the current movement counters to zero.
+ * This has the effect of nullifying any acceleration. */
+ void reset();
+
+ /* Translates a raw movement delta into an appropriately
+ * scaled / accelerated delta based on the current velocity. */
+ void move(nsecs_t eventTime, float* deltaX, float* deltaY);
+
+private:
+ // If no movements are received within this amount of time,
+ // we assume the movement has stopped and reset the movement counters.
+ static const nsecs_t STOP_TIME = 500 * 1000000; // 500 ms
+
+ VelocityControlParameters mParameters;
+
+ nsecs_t mLastMovementTime;
+ VelocityTracker::Position mRawPosition;
+ VelocityTracker mVelocityTracker;
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_VELOCITY_CONTROL_H
diff --git a/widget/gonk/libui/VelocityTracker.cpp b/widget/gonk/libui/VelocityTracker.cpp
new file mode 100644
index 000000000..11a8bf7fc
--- /dev/null
+++ b/widget/gonk/libui/VelocityTracker.cpp
@@ -0,0 +1,929 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VelocityTracker"
+//#define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+// Log debug messages about velocity tracking.
+#define DEBUG_VELOCITY 0
+
+// Log debug messages about the progress of the algorithm itself.
+#define DEBUG_STRATEGY 0
+
+#include <math.h>
+#include <limits.h>
+
+#include "VelocityTracker.h"
+#include <utils/BitSet.h>
+#include <utils/String8.h>
+#include <utils/Timers.h>
+
+#include <cutils/properties.h>
+
+namespace android {
+
+// Nanoseconds per milliseconds.
+static const nsecs_t NANOS_PER_MS = 1000000;
+
+// Threshold for determining that a pointer has stopped moving.
+// Some input devices do not send ACTION_MOVE events in the case where a pointer has
+// stopped. We need to detect this case so that we can accurately predict the
+// velocity after the pointer starts moving again.
+static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS;
+
+
+static float vectorDot(const float* a, const float* b, uint32_t m) {
+ float r = 0;
+ while (m--) {
+ r += *(a++) * *(b++);
+ }
+ return r;
+}
+
+static float vectorNorm(const float* a, uint32_t m) {
+ float r = 0;
+ while (m--) {
+ float t = *(a++);
+ r += t * t;
+ }
+ return sqrtf(r);
+}
+
+#if DEBUG_STRATEGY || DEBUG_VELOCITY
+static String8 vectorToString(const float* a, uint32_t m) {
+ String8 str;
+ str.append("[");
+ while (m--) {
+ str.appendFormat(" %f", *(a++));
+ if (m) {
+ str.append(",");
+ }
+ }
+ str.append(" ]");
+ return str;
+}
+
+static String8 matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
+ String8 str;
+ str.append("[");
+ for (size_t i = 0; i < m; i++) {
+ if (i) {
+ str.append(",");
+ }
+ str.append(" [");
+ for (size_t j = 0; j < n; j++) {
+ if (j) {
+ str.append(",");
+ }
+ str.appendFormat(" %f", a[rowMajor ? i * n + j : j * m + i]);
+ }
+ str.append(" ]");
+ }
+ str.append(" ]");
+ return str;
+}
+#endif
+
+
+// --- VelocityTracker ---
+
+// The default velocity tracker strategy.
+// Although other strategies are available for testing and comparison purposes,
+// this is the strategy that applications will actually use. Be very careful
+// when adjusting the default strategy because it can dramatically affect
+// (often in a bad way) the user experience.
+const char* VelocityTracker::DEFAULT_STRATEGY = "lsq2";
+
+VelocityTracker::VelocityTracker(const char* strategy) :
+ mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) {
+ char value[PROPERTY_VALUE_MAX];
+
+ // Allow the default strategy to be overridden using a system property for debugging.
+ if (!strategy) {
+ int length = property_get("debug.velocitytracker.strategy", value, NULL);
+ if (length > 0) {
+ strategy = value;
+ } else {
+ strategy = DEFAULT_STRATEGY;
+ }
+ }
+
+ // Configure the strategy.
+ if (!configureStrategy(strategy)) {
+ ALOGD("Unrecognized velocity tracker strategy name '%s'.", strategy);
+ if (!configureStrategy(DEFAULT_STRATEGY)) {
+ LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%s'!",
+ strategy);
+ }
+ }
+}
+
+VelocityTracker::~VelocityTracker() {
+ delete mStrategy;
+}
+
+bool VelocityTracker::configureStrategy(const char* strategy) {
+ mStrategy = createStrategy(strategy);
+ return mStrategy != NULL;
+}
+
+VelocityTrackerStrategy* VelocityTracker::createStrategy(const char* strategy) {
+ if (!strcmp("lsq1", strategy)) {
+ // 1st order least squares. Quality: POOR.
+ // Frequently underfits the touch data especially when the finger accelerates
+ // or changes direction. Often underestimates velocity. The direction
+ // is overly influenced by historical touch points.
+ return new LeastSquaresVelocityTrackerStrategy(1);
+ }
+ if (!strcmp("lsq2", strategy)) {
+ // 2nd order least squares. Quality: VERY GOOD.
+ // Pretty much ideal, but can be confused by certain kinds of touch data,
+ // particularly if the panel has a tendency to generate delayed,
+ // duplicate or jittery touch coordinates when the finger is released.
+ return new LeastSquaresVelocityTrackerStrategy(2);
+ }
+ if (!strcmp("lsq3", strategy)) {
+ // 3rd order least squares. Quality: UNUSABLE.
+ // Frequently overfits the touch data yielding wildly divergent estimates
+ // of the velocity when the finger is released.
+ return new LeastSquaresVelocityTrackerStrategy(3);
+ }
+ if (!strcmp("wlsq2-delta", strategy)) {
+ // 2nd order weighted least squares, delta weighting. Quality: EXPERIMENTAL
+ return new LeastSquaresVelocityTrackerStrategy(2,
+ LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA);
+ }
+ if (!strcmp("wlsq2-central", strategy)) {
+ // 2nd order weighted least squares, central weighting. Quality: EXPERIMENTAL
+ return new LeastSquaresVelocityTrackerStrategy(2,
+ LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL);
+ }
+ if (!strcmp("wlsq2-recent", strategy)) {
+ // 2nd order weighted least squares, recent weighting. Quality: EXPERIMENTAL
+ return new LeastSquaresVelocityTrackerStrategy(2,
+ LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT);
+ }
+ if (!strcmp("int1", strategy)) {
+ // 1st order integrating filter. Quality: GOOD.
+ // Not as good as 'lsq2' because it cannot estimate acceleration but it is
+ // more tolerant of errors. Like 'lsq1', this strategy tends to underestimate
+ // the velocity of a fling but this strategy tends to respond to changes in
+ // direction more quickly and accurately.
+ return new IntegratingVelocityTrackerStrategy(1);
+ }
+ if (!strcmp("int2", strategy)) {
+ // 2nd order integrating filter. Quality: EXPERIMENTAL.
+ // For comparison purposes only. Unlike 'int1' this strategy can compensate
+ // for acceleration but it typically overestimates the effect.
+ return new IntegratingVelocityTrackerStrategy(2);
+ }
+ if (!strcmp("legacy", strategy)) {
+ // Legacy velocity tracker algorithm. Quality: POOR.
+ // For comparison purposes only. This algorithm is strongly influenced by
+ // old data points, consistently underestimates velocity and takes a very long
+ // time to adjust to changes in direction.
+ return new LegacyVelocityTrackerStrategy();
+ }
+ return NULL;
+}
+
+void VelocityTracker::clear() {
+ mCurrentPointerIdBits.clear();
+ mActivePointerId = -1;
+
+ mStrategy->clear();
+}
+
+void VelocityTracker::clearPointers(BitSet32 idBits) {
+ BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value);
+ mCurrentPointerIdBits = remainingIdBits;
+
+ if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
+ mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
+ }
+
+ mStrategy->clearPointers(idBits);
+}
+
+void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) {
+ while (idBits.count() > MAX_POINTERS) {
+ idBits.clearLastMarkedBit();
+ }
+
+ if ((mCurrentPointerIdBits.value & idBits.value)
+ && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) {
+#if DEBUG_VELOCITY
+ ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.",
+ (eventTime - mLastEventTime) * 0.000001f);
+#endif
+ // We have not received any movements for too long. Assume that all pointers
+ // have stopped.
+ mStrategy->clear();
+ }
+ mLastEventTime = eventTime;
+
+ mCurrentPointerIdBits = idBits;
+ if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
+ mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit();
+ }
+
+ mStrategy->addMovement(eventTime, idBits, positions);
+
+#if DEBUG_VELOCITY
+ ALOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x, activePointerId=%d",
+ eventTime, idBits.value, mActivePointerId);
+ for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
+ uint32_t id = iterBits.firstMarkedBit();
+ uint32_t index = idBits.getIndexOfBit(id);
+ iterBits.clearBit(id);
+ Estimator estimator;
+ getEstimator(id, &estimator);
+ ALOGD(" %d: position (%0.3f, %0.3f), "
+ "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)",
+ id, positions[index].x, positions[index].y,
+ int(estimator.degree),
+ vectorToString(estimator.xCoeff, estimator.degree + 1).string(),
+ vectorToString(estimator.yCoeff, estimator.degree + 1).string(),
+ estimator.confidence);
+ }
+#endif
+}
+
+void VelocityTracker::addMovement(const MotionEvent* event) {
+ int32_t actionMasked = event->getActionMasked();
+
+ switch (actionMasked) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ // Clear all pointers on down before adding the new movement.
+ clear();
+ break;
+ case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+ // Start a new movement trace for a pointer that just went down.
+ // We do this on down instead of on up because the client may want to query the
+ // final velocity for a pointer that just went up.
+ BitSet32 downIdBits;
+ downIdBits.markBit(event->getPointerId(event->getActionIndex()));
+ clearPointers(downIdBits);
+ break;
+ }
+ case AMOTION_EVENT_ACTION_MOVE:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ break;
+ default:
+ // Ignore all other actions because they do not convey any new information about
+ // pointer movement. We also want to preserve the last known velocity of the pointers.
+ // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
+ // of the pointers that went up. ACTION_POINTER_UP does include the new position of
+ // pointers that remained down but we will also receive an ACTION_MOVE with this
+ // information if any of them actually moved. Since we don't know how many pointers
+ // will be going up at once it makes sense to just wait for the following ACTION_MOVE
+ // before adding the movement.
+ return;
+ }
+
+ size_t pointerCount = event->getPointerCount();
+ if (pointerCount > MAX_POINTERS) {
+ pointerCount = MAX_POINTERS;
+ }
+
+ BitSet32 idBits;
+ for (size_t i = 0; i < pointerCount; i++) {
+ idBits.markBit(event->getPointerId(i));
+ }
+
+ uint32_t pointerIndex[MAX_POINTERS];
+ for (size_t i = 0; i < pointerCount; i++) {
+ pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
+ }
+
+ nsecs_t eventTime;
+ Position positions[pointerCount];
+
+ size_t historySize = event->getHistorySize();
+ for (size_t h = 0; h < historySize; h++) {
+ eventTime = event->getHistoricalEventTime(h);
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t index = pointerIndex[i];
+ positions[index].x = event->getHistoricalX(i, h);
+ positions[index].y = event->getHistoricalY(i, h);
+ }
+ addMovement(eventTime, idBits, positions);
+ }
+
+ eventTime = event->getEventTime();
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t index = pointerIndex[i];
+ positions[index].x = event->getX(i);
+ positions[index].y = event->getY(i);
+ }
+ addMovement(eventTime, idBits, positions);
+}
+
+bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
+ Estimator estimator;
+ if (getEstimator(id, &estimator) && estimator.degree >= 1) {
+ *outVx = estimator.xCoeff[1];
+ *outVy = estimator.yCoeff[1];
+ return true;
+ }
+ *outVx = 0;
+ *outVy = 0;
+ return false;
+}
+
+bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const {
+ return mStrategy->getEstimator(id, outEstimator);
+}
+
+
+// --- LeastSquaresVelocityTrackerStrategy ---
+
+const nsecs_t LeastSquaresVelocityTrackerStrategy::HORIZON;
+const uint32_t LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE;
+
+LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
+ uint32_t degree, Weighting weighting) :
+ mDegree(degree), mWeighting(weighting) {
+ clear();
+}
+
+LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
+}
+
+void LeastSquaresVelocityTrackerStrategy::clear() {
+ mIndex = 0;
+ mMovements[0].idBits.clear();
+}
+
+void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
+ BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
+ mMovements[mIndex].idBits = remainingIdBits;
+}
+
+void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions) {
+ if (++mIndex == HISTORY_SIZE) {
+ mIndex = 0;
+ }
+
+ Movement& movement = mMovements[mIndex];
+ movement.eventTime = eventTime;
+ movement.idBits = idBits;
+ uint32_t count = idBits.count();
+ for (uint32_t i = 0; i < count; i++) {
+ movement.positions[i] = positions[i];
+ }
+}
+
+/**
+ * Solves a linear least squares problem to obtain a N degree polynomial that fits
+ * the specified input data as nearly as possible.
+ *
+ * Returns true if a solution is found, false otherwise.
+ *
+ * The input consists of two vectors of data points X and Y with indices 0..m-1
+ * along with a weight vector W of the same size.
+ *
+ * The output is a vector B with indices 0..n that describes a polynomial
+ * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i]
+ * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized.
+ *
+ * Accordingly, the weight vector W should be initialized by the caller with the
+ * reciprocal square root of the variance of the error in each input data point.
+ * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]).
+ * The weights express the relative importance of each data point. If the weights are
+ * all 1, then the data points are considered to be of equal importance when fitting
+ * the polynomial. It is a good idea to choose weights that diminish the importance
+ * of data points that may have higher than usual error margins.
+ *
+ * Errors among data points are assumed to be independent. W is represented here
+ * as a vector although in the literature it is typically taken to be a diagonal matrix.
+ *
+ * That is to say, the function that generated the input data can be approximated
+ * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
+ *
+ * The coefficient of determination (R^2) is also returned to describe the goodness
+ * of fit of the model for the given data. It is a value between 0 and 1, where 1
+ * indicates perfect correspondence.
+ *
+ * This function first expands the X vector to a m by n matrix A such that
+ * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
+ * multiplies it by w[i]./
+ *
+ * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
+ * and an m by n upper triangular matrix R. Because R is upper triangular (lower
+ * part is all zeroes), we can simplify the decomposition into an m by n matrix
+ * Q1 and a n by n matrix R1 such that A = Q1 R1.
+ *
+ * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y)
+ * to find B.
+ *
+ * For efficiency, we lay out A and Q column-wise in memory because we frequently
+ * operate on the column vectors. Conversely, we lay out R row-wise.
+ *
+ * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
+ * http://en.wikipedia.org/wiki/Gram-Schmidt
+ */
+static bool solveLeastSquares(const float* x, const float* y,
+ const float* w, uint32_t m, uint32_t n, float* outB, float* outDet) {
+#if DEBUG_STRATEGY
+ ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
+ vectorToString(x, m).string(), vectorToString(y, m).string(),
+ vectorToString(w, m).string());
+#endif
+
+ // Expand the X vector to a matrix A, pre-multiplied by the weights.
+ float a[n][m]; // column-major order
+ for (uint32_t h = 0; h < m; h++) {
+ a[0][h] = w[h];
+ for (uint32_t i = 1; i < n; i++) {
+ a[i][h] = a[i - 1][h] * x[h];
+ }
+ }
+#if DEBUG_STRATEGY
+ ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).string());
+#endif
+
+ // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
+ float q[n][m]; // orthonormal basis, column-major order
+ float r[n][n]; // upper triangular matrix, row-major order
+ for (uint32_t j = 0; j < n; j++) {
+ for (uint32_t h = 0; h < m; h++) {
+ q[j][h] = a[j][h];
+ }
+ for (uint32_t i = 0; i < j; i++) {
+ float dot = vectorDot(&q[j][0], &q[i][0], m);
+ for (uint32_t h = 0; h < m; h++) {
+ q[j][h] -= dot * q[i][h];
+ }
+ }
+
+ float norm = vectorNorm(&q[j][0], m);
+ if (norm < 0.000001f) {
+ // vectors are linearly dependent or zero so no solution
+#if DEBUG_STRATEGY
+ ALOGD(" - no solution, norm=%f", norm);
+#endif
+ return false;
+ }
+
+ float invNorm = 1.0f / norm;
+ for (uint32_t h = 0; h < m; h++) {
+ q[j][h] *= invNorm;
+ }
+ for (uint32_t i = 0; i < n; i++) {
+ r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
+ }
+ }
+#if DEBUG_STRATEGY
+ ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).string());
+ ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).string());
+
+ // calculate QR, if we factored A correctly then QR should equal A
+ float qr[n][m];
+ for (uint32_t h = 0; h < m; h++) {
+ for (uint32_t i = 0; i < n; i++) {
+ qr[i][h] = 0;
+ for (uint32_t j = 0; j < n; j++) {
+ qr[i][h] += q[j][h] * r[j][i];
+ }
+ }
+ }
+ ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).string());
+#endif
+
+ // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
+ // We just work from bottom-right to top-left calculating B's coefficients.
+ float wy[m];
+ for (uint32_t h = 0; h < m; h++) {
+ wy[h] = y[h] * w[h];
+ }
+ for (uint32_t i = n; i-- != 0; ) {
+ outB[i] = vectorDot(&q[i][0], wy, m);
+ for (uint32_t j = n - 1; j > i; j--) {
+ outB[i] -= r[i][j] * outB[j];
+ }
+ outB[i] /= r[i][i];
+ }
+#if DEBUG_STRATEGY
+ ALOGD(" - b=%s", vectorToString(outB, n).string());
+#endif
+
+ // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
+ // SSerr is the residual sum of squares (variance of the error),
+ // and SStot is the total sum of squares (variance of the data) where each
+ // has been weighted.
+ float ymean = 0;
+ for (uint32_t h = 0; h < m; h++) {
+ ymean += y[h];
+ }
+ ymean /= m;
+
+ float sserr = 0;
+ float sstot = 0;
+ for (uint32_t h = 0; h < m; h++) {
+ float err = y[h] - outB[0];
+ float term = 1;
+ for (uint32_t i = 1; i < n; i++) {
+ term *= x[h];
+ err -= term * outB[i];
+ }
+ sserr += w[h] * w[h] * err * err;
+ float var = y[h] - ymean;
+ sstot += w[h] * w[h] * var * var;
+ }
+ *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
+#if DEBUG_STRATEGY
+ ALOGD(" - sserr=%f", sserr);
+ ALOGD(" - sstot=%f", sstot);
+ ALOGD(" - det=%f", *outDet);
+#endif
+ return true;
+}
+
+bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
+ VelocityTracker::Estimator* outEstimator) const {
+ outEstimator->clear();
+
+ // Iterate over movement samples in reverse time order and collect samples.
+ float x[HISTORY_SIZE];
+ float y[HISTORY_SIZE];
+ float w[HISTORY_SIZE];
+ float time[HISTORY_SIZE];
+ uint32_t m = 0;
+ uint32_t index = mIndex;
+ const Movement& newestMovement = mMovements[mIndex];
+ do {
+ const Movement& movement = mMovements[index];
+ if (!movement.idBits.hasBit(id)) {
+ break;
+ }
+
+ nsecs_t age = newestMovement.eventTime - movement.eventTime;
+ if (age > HORIZON) {
+ break;
+ }
+
+ const VelocityTracker::Position& position = movement.getPosition(id);
+ x[m] = position.x;
+ y[m] = position.y;
+ w[m] = chooseWeight(index);
+ time[m] = -age * 0.000000001f;
+ index = (index == 0 ? HISTORY_SIZE : index) - 1;
+ } while (++m < HISTORY_SIZE);
+
+ if (m == 0) {
+ return false; // no data
+ }
+
+ // Calculate a least squares polynomial fit.
+ uint32_t degree = mDegree;
+ if (degree > m - 1) {
+ degree = m - 1;
+ }
+ if (degree >= 1) {
+ float xdet, ydet;
+ uint32_t n = degree + 1;
+ if (solveLeastSquares(time, x, w, m, n, outEstimator->xCoeff, &xdet)
+ && solveLeastSquares(time, y, w, m, n, outEstimator->yCoeff, &ydet)) {
+ outEstimator->time = newestMovement.eventTime;
+ outEstimator->degree = degree;
+ outEstimator->confidence = xdet * ydet;
+#if DEBUG_STRATEGY
+ ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f",
+ int(outEstimator->degree),
+ vectorToString(outEstimator->xCoeff, n).string(),
+ vectorToString(outEstimator->yCoeff, n).string(),
+ outEstimator->confidence);
+#endif
+ return true;
+ }
+ }
+
+ // No velocity data available for this pointer, but we do have its current position.
+ outEstimator->xCoeff[0] = x[0];
+ outEstimator->yCoeff[0] = y[0];
+ outEstimator->time = newestMovement.eventTime;
+ outEstimator->degree = 0;
+ outEstimator->confidence = 1;
+ return true;
+}
+
+float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const {
+ switch (mWeighting) {
+ case WEIGHTING_DELTA: {
+ // Weight points based on how much time elapsed between them and the next
+ // point so that points that "cover" a shorter time span are weighed less.
+ // delta 0ms: 0.5
+ // delta 10ms: 1.0
+ if (index == mIndex) {
+ return 1.0f;
+ }
+ uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
+ float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime)
+ * 0.000001f;
+ if (deltaMillis < 0) {
+ return 0.5f;
+ }
+ if (deltaMillis < 10) {
+ return 0.5f + deltaMillis * 0.05;
+ }
+ return 1.0f;
+ }
+
+ case WEIGHTING_CENTRAL: {
+ // Weight points based on their age, weighing very recent and very old points less.
+ // age 0ms: 0.5
+ // age 10ms: 1.0
+ // age 50ms: 1.0
+ // age 60ms: 0.5
+ float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
+ * 0.000001f;
+ if (ageMillis < 0) {
+ return 0.5f;
+ }
+ if (ageMillis < 10) {
+ return 0.5f + ageMillis * 0.05;
+ }
+ if (ageMillis < 50) {
+ return 1.0f;
+ }
+ if (ageMillis < 60) {
+ return 0.5f + (60 - ageMillis) * 0.05;
+ }
+ return 0.5f;
+ }
+
+ case WEIGHTING_RECENT: {
+ // Weight points based on their age, weighing older points less.
+ // age 0ms: 1.0
+ // age 50ms: 1.0
+ // age 100ms: 0.5
+ float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
+ * 0.000001f;
+ if (ageMillis < 50) {
+ return 1.0f;
+ }
+ if (ageMillis < 100) {
+ return 0.5f + (100 - ageMillis) * 0.01f;
+ }
+ return 0.5f;
+ }
+
+ case WEIGHTING_NONE:
+ default:
+ return 1.0f;
+ }
+}
+
+
+// --- IntegratingVelocityTrackerStrategy ---
+
+IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
+ mDegree(degree) {
+}
+
+IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
+}
+
+void IntegratingVelocityTrackerStrategy::clear() {
+ mPointerIdBits.clear();
+}
+
+void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
+ mPointerIdBits.value &= ~idBits.value;
+}
+
+void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions) {
+ uint32_t index = 0;
+ for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) {
+ uint32_t id = iterIdBits.clearFirstMarkedBit();
+ State& state = mPointerState[id];
+ const VelocityTracker::Position& position = positions[index++];
+ if (mPointerIdBits.hasBit(id)) {
+ updateState(state, eventTime, position.x, position.y);
+ } else {
+ initState(state, eventTime, position.x, position.y);
+ }
+ }
+
+ mPointerIdBits = idBits;
+}
+
+bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id,
+ VelocityTracker::Estimator* outEstimator) const {
+ outEstimator->clear();
+
+ if (mPointerIdBits.hasBit(id)) {
+ const State& state = mPointerState[id];
+ populateEstimator(state, outEstimator);
+ return true;
+ }
+
+ return false;
+}
+
+void IntegratingVelocityTrackerStrategy::initState(State& state,
+ nsecs_t eventTime, float xpos, float ypos) const {
+ state.updateTime = eventTime;
+ state.degree = 0;
+
+ state.xpos = xpos;
+ state.xvel = 0;
+ state.xaccel = 0;
+ state.ypos = ypos;
+ state.yvel = 0;
+ state.yaccel = 0;
+}
+
+void IntegratingVelocityTrackerStrategy::updateState(State& state,
+ nsecs_t eventTime, float xpos, float ypos) const {
+ const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS;
+ const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
+
+ if (eventTime <= state.updateTime + MIN_TIME_DELTA) {
+ return;
+ }
+
+ float dt = (eventTime - state.updateTime) * 0.000000001f;
+ state.updateTime = eventTime;
+
+ float xvel = (xpos - state.xpos) / dt;
+ float yvel = (ypos - state.ypos) / dt;
+ if (state.degree == 0) {
+ state.xvel = xvel;
+ state.yvel = yvel;
+ state.degree = 1;
+ } else {
+ float alpha = dt / (FILTER_TIME_CONSTANT + dt);
+ if (mDegree == 1) {
+ state.xvel += (xvel - state.xvel) * alpha;
+ state.yvel += (yvel - state.yvel) * alpha;
+ } else {
+ float xaccel = (xvel - state.xvel) / dt;
+ float yaccel = (yvel - state.yvel) / dt;
+ if (state.degree == 1) {
+ state.xaccel = xaccel;
+ state.yaccel = yaccel;
+ state.degree = 2;
+ } else {
+ state.xaccel += (xaccel - state.xaccel) * alpha;
+ state.yaccel += (yaccel - state.yaccel) * alpha;
+ }
+ state.xvel += (state.xaccel * dt) * alpha;
+ state.yvel += (state.yaccel * dt) * alpha;
+ }
+ }
+ state.xpos = xpos;
+ state.ypos = ypos;
+}
+
+void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state,
+ VelocityTracker::Estimator* outEstimator) const {
+ outEstimator->time = state.updateTime;
+ outEstimator->confidence = 1.0f;
+ outEstimator->degree = state.degree;
+ outEstimator->xCoeff[0] = state.xpos;
+ outEstimator->xCoeff[1] = state.xvel;
+ outEstimator->xCoeff[2] = state.xaccel / 2;
+ outEstimator->yCoeff[0] = state.ypos;
+ outEstimator->yCoeff[1] = state.yvel;
+ outEstimator->yCoeff[2] = state.yaccel / 2;
+}
+
+
+// --- LegacyVelocityTrackerStrategy ---
+
+const nsecs_t LegacyVelocityTrackerStrategy::HORIZON;
+const uint32_t LegacyVelocityTrackerStrategy::HISTORY_SIZE;
+const nsecs_t LegacyVelocityTrackerStrategy::MIN_DURATION;
+
+LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {
+ clear();
+}
+
+LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
+}
+
+void LegacyVelocityTrackerStrategy::clear() {
+ mIndex = 0;
+ mMovements[0].idBits.clear();
+}
+
+void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
+ BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
+ mMovements[mIndex].idBits = remainingIdBits;
+}
+
+void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions) {
+ if (++mIndex == HISTORY_SIZE) {
+ mIndex = 0;
+ }
+
+ Movement& movement = mMovements[mIndex];
+ movement.eventTime = eventTime;
+ movement.idBits = idBits;
+ uint32_t count = idBits.count();
+ for (uint32_t i = 0; i < count; i++) {
+ movement.positions[i] = positions[i];
+ }
+}
+
+bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id,
+ VelocityTracker::Estimator* outEstimator) const {
+ outEstimator->clear();
+
+ const Movement& newestMovement = mMovements[mIndex];
+ if (!newestMovement.idBits.hasBit(id)) {
+ return false; // no data
+ }
+
+ // Find the oldest sample that contains the pointer and that is not older than HORIZON.
+ nsecs_t minTime = newestMovement.eventTime - HORIZON;
+ uint32_t oldestIndex = mIndex;
+ uint32_t numTouches = 1;
+ do {
+ uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
+ const Movement& nextOldestMovement = mMovements[nextOldestIndex];
+ if (!nextOldestMovement.idBits.hasBit(id)
+ || nextOldestMovement.eventTime < minTime) {
+ break;
+ }
+ oldestIndex = nextOldestIndex;
+ } while (++numTouches < HISTORY_SIZE);
+
+ // Calculate an exponentially weighted moving average of the velocity estimate
+ // at different points in time measured relative to the oldest sample.
+ // This is essentially an IIR filter. Newer samples are weighted more heavily
+ // than older samples. Samples at equal time points are weighted more or less
+ // equally.
+ //
+ // One tricky problem is that the sample data may be poorly conditioned.
+ // Sometimes samples arrive very close together in time which can cause us to
+ // overestimate the velocity at that time point. Most samples might be measured
+ // 16ms apart but some consecutive samples could be only 0.5sm apart because
+ // the hardware or driver reports them irregularly or in bursts.
+ float accumVx = 0;
+ float accumVy = 0;
+ uint32_t index = oldestIndex;
+ uint32_t samplesUsed = 0;
+ const Movement& oldestMovement = mMovements[oldestIndex];
+ const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id);
+ nsecs_t lastDuration = 0;
+
+ while (numTouches-- > 1) {
+ if (++index == HISTORY_SIZE) {
+ index = 0;
+ }
+ const Movement& movement = mMovements[index];
+ nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
+
+ // If the duration between samples is small, we may significantly overestimate
+ // the velocity. Consequently, we impose a minimum duration constraint on the
+ // samples that we include in the calculation.
+ if (duration >= MIN_DURATION) {
+ const VelocityTracker::Position& position = movement.getPosition(id);
+ float scale = 1000000000.0f / duration; // one over time delta in seconds
+ float vx = (position.x - oldestPosition.x) * scale;
+ float vy = (position.y - oldestPosition.y) * scale;
+ accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
+ accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
+ lastDuration = duration;
+ samplesUsed += 1;
+ }
+ }
+
+ // Report velocity.
+ const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id);
+ outEstimator->time = newestMovement.eventTime;
+ outEstimator->confidence = 1;
+ outEstimator->xCoeff[0] = newestPosition.x;
+ outEstimator->yCoeff[0] = newestPosition.y;
+ if (samplesUsed) {
+ outEstimator->xCoeff[1] = accumVx;
+ outEstimator->yCoeff[1] = accumVy;
+ outEstimator->degree = 1;
+ } else {
+ outEstimator->degree = 0;
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/VelocityTracker.h b/widget/gonk/libui/VelocityTracker.h
new file mode 100644
index 000000000..fd077d438
--- /dev/null
+++ b/widget/gonk/libui/VelocityTracker.h
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_VELOCITY_TRACKER_H
+#define _ANDROIDFW_VELOCITY_TRACKER_H
+
+#include "Input.h"
+#include <utils/Timers.h>
+#include <utils/BitSet.h>
+
+namespace android {
+
+class VelocityTrackerStrategy;
+
+/*
+ * Calculates the velocity of pointer movements over time.
+ */
+class VelocityTracker {
+public:
+ struct Position {
+ float x, y;
+ };
+
+ struct Estimator {
+ static const size_t MAX_DEGREE = 4;
+
+ // Estimator time base.
+ nsecs_t time;
+
+ // Polynomial coefficients describing motion in X and Y.
+ float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1];
+
+ // Polynomial degree (number of coefficients), or zero if no information is
+ // available.
+ uint32_t degree;
+
+ // Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
+ float confidence;
+
+ inline void clear() {
+ time = 0;
+ degree = 0;
+ confidence = 0;
+ for (size_t i = 0; i <= MAX_DEGREE; i++) {
+ xCoeff[i] = 0;
+ yCoeff[i] = 0;
+ }
+ }
+ };
+
+ // Creates a velocity tracker using the specified strategy.
+ // If strategy is NULL, uses the default strategy for the platform.
+ VelocityTracker(const char* strategy = NULL);
+
+ ~VelocityTracker();
+
+ // Resets the velocity tracker state.
+ void clear();
+
+ // Resets the velocity tracker state for specific pointers.
+ // Call this method when some pointers have changed and may be reusing
+ // an id that was assigned to a different pointer earlier.
+ void clearPointers(BitSet32 idBits);
+
+ // Adds movement information for a set of pointers.
+ // The idBits bitfield specifies the pointer ids of the pointers whose positions
+ // are included in the movement.
+ // The positions array contains position information for each pointer in order by
+ // increasing id. Its size should be equal to the number of one bits in idBits.
+ void addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions);
+
+ // Adds movement information for all pointers in a MotionEvent, including historical samples.
+ void addMovement(const MotionEvent* event);
+
+ // Gets the velocity of the specified pointer id in position units per second.
+ // Returns false and sets the velocity components to zero if there is
+ // insufficient movement information for the pointer.
+ bool getVelocity(uint32_t id, float* outVx, float* outVy) const;
+
+ // Gets an estimator for the recent movements of the specified pointer id.
+ // Returns false and clears the estimator if there is no information available
+ // about the pointer.
+ bool getEstimator(uint32_t id, Estimator* outEstimator) const;
+
+ // Gets the active pointer id, or -1 if none.
+ inline int32_t getActivePointerId() const { return mActivePointerId; }
+
+ // Gets a bitset containing all pointer ids from the most recent movement.
+ inline BitSet32 getCurrentPointerIdBits() const { return mCurrentPointerIdBits; }
+
+private:
+ static const char* DEFAULT_STRATEGY;
+
+ nsecs_t mLastEventTime;
+ BitSet32 mCurrentPointerIdBits;
+ int32_t mActivePointerId;
+ VelocityTrackerStrategy* mStrategy;
+
+ bool configureStrategy(const char* strategy);
+
+ static VelocityTrackerStrategy* createStrategy(const char* strategy);
+};
+
+
+/*
+ * Implements a particular velocity tracker algorithm.
+ */
+class VelocityTrackerStrategy {
+protected:
+ VelocityTrackerStrategy() { }
+
+public:
+ virtual ~VelocityTrackerStrategy() { }
+
+ virtual void clear() = 0;
+ virtual void clearPointers(BitSet32 idBits) = 0;
+ virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions) = 0;
+ virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0;
+};
+
+
+/*
+ * Velocity tracker algorithm based on least-squares linear regression.
+ */
+class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
+public:
+ enum Weighting {
+ // No weights applied. All data points are equally reliable.
+ WEIGHTING_NONE,
+
+ // Weight by time delta. Data points clustered together are weighted less.
+ WEIGHTING_DELTA,
+
+ // Weight such that points within a certain horizon are weighed more than those
+ // outside of that horizon.
+ WEIGHTING_CENTRAL,
+
+ // Weight such that points older than a certain amount are weighed less.
+ WEIGHTING_RECENT,
+ };
+
+ // Degree must be no greater than Estimator::MAX_DEGREE.
+ LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = WEIGHTING_NONE);
+ virtual ~LeastSquaresVelocityTrackerStrategy();
+
+ virtual void clear();
+ virtual void clearPointers(BitSet32 idBits);
+ virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions);
+ virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+
+private:
+ // Sample horizon.
+ // We don't use too much history by default since we want to react to quick
+ // changes in direction.
+ static const nsecs_t HORIZON = 100 * 1000000; // 100 ms
+
+ // Number of samples to keep.
+ static const uint32_t HISTORY_SIZE = 20;
+
+ struct Movement {
+ nsecs_t eventTime;
+ BitSet32 idBits;
+ VelocityTracker::Position positions[MAX_POINTERS];
+
+ inline const VelocityTracker::Position& getPosition(uint32_t id) const {
+ return positions[idBits.getIndexOfBit(id)];
+ }
+ };
+
+ float chooseWeight(uint32_t index) const;
+
+ const uint32_t mDegree;
+ const Weighting mWeighting;
+ uint32_t mIndex;
+ Movement mMovements[HISTORY_SIZE];
+};
+
+
+/*
+ * Velocity tracker algorithm that uses an IIR filter.
+ */
+class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy {
+public:
+ // Degree must be 1 or 2.
+ IntegratingVelocityTrackerStrategy(uint32_t degree);
+ ~IntegratingVelocityTrackerStrategy();
+
+ virtual void clear();
+ virtual void clearPointers(BitSet32 idBits);
+ virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions);
+ virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+
+private:
+ // Current state estimate for a particular pointer.
+ struct State {
+ nsecs_t updateTime;
+ uint32_t degree;
+
+ float xpos, xvel, xaccel;
+ float ypos, yvel, yaccel;
+ };
+
+ const uint32_t mDegree;
+ BitSet32 mPointerIdBits;
+ State mPointerState[MAX_POINTER_ID + 1];
+
+ void initState(State& state, nsecs_t eventTime, float xpos, float ypos) const;
+ void updateState(State& state, nsecs_t eventTime, float xpos, float ypos) const;
+ void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const;
+};
+
+
+/*
+ * Velocity tracker strategy used prior to ICS.
+ */
+class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy {
+public:
+ LegacyVelocityTrackerStrategy();
+ virtual ~LegacyVelocityTrackerStrategy();
+
+ virtual void clear();
+ virtual void clearPointers(BitSet32 idBits);
+ virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions);
+ virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+
+private:
+ // Oldest sample to consider when calculating the velocity.
+ static const nsecs_t HORIZON = 200 * 1000000; // 100 ms
+
+ // Number of samples to keep.
+ static const uint32_t HISTORY_SIZE = 20;
+
+ // The minimum duration between samples when estimating velocity.
+ static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
+
+ struct Movement {
+ nsecs_t eventTime;
+ BitSet32 idBits;
+ VelocityTracker::Position positions[MAX_POINTERS];
+
+ inline const VelocityTracker::Position& getPosition(uint32_t id) const {
+ return positions[idBits.getIndexOfBit(id)];
+ }
+ };
+
+ uint32_t mIndex;
+ Movement mMovements[HISTORY_SIZE];
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_VELOCITY_TRACKER_H
diff --git a/widget/gonk/libui/VirtualKeyMap.cpp b/widget/gonk/libui/VirtualKeyMap.cpp
new file mode 100644
index 000000000..444ab3718
--- /dev/null
+++ b/widget/gonk/libui/VirtualKeyMap.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "VirtualKeyMap"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include "VirtualKeyMap.h"
+#include <utils/Errors.h>
+#include "Tokenizer.h"
+#include <utils/Timers.h>
+
+// Enables debug output for the parser.
+#define DEBUG_PARSER 0
+
+// Enables debug output for parser performance.
+#define DEBUG_PARSER_PERFORMANCE 0
+
+
+namespace android {
+
+static const char* WHITESPACE = " \t\r";
+static const char* WHITESPACE_OR_FIELD_DELIMITER = " \t\r:";
+
+
+// --- VirtualKeyMap ---
+
+VirtualKeyMap::VirtualKeyMap() {
+}
+
+VirtualKeyMap::~VirtualKeyMap() {
+}
+
+status_t VirtualKeyMap::load(const String8& filename, VirtualKeyMap** outMap) {
+ *outMap = NULL;
+
+ Tokenizer* tokenizer;
+ status_t status = Tokenizer::open(filename, &tokenizer);
+ if (status) {
+ ALOGE("Error %d opening virtual key map file %s.", status, filename.string());
+ } else {
+ VirtualKeyMap* map = new VirtualKeyMap();
+ if (!map) {
+ ALOGE("Error allocating virtual key map.");
+ status = NO_MEMORY;
+ } else {
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+#endif
+ Parser parser(map, tokenizer);
+ status = parser.parse();
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
+ ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",
+ tokenizer->getFilename().string(), tokenizer->getLineNumber(),
+ elapsedTime / 1000000.0);
+#endif
+ if (status) {
+ delete map;
+ } else {
+ *outMap = map;
+ }
+ }
+ delete tokenizer;
+ }
+ return status;
+}
+
+
+// --- VirtualKeyMap::Parser ---
+
+VirtualKeyMap::Parser::Parser(VirtualKeyMap* map, Tokenizer* tokenizer) :
+ mMap(map), mTokenizer(tokenizer) {
+}
+
+VirtualKeyMap::Parser::~Parser() {
+}
+
+status_t VirtualKeyMap::Parser::parse() {
+ while (!mTokenizer->isEof()) {
+#if DEBUG_PARSER
+ ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+#endif
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ // Multiple keys can appear on one line or they can be broken up across multiple lines.
+ do {
+ String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);
+ if (token != "0x01") {
+ ALOGE("%s: Unknown virtual key type, expected 0x01.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ VirtualKeyDefinition defn;
+ bool success = parseNextIntField(&defn.scanCode)
+ && parseNextIntField(&defn.centerX)
+ && parseNextIntField(&defn.centerY)
+ && parseNextIntField(&defn.width)
+ && parseNextIntField(&defn.height);
+ if (!success) {
+ ALOGE("%s: Expected 5 colon-delimited integers in virtual key definition.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed virtual key: scanCode=%d, centerX=%d, centerY=%d, "
+ "width=%d, height=%d",
+ defn.scanCode, defn.centerX, defn.centerY, defn.width, defn.height);
+#endif
+ mMap->mVirtualKeys.push(defn);
+ } while (consumeFieldDelimiterAndSkipWhitespace());
+
+ if (!mTokenizer->isEol()) {
+ ALOGE("%s: Expected end of line, got '%s'.",
+ mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+ return BAD_VALUE;
+ }
+ }
+
+ mTokenizer->nextLine();
+ }
+
+ return NO_ERROR;
+}
+
+bool VirtualKeyMap::Parser::consumeFieldDelimiterAndSkipWhitespace() {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (mTokenizer->peekChar() == ':') {
+ mTokenizer->nextChar();
+ mTokenizer->skipDelimiters(WHITESPACE);
+ return true;
+ }
+ return false;
+}
+
+bool VirtualKeyMap::Parser::parseNextIntField(int32_t* outValue) {
+ if (!consumeFieldDelimiterAndSkipWhitespace()) {
+ return false;
+ }
+
+ String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);
+ char* end;
+ *outValue = strtol(token.string(), &end, 0);
+ if (token.isEmpty() || *end != '\0') {
+ ALOGE("Expected an integer, got '%s'.", token.string());
+ return false;
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/VirtualKeyMap.h b/widget/gonk/libui/VirtualKeyMap.h
new file mode 100644
index 000000000..79d61a536
--- /dev/null
+++ b/widget/gonk/libui/VirtualKeyMap.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROIDFW_VIRTUAL_KEY_MAP_H
+#define _ANDROIDFW_VIRTUAL_KEY_MAP_H
+
+#include <stdint.h>
+
+#include "Input.h"
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include "Tokenizer.h"
+#include <utils/String8.h>
+#include <utils/Unicode.h>
+
+namespace android {
+
+/* Describes a virtual key. */
+struct VirtualKeyDefinition {
+ int32_t scanCode;
+
+ // configured position data, specified in display coords
+ int32_t centerX;
+ int32_t centerY;
+ int32_t width;
+ int32_t height;
+};
+
+
+/**
+ * Describes a collection of virtual keys on a touch screen in terms of
+ * virtual scan codes and hit rectangles.
+ *
+ * This object is immutable after it has been loaded.
+ */
+class VirtualKeyMap {
+public:
+ ~VirtualKeyMap();
+
+ static status_t load(const String8& filename, VirtualKeyMap** outMap);
+
+ inline const Vector<VirtualKeyDefinition>& getVirtualKeys() const {
+ return mVirtualKeys;
+ }
+
+private:
+ class Parser {
+ VirtualKeyMap* mMap;
+ Tokenizer* mTokenizer;
+
+ public:
+ Parser(VirtualKeyMap* map, Tokenizer* tokenizer);
+ ~Parser();
+ status_t parse();
+
+ private:
+ bool consumeFieldDelimiterAndSkipWhitespace();
+ bool parseNextIntField(int32_t* outValue);
+ };
+
+ Vector<VirtualKeyDefinition> mVirtualKeys;
+
+ VirtualKeyMap();
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_KEY_CHARACTER_MAP_H
diff --git a/widget/gonk/libui/android_input.h b/widget/gonk/libui/android_input.h
new file mode 100644
index 000000000..00e81b28d
--- /dev/null
+++ b/widget/gonk/libui/android_input.h
@@ -0,0 +1,850 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_INPUT_H
+#define _ANDROID_INPUT_H
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ * This file is part of Android's set of stable system headers
+ * exposed by the Android NDK (Native Development Kit).
+ *
+ * Third-party source AND binary code relies on the definitions
+ * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+/*
+ * Structures and functions to receive and process input events in
+ * native code.
+ *
+ * NOTE: These functions MUST be implemented by /system/lib/libui.so
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+#include "android_keycodes.h"
+#include <android/looper.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Key states (may be returned by queries about the current state of a
+ * particular key code, scan code or switch).
+ */
+enum {
+ /* The key state is unknown or the requested key itself is not supported. */
+ AKEY_STATE_UNKNOWN = -1,
+
+ /* The key is up. */
+ AKEY_STATE_UP = 0,
+
+ /* The key is down. */
+ AKEY_STATE_DOWN = 1,
+
+ /* The key is down but is a virtual key press that is being emulated by the system. */
+ AKEY_STATE_VIRTUAL = 2
+};
+
+/*
+ * Meta key / modifer state.
+ */
+enum {
+ /* No meta keys are pressed. */
+ AMETA_NONE = 0,
+
+ /* This mask is used to check whether one of the ALT meta keys is pressed. */
+ AMETA_ALT_ON = 0x02,
+
+ /* This mask is used to check whether the left ALT meta key is pressed. */
+ AMETA_ALT_LEFT_ON = 0x10,
+
+ /* This mask is used to check whether the right ALT meta key is pressed. */
+ AMETA_ALT_RIGHT_ON = 0x20,
+
+ /* This mask is used to check whether one of the SHIFT meta keys is pressed. */
+ AMETA_SHIFT_ON = 0x01,
+
+ /* This mask is used to check whether the left SHIFT meta key is pressed. */
+ AMETA_SHIFT_LEFT_ON = 0x40,
+
+ /* This mask is used to check whether the right SHIFT meta key is pressed. */
+ AMETA_SHIFT_RIGHT_ON = 0x80,
+
+ /* This mask is used to check whether the SYM meta key is pressed. */
+ AMETA_SYM_ON = 0x04,
+
+ /* This mask is used to check whether the FUNCTION meta key is pressed. */
+ AMETA_FUNCTION_ON = 0x08,
+
+ /* This mask is used to check whether one of the CTRL meta keys is pressed. */
+ AMETA_CTRL_ON = 0x1000,
+
+ /* This mask is used to check whether the left CTRL meta key is pressed. */
+ AMETA_CTRL_LEFT_ON = 0x2000,
+
+ /* This mask is used to check whether the right CTRL meta key is pressed. */
+ AMETA_CTRL_RIGHT_ON = 0x4000,
+
+ /* This mask is used to check whether one of the META meta keys is pressed. */
+ AMETA_META_ON = 0x10000,
+
+ /* This mask is used to check whether the left META meta key is pressed. */
+ AMETA_META_LEFT_ON = 0x20000,
+
+ /* This mask is used to check whether the right META meta key is pressed. */
+ AMETA_META_RIGHT_ON = 0x40000,
+
+ /* This mask is used to check whether the CAPS LOCK meta key is on. */
+ AMETA_CAPS_LOCK_ON = 0x100000,
+
+ /* This mask is used to check whether the NUM LOCK meta key is on. */
+ AMETA_NUM_LOCK_ON = 0x200000,
+
+ /* This mask is used to check whether the SCROLL LOCK meta key is on. */
+ AMETA_SCROLL_LOCK_ON = 0x400000,
+};
+
+/*
+ * Input events.
+ *
+ * Input events are opaque structures. Use the provided accessors functions to
+ * read their properties.
+ */
+struct AInputEvent;
+typedef struct AInputEvent AInputEvent;
+
+/*
+ * Input event types.
+ */
+enum {
+ /* Indicates that the input event is a key event. */
+ AINPUT_EVENT_TYPE_KEY = 1,
+
+ /* Indicates that the input event is a motion event. */
+ AINPUT_EVENT_TYPE_MOTION = 2
+};
+
+/*
+ * Key event actions.
+ */
+enum {
+ /* The key has been pressed down. */
+ AKEY_EVENT_ACTION_DOWN = 0,
+
+ /* The key has been released. */
+ AKEY_EVENT_ACTION_UP = 1,
+
+ /* Multiple duplicate key events have occurred in a row, or a complex string is
+ * being delivered. The repeat_count property of the key event contains the number
+ * of times the given key code should be executed.
+ */
+ AKEY_EVENT_ACTION_MULTIPLE = 2
+};
+
+/*
+ * Key event flags.
+ */
+enum {
+ /* This mask is set if the device woke because of this key event. */
+ AKEY_EVENT_FLAG_WOKE_HERE = 0x1,
+
+ /* This mask is set if the key event was generated by a software keyboard. */
+ AKEY_EVENT_FLAG_SOFT_KEYBOARD = 0x2,
+
+ /* This mask is set if we don't want the key event to cause us to leave touch mode. */
+ AKEY_EVENT_FLAG_KEEP_TOUCH_MODE = 0x4,
+
+ /* This mask is set if an event was known to come from a trusted part
+ * of the system. That is, the event is known to come from the user,
+ * and could not have been spoofed by a third party component. */
+ AKEY_EVENT_FLAG_FROM_SYSTEM = 0x8,
+
+ /* This mask is used for compatibility, to identify enter keys that are
+ * coming from an IME whose enter key has been auto-labelled "next" or
+ * "done". This allows TextView to dispatch these as normal enter keys
+ * for old applications, but still do the appropriate action when
+ * receiving them. */
+ AKEY_EVENT_FLAG_EDITOR_ACTION = 0x10,
+
+ /* When associated with up key events, this indicates that the key press
+ * has been canceled. Typically this is used with virtual touch screen
+ * keys, where the user can slide from the virtual key area on to the
+ * display: in that case, the application will receive a canceled up
+ * event and should not perform the action normally associated with the
+ * key. Note that for this to work, the application can not perform an
+ * action for a key until it receives an up or the long press timeout has
+ * expired. */
+ AKEY_EVENT_FLAG_CANCELED = 0x20,
+
+ /* This key event was generated by a virtual (on-screen) hard key area.
+ * Typically this is an area of the touchscreen, outside of the regular
+ * display, dedicated to "hardware" buttons. */
+ AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY = 0x40,
+
+ /* This flag is set for the first key repeat that occurs after the
+ * long press timeout. */
+ AKEY_EVENT_FLAG_LONG_PRESS = 0x80,
+
+ /* Set when a key event has AKEY_EVENT_FLAG_CANCELED set because a long
+ * press action was executed while it was down. */
+ AKEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100,
+
+ /* Set for AKEY_EVENT_ACTION_UP when this event's key code is still being
+ * tracked from its initial down. That is, somebody requested that tracking
+ * started on the key down and a long press has not caused
+ * the tracking to be canceled. */
+ AKEY_EVENT_FLAG_TRACKING = 0x200,
+
+ /* Set when a key event has been synthesized to implement default behavior
+ * for an event that the application did not handle.
+ * Fallback key events are generated by unhandled trackball motions
+ * (to emulate a directional keypad) and by certain unhandled key presses
+ * that are declared in the key map (such as special function numeric keypad
+ * keys when numlock is off). */
+ AKEY_EVENT_FLAG_FALLBACK = 0x400,
+};
+
+/*
+ * Motion event actions.
+ */
+
+/* Bit shift for the action bits holding the pointer index as
+ * defined by AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
+ */
+#define AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8
+
+enum {
+ /* Bit mask of the parts of the action code that are the action itself.
+ */
+ AMOTION_EVENT_ACTION_MASK = 0xff,
+
+ /* Bits in the action code that represent a pointer index, used with
+ * AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP. Shifting
+ * down by AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
+ * index where the data for the pointer going up or down can be found.
+ */
+ AMOTION_EVENT_ACTION_POINTER_INDEX_MASK = 0xff00,
+
+ /* A pressed gesture has started, the motion contains the initial starting location.
+ */
+ AMOTION_EVENT_ACTION_DOWN = 0,
+
+ /* A pressed gesture has finished, the motion contains the final release location
+ * as well as any intermediate points since the last down or move event.
+ */
+ AMOTION_EVENT_ACTION_UP = 1,
+
+ /* A change has happened during a press gesture (between AMOTION_EVENT_ACTION_DOWN and
+ * AMOTION_EVENT_ACTION_UP). The motion contains the most recent point, as well as
+ * any intermediate points since the last down or move event.
+ */
+ AMOTION_EVENT_ACTION_MOVE = 2,
+
+ /* The current gesture has been aborted.
+ * You will not receive any more points in it. You should treat this as
+ * an up event, but not perform any action that you normally would.
+ */
+ AMOTION_EVENT_ACTION_CANCEL = 3,
+
+ /* A movement has happened outside of the normal bounds of the UI element.
+ * This does not provide a full gesture, but only the initial location of the movement/touch.
+ */
+ AMOTION_EVENT_ACTION_OUTSIDE = 4,
+
+ /* A non-primary pointer has gone down.
+ * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+ */
+ AMOTION_EVENT_ACTION_POINTER_DOWN = 5,
+
+ /* A non-primary pointer has gone up.
+ * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+ */
+ AMOTION_EVENT_ACTION_POINTER_UP = 6,
+
+ /* A change happened but the pointer is not down (unlike AMOTION_EVENT_ACTION_MOVE).
+ * The motion contains the most recent point, as well as any intermediate points since
+ * the last hover move event.
+ */
+ AMOTION_EVENT_ACTION_HOVER_MOVE = 7,
+
+ /* The motion event contains relative vertical and/or horizontal scroll offsets.
+ * Use getAxisValue to retrieve the information from AMOTION_EVENT_AXIS_VSCROLL
+ * and AMOTION_EVENT_AXIS_HSCROLL.
+ * The pointer may or may not be down when this event is dispatched.
+ * This action is always delivered to the winder under the pointer, which
+ * may not be the window currently touched.
+ */
+ AMOTION_EVENT_ACTION_SCROLL = 8,
+
+ /* The pointer is not down but has entered the boundaries of a window or view.
+ */
+ AMOTION_EVENT_ACTION_HOVER_ENTER = 9,
+
+ /* The pointer is not down but has exited the boundaries of a window or view.
+ */
+ AMOTION_EVENT_ACTION_HOVER_EXIT = 10,
+};
+
+/*
+ * Motion event flags.
+ */
+enum {
+ /* This flag indicates that the window that received this motion event is partly
+ * or wholly obscured by another visible window above it. This flag is set to true
+ * even if the event did not directly pass through the obscured area.
+ * A security sensitive application can check this flag to identify situations in which
+ * a malicious application may have covered up part of its content for the purpose
+ * of misleading the user or hijacking touches. An appropriate response might be
+ * to drop the suspect touches or to take additional precautions to confirm the user's
+ * actual intent.
+ */
+ AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1,
+};
+
+/*
+ * Motion event edge touch flags.
+ */
+enum {
+ /* No edges intersected */
+ AMOTION_EVENT_EDGE_FLAG_NONE = 0,
+
+ /* Flag indicating the motion event intersected the top edge of the screen. */
+ AMOTION_EVENT_EDGE_FLAG_TOP = 0x01,
+
+ /* Flag indicating the motion event intersected the bottom edge of the screen. */
+ AMOTION_EVENT_EDGE_FLAG_BOTTOM = 0x02,
+
+ /* Flag indicating the motion event intersected the left edge of the screen. */
+ AMOTION_EVENT_EDGE_FLAG_LEFT = 0x04,
+
+ /* Flag indicating the motion event intersected the right edge of the screen. */
+ AMOTION_EVENT_EDGE_FLAG_RIGHT = 0x08
+};
+
+/*
+ * Constants that identify each individual axis of a motion event.
+ * Refer to the documentation on the MotionEvent class for descriptions of each axis.
+ */
+enum {
+ AMOTION_EVENT_AXIS_X = 0,
+ AMOTION_EVENT_AXIS_Y = 1,
+ AMOTION_EVENT_AXIS_PRESSURE = 2,
+ AMOTION_EVENT_AXIS_SIZE = 3,
+ AMOTION_EVENT_AXIS_TOUCH_MAJOR = 4,
+ AMOTION_EVENT_AXIS_TOUCH_MINOR = 5,
+ AMOTION_EVENT_AXIS_TOOL_MAJOR = 6,
+ AMOTION_EVENT_AXIS_TOOL_MINOR = 7,
+ AMOTION_EVENT_AXIS_ORIENTATION = 8,
+ AMOTION_EVENT_AXIS_VSCROLL = 9,
+ AMOTION_EVENT_AXIS_HSCROLL = 10,
+ AMOTION_EVENT_AXIS_Z = 11,
+ AMOTION_EVENT_AXIS_RX = 12,
+ AMOTION_EVENT_AXIS_RY = 13,
+ AMOTION_EVENT_AXIS_RZ = 14,
+ AMOTION_EVENT_AXIS_HAT_X = 15,
+ AMOTION_EVENT_AXIS_HAT_Y = 16,
+ AMOTION_EVENT_AXIS_LTRIGGER = 17,
+ AMOTION_EVENT_AXIS_RTRIGGER = 18,
+ AMOTION_EVENT_AXIS_THROTTLE = 19,
+ AMOTION_EVENT_AXIS_RUDDER = 20,
+ AMOTION_EVENT_AXIS_WHEEL = 21,
+ AMOTION_EVENT_AXIS_GAS = 22,
+ AMOTION_EVENT_AXIS_BRAKE = 23,
+ AMOTION_EVENT_AXIS_DISTANCE = 24,
+ AMOTION_EVENT_AXIS_TILT = 25,
+ AMOTION_EVENT_AXIS_GENERIC_1 = 32,
+ AMOTION_EVENT_AXIS_GENERIC_2 = 33,
+ AMOTION_EVENT_AXIS_GENERIC_3 = 34,
+ AMOTION_EVENT_AXIS_GENERIC_4 = 35,
+ AMOTION_EVENT_AXIS_GENERIC_5 = 36,
+ AMOTION_EVENT_AXIS_GENERIC_6 = 37,
+ AMOTION_EVENT_AXIS_GENERIC_7 = 38,
+ AMOTION_EVENT_AXIS_GENERIC_8 = 39,
+ AMOTION_EVENT_AXIS_GENERIC_9 = 40,
+ AMOTION_EVENT_AXIS_GENERIC_10 = 41,
+ AMOTION_EVENT_AXIS_GENERIC_11 = 42,
+ AMOTION_EVENT_AXIS_GENERIC_12 = 43,
+ AMOTION_EVENT_AXIS_GENERIC_13 = 44,
+ AMOTION_EVENT_AXIS_GENERIC_14 = 45,
+ AMOTION_EVENT_AXIS_GENERIC_15 = 46,
+ AMOTION_EVENT_AXIS_GENERIC_16 = 47,
+
+ // NOTE: If you add a new axis here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
+};
+
+/*
+ * Constants that identify buttons that are associated with motion events.
+ * Refer to the documentation on the MotionEvent class for descriptions of each button.
+ */
+enum {
+ AMOTION_EVENT_BUTTON_PRIMARY = 1 << 0,
+ AMOTION_EVENT_BUTTON_SECONDARY = 1 << 1,
+ AMOTION_EVENT_BUTTON_TERTIARY = 1 << 2,
+ AMOTION_EVENT_BUTTON_BACK = 1 << 3,
+ AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
+};
+
+/*
+ * Constants that identify tool types.
+ * Refer to the documentation on the MotionEvent class for descriptions of each tool type.
+ */
+enum {
+ AMOTION_EVENT_TOOL_TYPE_UNKNOWN = 0,
+ AMOTION_EVENT_TOOL_TYPE_FINGER = 1,
+ AMOTION_EVENT_TOOL_TYPE_STYLUS = 2,
+ AMOTION_EVENT_TOOL_TYPE_MOUSE = 3,
+ AMOTION_EVENT_TOOL_TYPE_ERASER = 4,
+};
+
+/*
+ * Input sources.
+ *
+ * Refer to the documentation on android.view.InputDevice for more details about input sources
+ * and their correct interpretation.
+ */
+enum {
+ AINPUT_SOURCE_CLASS_MASK = 0x000000ff,
+
+ AINPUT_SOURCE_CLASS_NONE = 0x00000000,
+ AINPUT_SOURCE_CLASS_BUTTON = 0x00000001,
+ AINPUT_SOURCE_CLASS_POINTER = 0x00000002,
+ AINPUT_SOURCE_CLASS_NAVIGATION = 0x00000004,
+ AINPUT_SOURCE_CLASS_POSITION = 0x00000008,
+ AINPUT_SOURCE_CLASS_JOYSTICK = 0x00000010,
+};
+
+enum {
+ AINPUT_SOURCE_UNKNOWN = 0x00000000,
+
+ AINPUT_SOURCE_KEYBOARD = 0x00000100 | AINPUT_SOURCE_CLASS_BUTTON,
+ AINPUT_SOURCE_DPAD = 0x00000200 | AINPUT_SOURCE_CLASS_BUTTON,
+ AINPUT_SOURCE_GAMEPAD = 0x00000400 | AINPUT_SOURCE_CLASS_BUTTON,
+ AINPUT_SOURCE_TOUCHSCREEN = 0x00001000 | AINPUT_SOURCE_CLASS_POINTER,
+ AINPUT_SOURCE_MOUSE = 0x00002000 | AINPUT_SOURCE_CLASS_POINTER,
+ AINPUT_SOURCE_STYLUS = 0x00004000 | AINPUT_SOURCE_CLASS_POINTER,
+ AINPUT_SOURCE_TRACKBALL = 0x00010000 | AINPUT_SOURCE_CLASS_NAVIGATION,
+ AINPUT_SOURCE_TOUCHPAD = 0x00100000 | AINPUT_SOURCE_CLASS_POSITION,
+ AINPUT_SOURCE_TOUCH_NAVIGATION = 0x00200000 | AINPUT_SOURCE_CLASS_NONE,
+ AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
+
+ AINPUT_SOURCE_ANY = 0xffffff00,
+};
+
+/*
+ * Keyboard types.
+ *
+ * Refer to the documentation on android.view.InputDevice for more details.
+ */
+enum {
+ AINPUT_KEYBOARD_TYPE_NONE = 0,
+ AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC = 1,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC = 2,
+};
+
+/*
+ * Constants used to retrieve information about the range of motion for a particular
+ * coordinate of a motion event.
+ *
+ * Refer to the documentation on android.view.InputDevice for more details about input sources
+ * and their correct interpretation.
+ *
+ * DEPRECATION NOTICE: These constants are deprecated. Use AMOTION_EVENT_AXIS_* constants instead.
+ */
+enum {
+ AINPUT_MOTION_RANGE_X = AMOTION_EVENT_AXIS_X,
+ AINPUT_MOTION_RANGE_Y = AMOTION_EVENT_AXIS_Y,
+ AINPUT_MOTION_RANGE_PRESSURE = AMOTION_EVENT_AXIS_PRESSURE,
+ AINPUT_MOTION_RANGE_SIZE = AMOTION_EVENT_AXIS_SIZE,
+ AINPUT_MOTION_RANGE_TOUCH_MAJOR = AMOTION_EVENT_AXIS_TOUCH_MAJOR,
+ AINPUT_MOTION_RANGE_TOUCH_MINOR = AMOTION_EVENT_AXIS_TOUCH_MINOR,
+ AINPUT_MOTION_RANGE_TOOL_MAJOR = AMOTION_EVENT_AXIS_TOOL_MAJOR,
+ AINPUT_MOTION_RANGE_TOOL_MINOR = AMOTION_EVENT_AXIS_TOOL_MINOR,
+ AINPUT_MOTION_RANGE_ORIENTATION = AMOTION_EVENT_AXIS_ORIENTATION,
+} __attribute__ ((deprecated));
+
+
+/*
+ * Input event accessors.
+ *
+ * Note that most functions can only be used on input events that are of a given type.
+ * Calling these functions on input events of other types will yield undefined behavior.
+ */
+
+/*** Accessors for all input events. ***/
+
+/* Get the input event type. */
+int32_t AInputEvent_getType(const AInputEvent* event);
+
+/* Get the id for the device that an input event came from.
+ *
+ * Input events can be generated by multiple different input devices.
+ * Use the input device id to obtain information about the input
+ * device that was responsible for generating a particular event.
+ *
+ * An input device id of 0 indicates that the event didn't come from a physical device;
+ * other numbers are arbitrary and you shouldn't depend on the values.
+ * Use the provided input device query API to obtain information about input devices.
+ */
+int32_t AInputEvent_getDeviceId(const AInputEvent* event);
+
+/* Get the input event source. */
+int32_t AInputEvent_getSource(const AInputEvent* event);
+
+/*** Accessors for key events only. ***/
+
+/* Get the key event action. */
+int32_t AKeyEvent_getAction(const AInputEvent* key_event);
+
+/* Get the key event flags. */
+int32_t AKeyEvent_getFlags(const AInputEvent* key_event);
+
+/* Get the key code of the key event.
+ * This is the physical key that was pressed, not the Unicode character. */
+int32_t AKeyEvent_getKeyCode(const AInputEvent* key_event);
+
+/* Get the hardware key id of this key event.
+ * These values are not reliable and vary from device to device. */
+int32_t AKeyEvent_getScanCode(const AInputEvent* key_event);
+
+/* Get the meta key state. */
+int32_t AKeyEvent_getMetaState(const AInputEvent* key_event);
+
+/* Get the repeat count of the event.
+ * For both key up an key down events, this is the number of times the key has
+ * repeated with the first down starting at 0 and counting up from there. For
+ * multiple key events, this is the number of down/up pairs that have occurred. */
+int32_t AKeyEvent_getRepeatCount(const AInputEvent* key_event);
+
+/* Get the time of the most recent key down event, in the
+ * java.lang.System.nanoTime() time base. If this is a down event,
+ * this will be the same as eventTime.
+ * Note that when chording keys, this value is the down time of the most recently
+ * pressed key, which may not be the same physical key of this event. */
+int64_t AKeyEvent_getDownTime(const AInputEvent* key_event);
+
+/* Get the time this event occurred, in the
+ * java.lang.System.nanoTime() time base. */
+int64_t AKeyEvent_getEventTime(const AInputEvent* key_event);
+
+/*** Accessors for motion events only. ***/
+
+/* Get the combined motion event action code and pointer index. */
+int32_t AMotionEvent_getAction(const AInputEvent* motion_event);
+
+/* Get the motion event flags. */
+int32_t AMotionEvent_getFlags(const AInputEvent* motion_event);
+
+/* Get the state of any meta / modifier keys that were in effect when the
+ * event was generated. */
+int32_t AMotionEvent_getMetaState(const AInputEvent* motion_event);
+
+/* Get the button state of all buttons that are pressed. */
+int32_t AMotionEvent_getButtonState(const AInputEvent* motion_event);
+
+/* Get a bitfield indicating which edges, if any, were touched by this motion event.
+ * For touch events, clients can use this to determine if the user's finger was
+ * touching the edge of the display. */
+int32_t AMotionEvent_getEdgeFlags(const AInputEvent* motion_event);
+
+/* Get the time when the user originally pressed down to start a stream of
+ * position events, in the java.lang.System.nanoTime() time base. */
+int64_t AMotionEvent_getDownTime(const AInputEvent* motion_event);
+
+/* Get the time when this specific event was generated,
+ * in the java.lang.System.nanoTime() time base. */
+int64_t AMotionEvent_getEventTime(const AInputEvent* motion_event);
+
+/* Get the X coordinate offset.
+ * For touch events on the screen, this is the delta that was added to the raw
+ * screen coordinates to adjust for the absolute position of the containing windows
+ * and views. */
+float AMotionEvent_getXOffset(const AInputEvent* motion_event);
+
+/* Get the precision of the Y coordinates being reported.
+ * For touch events on the screen, this is the delta that was added to the raw
+ * screen coordinates to adjust for the absolute position of the containing windows
+ * and views. */
+float AMotionEvent_getYOffset(const AInputEvent* motion_event);
+
+/* Get the precision of the X coordinates being reported.
+ * You can multiply this number with an X coordinate sample to find the
+ * actual hardware value of the X coordinate. */
+float AMotionEvent_getXPrecision(const AInputEvent* motion_event);
+
+/* Get the precision of the Y coordinates being reported.
+ * You can multiply this number with a Y coordinate sample to find the
+ * actual hardware value of the Y coordinate. */
+float AMotionEvent_getYPrecision(const AInputEvent* motion_event);
+
+/* Get the number of pointers of data contained in this event.
+ * Always >= 1. */
+size_t AMotionEvent_getPointerCount(const AInputEvent* motion_event);
+
+/* Get the pointer identifier associated with a particular pointer
+ * data index in this event. The identifier tells you the actual pointer
+ * number associated with the data, accounting for individual pointers
+ * going up and down since the start of the current gesture. */
+int32_t AMotionEvent_getPointerId(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the tool type of a pointer for the given pointer index.
+ * The tool type indicates the type of tool used to make contact such as a
+ * finger or stylus, if known. */
+int32_t AMotionEvent_getToolType(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the original raw X coordinate of this event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views. */
+float AMotionEvent_getRawX(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the original raw X coordinate of this event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views. */
+float AMotionEvent_getRawY(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current X coordinate of this event for the given pointer index.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getX(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current Y coordinate of this event for the given pointer index.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getY(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current pressure of this event for the given pointer index.
+ * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
+ * although values higher than 1 may be generated depending on the calibration of
+ * the input device. */
+float AMotionEvent_getPressure(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current scaled value of the approximate size for the given pointer index.
+ * This represents some approximation of the area of the screen being
+ * pressed; the actual value in pixels corresponding to the
+ * touch is normalized with the device specific range of values
+ * and scaled to a value between 0 and 1. The value of size can be used to
+ * determine fat touch events. */
+float AMotionEvent_getSize(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current length of the major axis of an ellipse that describes the touch area
+ * at the point of contact for the given pointer index. */
+float AMotionEvent_getTouchMajor(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current length of the minor axis of an ellipse that describes the touch area
+ * at the point of contact for the given pointer index. */
+float AMotionEvent_getTouchMinor(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current length of the major axis of an ellipse that describes the size
+ * of the approaching tool for the given pointer index.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact. */
+float AMotionEvent_getToolMajor(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current length of the minor axis of an ellipse that describes the size
+ * of the approaching tool for the given pointer index.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact. */
+float AMotionEvent_getToolMinor(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current orientation of the touch area and tool area in radians clockwise from
+ * vertical for the given pointer index.
+ * An angle of 0 degrees indicates that the major axis of contact is oriented
+ * upwards, is perfectly circular or is of unknown orientation. A positive angle
+ * indicates that the major axis of contact is oriented to the right. A negative angle
+ * indicates that the major axis of contact is oriented to the left.
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+ * (finger pointing fully right). */
+float AMotionEvent_getOrientation(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the value of the request axis for the given pointer index. */
+float AMotionEvent_getAxisValue(const AInputEvent* motion_event,
+ int32_t axis, size_t pointer_index);
+
+/* Get the number of historical points in this event. These are movements that
+ * have occurred between this event and the previous event. This only applies
+ * to AMOTION_EVENT_ACTION_MOVE events -- all other actions will have a size of 0.
+ * Historical samples are indexed from oldest to newest. */
+size_t AMotionEvent_getHistorySize(const AInputEvent* motion_event);
+
+/* Get the time that a historical movement occurred between this event and
+ * the previous event, in the java.lang.System.nanoTime() time base. */
+int64_t AMotionEvent_getHistoricalEventTime(AInputEvent* motion_event,
+ size_t history_index);
+
+/* Get the historical raw X coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getHistoricalRawX(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical raw Y coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getHistoricalRawY(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical X coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getHistoricalX(AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical Y coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getHistoricalY(AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical pressure of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
+ * although values higher than 1 may be generated depending on the calibration of
+ * the input device. */
+float AMotionEvent_getHistoricalPressure(AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the current scaled value of the approximate size for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * This represents some approximation of the area of the screen being
+ * pressed; the actual value in pixels corresponding to the
+ * touch is normalized with the device specific range of values
+ * and scaled to a value between 0 and 1. The value of size can be used to
+ * determine fat touch events. */
+float AMotionEvent_getHistoricalSize(AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical length of the major axis of an ellipse that describes the touch area
+ * at the point of contact for the given pointer index that
+ * occurred between this event and the previous motion event. */
+float AMotionEvent_getHistoricalTouchMajor(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical length of the minor axis of an ellipse that describes the touch area
+ * at the point of contact for the given pointer index that
+ * occurred between this event and the previous motion event. */
+float AMotionEvent_getHistoricalTouchMinor(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical length of the major axis of an ellipse that describes the size
+ * of the approaching tool for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact. */
+float AMotionEvent_getHistoricalToolMajor(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical length of the minor axis of an ellipse that describes the size
+ * of the approaching tool for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact. */
+float AMotionEvent_getHistoricalToolMinor(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical orientation of the touch area and tool area in radians clockwise from
+ * vertical for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * An angle of 0 degrees indicates that the major axis of contact is oriented
+ * upwards, is perfectly circular or is of unknown orientation. A positive angle
+ * indicates that the major axis of contact is oriented to the right. A negative angle
+ * indicates that the major axis of contact is oriented to the left.
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+ * (finger pointing fully right). */
+float AMotionEvent_getHistoricalOrientation(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical value of the request axis for the given pointer index
+ * that occurred between this event and the previous motion event. */
+float AMotionEvent_getHistoricalAxisValue(const AInputEvent* motion_event,
+ int32_t axis, size_t pointer_index, size_t history_index);
+
+
+/*
+ * Input queue
+ *
+ * An input queue is the facility through which you retrieve input
+ * events.
+ */
+struct AInputQueue;
+typedef struct AInputQueue AInputQueue;
+
+/*
+ * Add this input queue to a looper for processing. See
+ * ALooper_addFd() for information on the ident, callback, and data params.
+ */
+void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
+ int ident, ALooper_callbackFunc callback, void* data);
+
+/*
+ * Remove the input queue from the looper it is currently attached to.
+ */
+void AInputQueue_detachLooper(AInputQueue* queue);
+
+/*
+ * Returns true if there are one or more events available in the
+ * input queue. Returns 1 if the queue has events; 0 if
+ * it does not have events; and a negative value if there is an error.
+ */
+int32_t AInputQueue_hasEvents(AInputQueue* queue);
+
+/*
+ * Returns the next available event from the queue. Returns a negative
+ * value if no events are available or an error has occurred.
+ */
+int32_t AInputQueue_getEvent(AInputQueue* queue, AInputEvent** outEvent);
+
+/*
+ * Sends the key for standard pre-dispatching -- that is, possibly deliver
+ * it to the current IME to be consumed before the app. Returns 0 if it
+ * was not pre-dispatched, meaning you can process it right now. If non-zero
+ * is returned, you must abandon the current event processing and allow the
+ * event to appear again in the event queue (if it does not get consumed during
+ * pre-dispatching).
+ */
+int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event);
+
+/*
+ * Report that dispatching has finished with the given event.
+ * This must be called after receiving an event with AInputQueue_get_event().
+ */
+void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _ANDROID_INPUT_H
diff --git a/widget/gonk/libui/android_keycodes.h b/widget/gonk/libui/android_keycodes.h
new file mode 100644
index 000000000..9e63d1d01
--- /dev/null
+++ b/widget/gonk/libui/android_keycodes.h
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_KEYCODES_H
+#define _ANDROID_KEYCODES_H
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ * This file is part of Android's set of stable system headers
+ * exposed by the Android NDK (Native Development Kit).
+ *
+ * Third-party source AND binary code relies on the definitions
+ * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Key codes.
+ */
+enum {
+ AKEYCODE_UNKNOWN = 0,
+ AKEYCODE_SOFT_LEFT = 1,
+ AKEYCODE_SOFT_RIGHT = 2,
+ AKEYCODE_HOME = 3,
+ AKEYCODE_BACK = 4,
+ AKEYCODE_CALL = 5,
+ AKEYCODE_ENDCALL = 6,
+ AKEYCODE_0 = 7,
+ AKEYCODE_1 = 8,
+ AKEYCODE_2 = 9,
+ AKEYCODE_3 = 10,
+ AKEYCODE_4 = 11,
+ AKEYCODE_5 = 12,
+ AKEYCODE_6 = 13,
+ AKEYCODE_7 = 14,
+ AKEYCODE_8 = 15,
+ AKEYCODE_9 = 16,
+ AKEYCODE_STAR = 17,
+ AKEYCODE_POUND = 18,
+ AKEYCODE_DPAD_UP = 19,
+ AKEYCODE_DPAD_DOWN = 20,
+ AKEYCODE_DPAD_LEFT = 21,
+ AKEYCODE_DPAD_RIGHT = 22,
+ AKEYCODE_DPAD_CENTER = 23,
+ AKEYCODE_VOLUME_UP = 24,
+ AKEYCODE_VOLUME_DOWN = 25,
+ AKEYCODE_POWER = 26,
+ AKEYCODE_CAMERA = 27,
+ AKEYCODE_CLEAR = 28,
+ AKEYCODE_A = 29,
+ AKEYCODE_B = 30,
+ AKEYCODE_C = 31,
+ AKEYCODE_D = 32,
+ AKEYCODE_E = 33,
+ AKEYCODE_F = 34,
+ AKEYCODE_G = 35,
+ AKEYCODE_H = 36,
+ AKEYCODE_I = 37,
+ AKEYCODE_J = 38,
+ AKEYCODE_K = 39,
+ AKEYCODE_L = 40,
+ AKEYCODE_M = 41,
+ AKEYCODE_N = 42,
+ AKEYCODE_O = 43,
+ AKEYCODE_P = 44,
+ AKEYCODE_Q = 45,
+ AKEYCODE_R = 46,
+ AKEYCODE_S = 47,
+ AKEYCODE_T = 48,
+ AKEYCODE_U = 49,
+ AKEYCODE_V = 50,
+ AKEYCODE_W = 51,
+ AKEYCODE_X = 52,
+ AKEYCODE_Y = 53,
+ AKEYCODE_Z = 54,
+ AKEYCODE_COMMA = 55,
+ AKEYCODE_PERIOD = 56,
+ AKEYCODE_ALT_LEFT = 57,
+ AKEYCODE_ALT_RIGHT = 58,
+ AKEYCODE_SHIFT_LEFT = 59,
+ AKEYCODE_SHIFT_RIGHT = 60,
+ AKEYCODE_TAB = 61,
+ AKEYCODE_SPACE = 62,
+ AKEYCODE_SYM = 63,
+ AKEYCODE_EXPLORER = 64,
+ AKEYCODE_ENVELOPE = 65,
+ AKEYCODE_ENTER = 66,
+ AKEYCODE_DEL = 67,
+ AKEYCODE_GRAVE = 68,
+ AKEYCODE_MINUS = 69,
+ AKEYCODE_EQUALS = 70,
+ AKEYCODE_LEFT_BRACKET = 71,
+ AKEYCODE_RIGHT_BRACKET = 72,
+ AKEYCODE_BACKSLASH = 73,
+ AKEYCODE_SEMICOLON = 74,
+ AKEYCODE_APOSTROPHE = 75,
+ AKEYCODE_SLASH = 76,
+ AKEYCODE_AT = 77,
+ AKEYCODE_NUM = 78,
+ AKEYCODE_HEADSETHOOK = 79,
+ AKEYCODE_FOCUS = 80, // *Camera* focus
+ AKEYCODE_PLUS = 81,
+ AKEYCODE_MENU = 82,
+ AKEYCODE_NOTIFICATION = 83,
+ AKEYCODE_SEARCH = 84,
+ AKEYCODE_MEDIA_PLAY_PAUSE= 85,
+ AKEYCODE_MEDIA_STOP = 86,
+ AKEYCODE_MEDIA_NEXT = 87,
+ AKEYCODE_MEDIA_PREVIOUS = 88,
+ AKEYCODE_MEDIA_REWIND = 89,
+ AKEYCODE_MEDIA_FAST_FORWARD = 90,
+ AKEYCODE_MUTE = 91,
+ AKEYCODE_PAGE_UP = 92,
+ AKEYCODE_PAGE_DOWN = 93,
+ AKEYCODE_PICTSYMBOLS = 94,
+ AKEYCODE_SWITCH_CHARSET = 95,
+ AKEYCODE_BUTTON_A = 96,
+ AKEYCODE_BUTTON_B = 97,
+ AKEYCODE_BUTTON_C = 98,
+ AKEYCODE_BUTTON_X = 99,
+ AKEYCODE_BUTTON_Y = 100,
+ AKEYCODE_BUTTON_Z = 101,
+ AKEYCODE_BUTTON_L1 = 102,
+ AKEYCODE_BUTTON_R1 = 103,
+ AKEYCODE_BUTTON_L2 = 104,
+ AKEYCODE_BUTTON_R2 = 105,
+ AKEYCODE_BUTTON_THUMBL = 106,
+ AKEYCODE_BUTTON_THUMBR = 107,
+ AKEYCODE_BUTTON_START = 108,
+ AKEYCODE_BUTTON_SELECT = 109,
+ AKEYCODE_BUTTON_MODE = 110,
+ AKEYCODE_ESCAPE = 111,
+ AKEYCODE_FORWARD_DEL = 112,
+ AKEYCODE_CTRL_LEFT = 113,
+ AKEYCODE_CTRL_RIGHT = 114,
+ AKEYCODE_CAPS_LOCK = 115,
+ AKEYCODE_SCROLL_LOCK = 116,
+ AKEYCODE_META_LEFT = 117,
+ AKEYCODE_META_RIGHT = 118,
+ AKEYCODE_FUNCTION = 119,
+ AKEYCODE_SYSRQ = 120,
+ AKEYCODE_BREAK = 121,
+ AKEYCODE_MOVE_HOME = 122,
+ AKEYCODE_MOVE_END = 123,
+ AKEYCODE_INSERT = 124,
+ AKEYCODE_FORWARD = 125,
+ AKEYCODE_MEDIA_PLAY = 126,
+ AKEYCODE_MEDIA_PAUSE = 127,
+ AKEYCODE_MEDIA_CLOSE = 128,
+ AKEYCODE_MEDIA_EJECT = 129,
+ AKEYCODE_MEDIA_RECORD = 130,
+ AKEYCODE_F1 = 131,
+ AKEYCODE_F2 = 132,
+ AKEYCODE_F3 = 133,
+ AKEYCODE_F4 = 134,
+ AKEYCODE_F5 = 135,
+ AKEYCODE_F6 = 136,
+ AKEYCODE_F7 = 137,
+ AKEYCODE_F8 = 138,
+ AKEYCODE_F9 = 139,
+ AKEYCODE_F10 = 140,
+ AKEYCODE_F11 = 141,
+ AKEYCODE_F12 = 142,
+ AKEYCODE_NUM_LOCK = 143,
+ AKEYCODE_NUMPAD_0 = 144,
+ AKEYCODE_NUMPAD_1 = 145,
+ AKEYCODE_NUMPAD_2 = 146,
+ AKEYCODE_NUMPAD_3 = 147,
+ AKEYCODE_NUMPAD_4 = 148,
+ AKEYCODE_NUMPAD_5 = 149,
+ AKEYCODE_NUMPAD_6 = 150,
+ AKEYCODE_NUMPAD_7 = 151,
+ AKEYCODE_NUMPAD_8 = 152,
+ AKEYCODE_NUMPAD_9 = 153,
+ AKEYCODE_NUMPAD_DIVIDE = 154,
+ AKEYCODE_NUMPAD_MULTIPLY = 155,
+ AKEYCODE_NUMPAD_SUBTRACT = 156,
+ AKEYCODE_NUMPAD_ADD = 157,
+ AKEYCODE_NUMPAD_DOT = 158,
+ AKEYCODE_NUMPAD_COMMA = 159,
+ AKEYCODE_NUMPAD_ENTER = 160,
+ AKEYCODE_NUMPAD_EQUALS = 161,
+ AKEYCODE_NUMPAD_LEFT_PAREN = 162,
+ AKEYCODE_NUMPAD_RIGHT_PAREN = 163,
+ AKEYCODE_VOLUME_MUTE = 164,
+ AKEYCODE_INFO = 165,
+ AKEYCODE_CHANNEL_UP = 166,
+ AKEYCODE_CHANNEL_DOWN = 167,
+ AKEYCODE_ZOOM_IN = 168,
+ AKEYCODE_ZOOM_OUT = 169,
+ AKEYCODE_TV = 170,
+ AKEYCODE_WINDOW = 171,
+ AKEYCODE_GUIDE = 172,
+ AKEYCODE_DVR = 173,
+ AKEYCODE_BOOKMARK = 174,
+ AKEYCODE_CAPTIONS = 175,
+ AKEYCODE_SETTINGS = 176,
+ AKEYCODE_TV_POWER = 177,
+ AKEYCODE_TV_INPUT = 178,
+ AKEYCODE_STB_POWER = 179,
+ AKEYCODE_STB_INPUT = 180,
+ AKEYCODE_AVR_POWER = 181,
+ AKEYCODE_AVR_INPUT = 182,
+ AKEYCODE_PROG_RED = 183,
+ AKEYCODE_PROG_GREEN = 184,
+ AKEYCODE_PROG_YELLOW = 185,
+ AKEYCODE_PROG_BLUE = 186,
+ AKEYCODE_APP_SWITCH = 187,
+ AKEYCODE_BUTTON_1 = 188,
+ AKEYCODE_BUTTON_2 = 189,
+ AKEYCODE_BUTTON_3 = 190,
+ AKEYCODE_BUTTON_4 = 191,
+ AKEYCODE_BUTTON_5 = 192,
+ AKEYCODE_BUTTON_6 = 193,
+ AKEYCODE_BUTTON_7 = 194,
+ AKEYCODE_BUTTON_8 = 195,
+ AKEYCODE_BUTTON_9 = 196,
+ AKEYCODE_BUTTON_10 = 197,
+ AKEYCODE_BUTTON_11 = 198,
+ AKEYCODE_BUTTON_12 = 199,
+ AKEYCODE_BUTTON_13 = 200,
+ AKEYCODE_BUTTON_14 = 201,
+ AKEYCODE_BUTTON_15 = 202,
+ AKEYCODE_BUTTON_16 = 203,
+ AKEYCODE_LANGUAGE_SWITCH = 204,
+ AKEYCODE_MANNER_MODE = 205,
+ AKEYCODE_3D_MODE = 206,
+ AKEYCODE_CONTACTS = 207,
+ AKEYCODE_CALENDAR = 208,
+ AKEYCODE_MUSIC = 209,
+ AKEYCODE_CALCULATOR = 210,
+ AKEYCODE_ZENKAKU_HANKAKU = 211,
+ AKEYCODE_EISU = 212,
+ AKEYCODE_MUHENKAN = 213,
+ AKEYCODE_HENKAN = 214,
+ AKEYCODE_KATAKANA_HIRAGANA = 215,
+ AKEYCODE_YEN = 216,
+ AKEYCODE_RO = 217,
+ AKEYCODE_KANA = 218,
+ AKEYCODE_ASSIST = 219,
+ AKEYCODE_BRIGHTNESS_DOWN = 220,
+ AKEYCODE_BRIGHTNESS_UP = 221,
+ AKEYCODE_MEDIA_AUDIO_TRACK = 222,
+ AKEYCODE_SLEEP = 223,
+ AKEYCODE_WAKEUP = 224,
+ AKEYCODE_PAIRING = 225,
+ AKEYCODE_MEDIA_TOP_MENU = 226,
+ AKEYCODE_11 = 227,
+ AKEYCODE_12 = 228,
+ AKEYCODE_LAST_CHANNEL = 229,
+ AKEYCODE_TV_DATA_SERVICE = 230,
+ AKEYCODE_VOICE_ASSIST = 231,
+ AKEYCODE_TV_RADIO_SERVICE = 232,
+ AKEYCODE_TV_TELETEXT = 233,
+ AKEYCODE_TV_NUMBER_ENTRY = 234,
+ AKEYCODE_TV_TERRESTRIAL_ANALOG = 235,
+ AKEYCODE_TV_TERRESTRIAL_DIGITAL = 236,
+ AKEYCODE_TV_SATELLITE = 237,
+ AKEYCODE_TV_SATELLITE_BS = 238,
+ AKEYCODE_TV_SATELLITE_CS = 239,
+ AKEYCODE_TV_SATELLITE_SERVICE = 240,
+ AKEYCODE_TV_NETWORK = 241,
+ AKEYCODE_TV_ANTENNA_CABLE = 242,
+ AKEYCODE_TV_INPUT_HDMI_1 = 243,
+ AKEYCODE_TV_INPUT_HDMI_2 = 244,
+ AKEYCODE_TV_INPUT_HDMI_3 = 245,
+ AKEYCODE_TV_INPUT_HDMI_4 = 246,
+ AKEYCODE_TV_INPUT_COMPOSITE_1 = 247,
+ AKEYCODE_TV_INPUT_COMPOSITE_2 = 248,
+ AKEYCODE_TV_INPUT_COMPONENT_1 = 249,
+ AKEYCODE_TV_INPUT_COMPONENT_2 = 250,
+ AKEYCODE_TV_INPUT_VGA_1 = 251,
+ AKEYCODE_TV_AUDIO_DESCRIPTION = 252,
+ AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253,
+ AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254,
+ AKEYCODE_TV_ZOOM_MODE = 255,
+ AKEYCODE_TV_CONTENTS_MENU = 256,
+ AKEYCODE_TV_MEDIA_CONTEXT_MENU = 257,
+ AKEYCODE_TV_TIMER_PROGRAMMING = 258,
+ AKEYCODE_HELP = 259,
+
+ // NOTE: If you add a new keycode here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _ANDROID_KEYCODES_H
diff --git a/widget/gonk/libui/cutils_log.h b/widget/gonk/libui/cutils_log.h
new file mode 100644
index 000000000..f4252c867
--- /dev/null
+++ b/widget/gonk/libui/cutils_log.h
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// C/C++ logging functions. See the logging documentation for API details.
+//
+// We'd like these to be available from C code (in case we import some from
+// somewhere), so this has a C interface.
+//
+// The output will be correct when the log file is shared between multiple
+// threads and/or multiple processes so long as the operating system
+// supports O_APPEND. These calls have mutex-protected data structures
+// and so are NOT reentrant. Do not use LOG in a signal handler.
+//
+#if !defined(_LIBS_CUTILS_LOG_H) && !defined(_LIBS_LOG_LOG_H)
+#define _LIBS_LOG_LOG_H
+#define _LIBS_CUTILS_LOG_H
+
+#include <stdio.h>
+#include <time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifdef HAVE_PTHREADS
+#include <pthread.h>
+#endif
+#include <stdarg.h>
+
+#if ANDROID_VERSION >= 19
+#include <log/uio.h>
+#include <log/logd.h>
+#else
+#include <cutils/uio.h>
+#include <cutils/logd.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Normally we strip ALOGV (VERBOSE messages) from release builds.
+ * You can modify this (for example with "#define LOG_NDEBUG 0"
+ * at the top of your source file) to change that behavior.
+ */
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * This is the local tag used for the following simplified
+ * logging macros. You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+#ifndef LOG_TAG
+#define LOG_TAG NULL
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose log message using the current LOG_TAG.
+ */
+#ifndef ALOGV
+#if LOG_NDEBUG
+#define ALOGV(...) ((void)0)
+#else
+#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond) (__builtin_expect((cond)!=0, 0))
+
+#ifndef ALOGV_IF
+#if LOG_NDEBUG
+#define ALOGV_IF(cond, ...) ((void)0)
+#else
+#define ALOGV_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug log message using the current LOG_TAG.
+ */
+#ifndef ALOGD
+#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGD_IF
+#define ALOGD_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info log message using the current LOG_TAG.
+ */
+#ifndef ALOGI
+#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGI_IF
+#define ALOGI_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning log message using the current LOG_TAG.
+ */
+#ifndef ALOGW
+#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGW_IF
+#define ALOGW_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error log message using the current LOG_TAG.
+ */
+#ifndef ALOGE
+#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGE_IF
+#define ALOGE_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * verbose priority.
+ */
+#ifndef IF_ALOGV
+#if LOG_NDEBUG
+#define IF_ALOGV() if (false)
+#else
+#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
+#endif
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * debug priority.
+ */
+#ifndef IF_ALOGD
+#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * info priority.
+ */
+#ifndef IF_ALOGI
+#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * warn priority.
+ */
+#ifndef IF_ALOGW
+#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * error priority.
+ */
+#ifndef IF_ALOGE
+#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
+#endif
+
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose system log message using the current LOG_TAG.
+ */
+#ifndef SLOGV
+#if LOG_NDEBUG
+#define SLOGV(...) ((void)0)
+#else
+#define SLOGV(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond) (__builtin_expect((cond)!=0, 0))
+
+#ifndef SLOGV_IF
+#if LOG_NDEBUG
+#define SLOGV_IF(cond, ...) ((void)0)
+#else
+#define SLOGV_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug system log message using the current LOG_TAG.
+ */
+#ifndef SLOGD
+#define SLOGD(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGD_IF
+#define SLOGD_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info system log message using the current LOG_TAG.
+ */
+#ifndef SLOGI
+#define SLOGI(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGI_IF
+#define SLOGI_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning system log message using the current LOG_TAG.
+ */
+#ifndef SLOGW
+#define SLOGW(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGW_IF
+#define SLOGW_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error system log message using the current LOG_TAG.
+ */
+#ifndef SLOGE
+#define SLOGE(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGE_IF
+#define SLOGE_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGV
+#if LOG_NDEBUG
+#define RLOGV(...) ((void)0)
+#else
+#define RLOGV(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond) (__builtin_expect((cond)!=0, 0))
+
+#ifndef RLOGV_IF
+#if LOG_NDEBUG
+#define RLOGV_IF(cond, ...) ((void)0)
+#else
+#define RLOGV_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGD
+#define RLOGD(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef RLOGD_IF
+#define RLOGD_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGI
+#define RLOGI(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef RLOGI_IF
+#define RLOGI_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGW
+#define RLOGW(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef RLOGW_IF
+#define RLOGW_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGE
+#define RLOGE(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef RLOGE_IF
+#define RLOGE_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+
+// ---------------------------------------------------------------------
+
+/*
+ * Log a fatal error. If the given condition fails, this stops program
+ * execution like a normal assertion, but also generating the given message.
+ * It is NOT stripped from release builds. Note that the condition test
+ * is -inverted- from the normal assert() semantics.
+ */
+#ifndef LOG_ALWAYS_FATAL_IF
+#define LOG_ALWAYS_FATAL_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)android_printAssert(#cond, LOG_TAG, ## __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+#ifndef LOG_ALWAYS_FATAL
+#define LOG_ALWAYS_FATAL(...) \
+ ( ((void)android_printAssert(NULL, LOG_TAG, ## __VA_ARGS__)) )
+#endif
+
+/*
+ * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
+ * are stripped out of release builds.
+ */
+#if LOG_NDEBUG
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) ((void)0)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) ((void)0)
+#endif
+
+#else
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ## __VA_ARGS__)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
+#endif
+
+#endif
+
+/*
+ * Assertion that generates a log message when the assertion fails.
+ * Stripped out of release builds. Uses the current LOG_TAG.
+ */
+#ifndef ALOG_ASSERT
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__)
+//#define ALOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Basic log message macro.
+ *
+ * Example:
+ * ALOG(LOG_WARN, NULL, "Failed with error %d", errno);
+ *
+ * The second argument may be NULL or "" to indicate the "global" tag.
+ */
+#ifndef ALOG
+#define ALOG(priority, tag, ...) \
+ LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef LOG_PRI
+#define LOG_PRI(priority, tag, ...) \
+ android_printLog(priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to pass in a varargs ("args" is a va_list).
+ */
+#ifndef LOG_PRI_VA
+#define LOG_PRI_VA(priority, tag, fmt, args) \
+ android_vprintLog(priority, NULL, tag, fmt, args)
+#endif
+
+/*
+ * Conditional given a desired logging priority and tag.
+ */
+#ifndef IF_ALOG
+#define IF_ALOG(priority, tag) \
+ if (android_testLog(ANDROID_##priority, tag))
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Event logging.
+ */
+
+/*
+ * Event log entry types. These must match up with the declarations in
+ * java/android/android/util/EventLog.java.
+ */
+typedef enum {
+ EVENT_TYPE_INT = 0,
+ EVENT_TYPE_LONG = 1,
+ EVENT_TYPE_STRING = 2,
+ EVENT_TYPE_LIST = 3,
+} AndroidEventLogType;
+
+
+#ifndef LOG_EVENT_INT
+#define LOG_EVENT_INT(_tag, _value) { \
+ int intBuf = _value; \
+ (void) android_btWriteLog(_tag, EVENT_TYPE_INT, &intBuf, \
+ sizeof(intBuf)); \
+ }
+#endif
+#ifndef LOG_EVENT_LONG
+#define LOG_EVENT_LONG(_tag, _value) { \
+ long long longBuf = _value; \
+ (void) android_btWriteLog(_tag, EVENT_TYPE_LONG, &longBuf, \
+ sizeof(longBuf)); \
+ }
+#endif
+#ifndef LOG_EVENT_STRING
+#define LOG_EVENT_STRING(_tag, _value) \
+ ((void) 0) /* not implemented -- must combine len with string */
+#endif
+/* TODO: something for LIST */
+
+/*
+ * ===========================================================================
+ *
+ * The stuff in the rest of this file should not be used directly.
+ */
+
+#define android_printLog(prio, tag, fmt...) \
+ __android_log_print(prio, tag, fmt)
+
+#define android_vprintLog(prio, cond, tag, fmt...) \
+ __android_log_vprint(prio, tag, fmt)
+
+/* XXX Macros to work around syntax errors in places where format string
+ * arg is not passed to ALOG_ASSERT, LOG_ALWAYS_FATAL or LOG_ALWAYS_FATAL_IF
+ * (happens only in debug builds).
+ */
+
+/* Returns 2nd arg. Used to substitute default value if caller's vararg list
+ * is empty.
+ */
+#define __android_second(dummy, second, ...) second
+
+/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
+ * returns nothing.
+ */
+#define __android_rest(first, ...) , ## __VA_ARGS__
+
+#define android_printAssert(cond, tag, fmt...) \
+ __android_log_assert(cond, tag, \
+ __android_second(0, ## fmt, NULL) __android_rest(fmt))
+
+#define android_writeLog(prio, tag, text) \
+ __android_log_write(prio, tag, text)
+
+#define android_bWriteLog(tag, payload, len) \
+ __android_log_bwrite(tag, payload, len)
+#define android_btWriteLog(tag, type, payload, len) \
+ __android_log_btwrite(tag, type, payload, len)
+
+// TODO: remove these prototypes and their users
+#define android_testLog(prio, tag) (1)
+#define android_writevLog(vec,num) do{}while(0)
+#define android_write1Log(str,len) do{}while (0)
+#define android_setMinPriority(tag, prio) do{}while(0)
+//#define android_logToCallback(func) do{}while(0)
+#define android_logToFile(tag, file) (0)
+#define android_logToFd(tag, fd) (0)
+
+typedef enum {
+ LOG_ID_MAIN = 0,
+ LOG_ID_RADIO = 1,
+ LOG_ID_EVENTS = 2,
+ LOG_ID_SYSTEM = 3,
+
+ LOG_ID_MAX
+} log_id_t;
+
+/*
+ * Send a simple string to the log.
+ */
+int __android_log_buf_write(int bufID, int prio, const char *tag, const char *text);
+int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _LIBS_CUTILS_LOG_H
diff --git a/widget/gonk/libui/cutils_trace.h b/widget/gonk/libui/cutils_trace.h
new file mode 100644
index 000000000..29034cab5
--- /dev/null
+++ b/widget/gonk/libui/cutils_trace.h
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LIBS_CUTILS_TRACE_H
+#define _LIBS_CUTILS_TRACE_H
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <cutils/compiler.h>
+
+#ifdef ANDROID_SMP
+#include <cutils/atomic-inline.h>
+#else
+#include <cutils/atomic.h>
+#endif
+
+__BEGIN_DECLS
+
+/**
+ * The ATRACE_TAG macro can be defined before including this header to trace
+ * using one of the tags defined below. It must be defined to one of the
+ * following ATRACE_TAG_* macros. The trace tag is used to filter tracing in
+ * userland to avoid some of the runtime cost of tracing when it is not desired.
+ *
+ * Defining ATRACE_TAG to be ATRACE_TAG_ALWAYS will result in the tracing always
+ * being enabled - this should ONLY be done for debug code, as userland tracing
+ * has a performance cost even when the trace is not being recorded. Defining
+ * ATRACE_TAG to be ATRACE_TAG_NEVER or leaving ATRACE_TAG undefined will result
+ * in the tracing always being disabled.
+ *
+ * ATRACE_TAG_HAL should be bitwise ORed with the relevant tags for tracing
+ * within a hardware module. For example a camera hardware module would set:
+ * #define ATRACE_TAG (ATRACE_TAG_CAMERA | ATRACE_TAG_HAL)
+ *
+ * Keep these in sync with frameworks/base/core/java/android/os/Trace.java.
+ */
+#define ATRACE_TAG_NEVER 0 // This tag is never enabled.
+#define ATRACE_TAG_ALWAYS (1<<0) // This tag is always enabled.
+#define ATRACE_TAG_GRAPHICS (1<<1)
+#define ATRACE_TAG_INPUT (1<<2)
+#define ATRACE_TAG_VIEW (1<<3)
+#define ATRACE_TAG_WEBVIEW (1<<4)
+#define ATRACE_TAG_WINDOW_MANAGER (1<<5)
+#define ATRACE_TAG_ACTIVITY_MANAGER (1<<6)
+#define ATRACE_TAG_SYNC_MANAGER (1<<7)
+#define ATRACE_TAG_AUDIO (1<<8)
+#define ATRACE_TAG_VIDEO (1<<9)
+#define ATRACE_TAG_CAMERA (1<<10)
+#define ATRACE_TAG_HAL (1<<11)
+#define ATRACE_TAG_APP (1<<12)
+#define ATRACE_TAG_RESOURCES (1<<13)
+#define ATRACE_TAG_DALVIK (1<<14)
+#define ATRACE_TAG_LAST ATRACE_TAG_DALVIK
+
+// Reserved for initialization.
+#define ATRACE_TAG_NOT_READY (1LL<<63)
+
+#define ATRACE_TAG_VALID_MASK ((ATRACE_TAG_LAST - 1) | ATRACE_TAG_LAST)
+
+#ifndef ATRACE_TAG
+#define ATRACE_TAG ATRACE_TAG_NEVER
+#elif ATRACE_TAG > ATRACE_TAG_VALID_MASK
+#error ATRACE_TAG must be defined to be one of the tags defined in cutils/trace.h
+#endif
+
+#ifdef HAVE_ANDROID_OS
+/**
+ * Maximum size of a message that can be logged to the trace buffer.
+ * Note this message includes a tag, the pid, and the string given as the name.
+ * Names should be kept short to get the most use of the trace buffer.
+ */
+#define ATRACE_MESSAGE_LENGTH 1024
+
+/**
+ * Opens the trace file for writing and reads the property for initial tags.
+ * The atrace.tags.enableflags property sets the tags to trace.
+ * This function should not be explicitly called, the first call to any normal
+ * trace function will cause it to be run safely.
+ */
+void atrace_setup();
+
+/**
+ * If tracing is ready, set atrace_enabled_tags to the system property
+ * debug.atrace.tags.enableflags. Can be used as a sysprop change callback.
+ */
+void atrace_update_tags();
+
+/**
+ * Set whether the process is debuggable. By default the process is not
+ * considered debuggable. If the process is not debuggable then application-
+ * level tracing is not allowed unless the ro.debuggable system property is
+ * set to '1'.
+ */
+void atrace_set_debuggable(bool debuggable);
+
+/**
+ * Set whether tracing is enabled for the current process. This is used to
+ * prevent tracing within the Zygote process.
+ */
+void atrace_set_tracing_enabled(bool enabled);
+
+/**
+ * Flag indicating whether setup has been completed, initialized to 0.
+ * Nonzero indicates setup has completed.
+ * Note: This does NOT indicate whether or not setup was successful.
+ */
+extern volatile int32_t atrace_is_ready;
+
+/**
+ * Set of ATRACE_TAG flags to trace for, initialized to ATRACE_TAG_NOT_READY.
+ * A value of zero indicates setup has failed.
+ * Any other nonzero value indicates setup has succeeded, and tracing is on.
+ */
+extern uint64_t atrace_enabled_tags;
+
+/**
+ * Handle to the kernel's trace buffer, initialized to -1.
+ * Any other value indicates setup has succeeded, and is a valid fd for tracing.
+ */
+extern int atrace_marker_fd;
+
+/**
+ * atrace_init readies the process for tracing by opening the trace_marker file.
+ * Calling any trace function causes this to be run, so calling it is optional.
+ * This can be explicitly run to avoid setup delay on first trace function.
+ */
+#define ATRACE_INIT() atrace_init()
+static inline void atrace_init()
+{
+ if (CC_UNLIKELY(!android_atomic_acquire_load(&atrace_is_ready))) {
+ atrace_setup();
+ }
+}
+
+/**
+ * Get the mask of all tags currently enabled.
+ * It can be used as a guard condition around more expensive trace calculations.
+ * Every trace function calls this, which ensures atrace_init is run.
+ */
+#define ATRACE_GET_ENABLED_TAGS() atrace_get_enabled_tags()
+static inline uint64_t atrace_get_enabled_tags()
+{
+ atrace_init();
+ return atrace_enabled_tags;
+}
+
+/**
+ * Test if a given tag is currently enabled.
+ * Returns nonzero if the tag is enabled, otherwise zero.
+ * It can be used as a guard condition around more expensive trace calculations.
+ */
+#define ATRACE_ENABLED() atrace_is_tag_enabled(ATRACE_TAG)
+static inline uint64_t atrace_is_tag_enabled(uint64_t tag)
+{
+ return atrace_get_enabled_tags() & tag;
+}
+
+/**
+ * Trace the beginning of a context. name is used to identify the context.
+ * This is often used to time function execution.
+ */
+#define ATRACE_BEGIN(name) atrace_begin(ATRACE_TAG, name)
+static inline void atrace_begin(uint64_t tag, const char* name)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char buf[ATRACE_MESSAGE_LENGTH];
+ size_t len;
+
+ len = snprintf(buf, ATRACE_MESSAGE_LENGTH, "B|%d|%s", getpid(), name);
+ write(atrace_marker_fd, buf, len);
+ }
+}
+
+/**
+ * Trace the end of a context.
+ * This should match up (and occur after) a corresponding ATRACE_BEGIN.
+ */
+#define ATRACE_END() atrace_end(ATRACE_TAG)
+static inline void atrace_end(uint64_t tag)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char c = 'E';
+ write(atrace_marker_fd, &c, 1);
+ }
+}
+
+/**
+ * Trace the beginning of an asynchronous event. Unlike ATRACE_BEGIN/ATRACE_END
+ * contexts, asynchronous events do not need to be nested. The name describes
+ * the event, and the cookie provides a unique identifier for distinguishing
+ * simultaneous events. The name and cookie used to begin an event must be
+ * used to end it.
+ */
+#define ATRACE_ASYNC_BEGIN(name, cookie) \
+ atrace_async_begin(ATRACE_TAG, name, cookie)
+static inline void atrace_async_begin(uint64_t tag, const char* name,
+ int32_t cookie)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char buf[ATRACE_MESSAGE_LENGTH];
+ size_t len;
+
+ len = snprintf(buf, ATRACE_MESSAGE_LENGTH, "S|%d|%s|%d", getpid(),
+ name, cookie);
+ write(atrace_marker_fd, buf, len);
+ }
+}
+
+/**
+ * Trace the end of an asynchronous event.
+ * This should have a corresponding ATRACE_ASYNC_BEGIN.
+ */
+#define ATRACE_ASYNC_END(name, cookie) atrace_async_end(ATRACE_TAG, name, cookie)
+static inline void atrace_async_end(uint64_t tag, const char* name,
+ int32_t cookie)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char buf[ATRACE_MESSAGE_LENGTH];
+ size_t len;
+
+ len = snprintf(buf, ATRACE_MESSAGE_LENGTH, "F|%d|%s|%d", getpid(),
+ name, cookie);
+ write(atrace_marker_fd, buf, len);
+ }
+}
+
+
+/**
+ * Traces an integer counter value. name is used to identify the counter.
+ * This can be used to track how a value changes over time.
+ */
+#define ATRACE_INT(name, value) atrace_int(ATRACE_TAG, name, value)
+static inline void atrace_int(uint64_t tag, const char* name, int32_t value)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char buf[ATRACE_MESSAGE_LENGTH];
+ size_t len;
+
+ len = snprintf(buf, ATRACE_MESSAGE_LENGTH, "C|%d|%s|%d",
+ getpid(), name, value);
+ write(atrace_marker_fd, buf, len);
+ }
+}
+
+#else // not HAVE_ANDROID_OS
+
+#define ATRACE_INIT()
+#define ATRACE_GET_ENABLED_TAGS()
+#define ATRACE_ENABLED()
+#define ATRACE_BEGIN(name)
+#define ATRACE_END()
+#define ATRACE_ASYNC_BEGIN(name, cookie)
+#define ATRACE_ASYNC_END(name, cookie)
+#define ATRACE_INT(name, value)
+
+#endif // not HAVE_ANDROID_OS
+
+__END_DECLS
+
+#endif // _LIBS_CUTILS_TRACE_H
diff --git a/widget/gonk/libui/linux_input.h b/widget/gonk/libui/linux_input.h
new file mode 100644
index 000000000..2ba14973f
--- /dev/null
+++ b/widget/gonk/libui/linux_input.h
@@ -0,0 +1,1029 @@
+/****************************************************************************
+ ****************************************************************************
+ ***
+ *** This header was automatically generated from a Linux kernel header
+ *** of the same name, to make information necessary for userspace to
+ *** call into the kernel available to libc. It contains only constants,
+ *** structures, and macros generated from the original header, and thus,
+ *** contains no copyrightable information.
+ ***
+ *** To edit the content of this header, modify the corresponding
+ *** source file (e.g. under external/kernel-headers/original/) then
+ *** run bionic/libc/kernel/tools/update_all.py
+ ***
+ *** Any manual change here will be lost the next time this script will
+ *** be run. You've been warned!
+ ***
+ ****************************************************************************
+ ****************************************************************************/
+#ifndef _INPUT_H
+#define _INPUT_H
+#include <sys/time.h>
+#include <sys/ioctl.h>
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#include <sys/types.h>
+#include <linux/types.h>
+struct input_event {
+ struct timeval time;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 type;
+ __u16 code;
+ __s32 value;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_VERSION 0x010001
+struct input_id {
+ __u16 bustype;
+ __u16 vendor;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 product;
+ __u16 version;
+};
+struct input_absinfo {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __s32 value;
+ __s32 minimum;
+ __s32 maximum;
+ __s32 fuzz;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __s32 flat;
+ __s32 resolution;
+};
+struct input_keymap_entry {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define INPUT_KEYMAP_BY_INDEX (1 << 0)
+ __u8 flags;
+ __u8 len;
+ __u16 index;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u32 keycode;
+ __u8 scancode[32];
+};
+#define EVIOCGVERSION _IOR('E', 0x01, int)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGID _IOR('E', 0x02, struct input_id)
+#define EVIOCGREP _IOR('E', 0x03, unsigned int[2])
+#define EVIOCSREP _IOW('E', 0x03, unsigned int[2])
+#define EVIOCGKEYCODE _IOR('E', 0x04, unsigned int[2])
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGKEYCODE_V2 _IOR('E', 0x04, struct input_keymap_entry)
+#define EVIOCSKEYCODE _IOW('E', 0x04, unsigned int[2])
+#define EVIOCSKEYCODE_V2 _IOW('E', 0x04, struct input_keymap_entry)
+#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGPHYS(len) _IOC(_IOC_READ, 'E', 0x07, len)
+#define EVIOCGUNIQ(len) _IOC(_IOC_READ, 'E', 0x08, len)
+#define EVIOCGPROP(len) _IOC(_IOC_READ, 'E', 0x09, len)
+#define EVIOCGMTSLOTS(len) _IOC(_IOC_READ, 'E', 0x0a, len)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len)
+#define EVIOCGLED(len) _IOC(_IOC_READ, 'E', 0x19, len)
+#define EVIOCGSND(len) _IOC(_IOC_READ, 'E', 0x1a, len)
+#define EVIOCGSW(len) _IOC(_IOC_READ, 'E', 0x1b, len)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len)
+#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo)
+#define EVIOCSABS(abs) _IOW('E', 0xc0 + (abs), struct input_absinfo)
+#define EVIOCSFF _IOC(_IOC_WRITE, 'E', 0x80, sizeof(struct ff_effect))
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCRMFF _IOW('E', 0x81, int)
+#define EVIOCGEFFECTS _IOR('E', 0x84, int)
+#define EVIOCGRAB _IOW('E', 0x90, int)
+#define EVIOCGSUSPENDBLOCK _IOR('E', 0x91, int)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCSSUSPENDBLOCK _IOW('E', 0x91, int)
+#define EVIOCSCLOCKID _IOW('E', 0xa0, int)
+#define INPUT_PROP_POINTER 0x00
+#define INPUT_PROP_DIRECT 0x01
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define INPUT_PROP_BUTTONPAD 0x02
+#define INPUT_PROP_SEMI_MT 0x03
+#define INPUT_PROP_MAX 0x1f
+#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_SYN 0x00
+#define EV_KEY 0x01
+#define EV_REL 0x02
+#define EV_ABS 0x03
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_MSC 0x04
+#define EV_SW 0x05
+#define EV_LED 0x11
+#define EV_SND 0x12
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_REP 0x14
+#define EV_FF 0x15
+#define EV_PWR 0x16
+#define EV_FF_STATUS 0x17
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_MAX 0x1f
+#define EV_CNT (EV_MAX+1)
+#define SYN_REPORT 0
+#define SYN_CONFIG 1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SYN_MT_REPORT 2
+#define SYN_DROPPED 3
+#define SYN_TIME_SEC 4
+#define SYN_TIME_NSEC 5
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RESERVED 0
+#define KEY_ESC 1
+#define KEY_1 2
+#define KEY_2 3
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_3 4
+#define KEY_4 5
+#define KEY_5 6
+#define KEY_6 7
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_7 8
+#define KEY_8 9
+#define KEY_9 10
+#define KEY_0 11
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MINUS 12
+#define KEY_EQUAL 13
+#define KEY_BACKSPACE 14
+#define KEY_TAB 15
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_Q 16
+#define KEY_W 17
+#define KEY_E 18
+#define KEY_R 19
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_T 20
+#define KEY_Y 21
+#define KEY_U 22
+#define KEY_I 23
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_O 24
+#define KEY_P 25
+#define KEY_LEFTBRACE 26
+#define KEY_RIGHTBRACE 27
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_ENTER 28
+#define KEY_LEFTCTRL 29
+#define KEY_A 30
+#define KEY_S 31
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_D 32
+#define KEY_F 33
+#define KEY_G 34
+#define KEY_H 35
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_J 36
+#define KEY_K 37
+#define KEY_L 38
+#define KEY_SEMICOLON 39
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_APOSTROPHE 40
+#define KEY_GRAVE 41
+#define KEY_LEFTSHIFT 42
+#define KEY_BACKSLASH 43
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_Z 44
+#define KEY_X 45
+#define KEY_C 46
+#define KEY_V 47
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_B 48
+#define KEY_N 49
+#define KEY_M 50
+#define KEY_COMMA 51
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DOT 52
+#define KEY_SLASH 53
+#define KEY_RIGHTSHIFT 54
+#define KEY_KPASTERISK 55
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_LEFTALT 56
+#define KEY_SPACE 57
+#define KEY_CAPSLOCK 58
+#define KEY_F1 59
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F2 60
+#define KEY_F3 61
+#define KEY_F4 62
+#define KEY_F5 63
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F6 64
+#define KEY_F7 65
+#define KEY_F8 66
+#define KEY_F9 67
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F10 68
+#define KEY_NUMLOCK 69
+#define KEY_SCROLLLOCK 70
+#define KEY_KP7 71
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KP8 72
+#define KEY_KP9 73
+#define KEY_KPMINUS 74
+#define KEY_KP4 75
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KP5 76
+#define KEY_KP6 77
+#define KEY_KPPLUS 78
+#define KEY_KP1 79
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KP2 80
+#define KEY_KP3 81
+#define KEY_KP0 82
+#define KEY_KPDOT 83
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_ZENKAKUHANKAKU 85
+#define KEY_102ND 86
+#define KEY_F11 87
+#define KEY_F12 88
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RO 89
+#define KEY_KATAKANA 90
+#define KEY_HIRAGANA 91
+#define KEY_HENKAN 92
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KATAKANAHIRAGANA 93
+#define KEY_MUHENKAN 94
+#define KEY_KPJPCOMMA 95
+#define KEY_KPENTER 96
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RIGHTCTRL 97
+#define KEY_KPSLASH 98
+#define KEY_SYSRQ 99
+#define KEY_RIGHTALT 100
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_LINEFEED 101
+#define KEY_HOME 102
+#define KEY_UP 103
+#define KEY_PAGEUP 104
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_LEFT 105
+#define KEY_RIGHT 106
+#define KEY_END 107
+#define KEY_DOWN 108
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PAGEDOWN 109
+#define KEY_INSERT 110
+#define KEY_DELETE 111
+#define KEY_MACRO 112
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MUTE 113
+#define KEY_VOLUMEDOWN 114
+#define KEY_VOLUMEUP 115
+#define KEY_POWER 116
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KPEQUAL 117
+#define KEY_KPPLUSMINUS 118
+#define KEY_PAUSE 119
+#define KEY_SCALE 120
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KPCOMMA 121
+#define KEY_HANGEUL 122
+#define KEY_HANGUEL KEY_HANGEUL
+#define KEY_HANJA 123
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_YEN 124
+#define KEY_LEFTMETA 125
+#define KEY_RIGHTMETA 126
+#define KEY_COMPOSE 127
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_STOP 128
+#define KEY_AGAIN 129
+#define KEY_PROPS 130
+#define KEY_UNDO 131
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FRONT 132
+#define KEY_COPY 133
+#define KEY_OPEN 134
+#define KEY_PASTE 135
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FIND 136
+#define KEY_CUT 137
+#define KEY_HELP 138
+#define KEY_MENU 139
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CALC 140
+#define KEY_SETUP 141
+#define KEY_SLEEP 142
+#define KEY_WAKEUP 143
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FILE 144
+#define KEY_SENDFILE 145
+#define KEY_DELETEFILE 146
+#define KEY_XFER 147
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PROG1 148
+#define KEY_PROG2 149
+#define KEY_WWW 150
+#define KEY_MSDOS 151
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_COFFEE 152
+#define KEY_SCREENLOCK KEY_COFFEE
+#define KEY_DIRECTION 153
+#define KEY_CYCLEWINDOWS 154
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MAIL 155
+#define KEY_BOOKMARKS 156
+#define KEY_COMPUTER 157
+#define KEY_BACK 158
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FORWARD 159
+#define KEY_CLOSECD 160
+#define KEY_EJECTCD 161
+#define KEY_EJECTCLOSECD 162
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_NEXTSONG 163
+#define KEY_PLAYPAUSE 164
+#define KEY_PREVIOUSSONG 165
+#define KEY_STOPCD 166
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RECORD 167
+#define KEY_REWIND 168
+#define KEY_PHONE 169
+#define KEY_ISO 170
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CONFIG 171
+#define KEY_HOMEPAGE 172
+#define KEY_REFRESH 173
+#define KEY_EXIT 174
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MOVE 175
+#define KEY_EDIT 176
+#define KEY_SCROLLUP 177
+#define KEY_SCROLLDOWN 178
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KPLEFTPAREN 179
+#define KEY_KPRIGHTPAREN 180
+#define KEY_NEW 181
+#define KEY_REDO 182
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F13 183
+#define KEY_F14 184
+#define KEY_F15 185
+#define KEY_F16 186
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F17 187
+#define KEY_F18 188
+#define KEY_F19 189
+#define KEY_F20 190
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F21 191
+#define KEY_F22 192
+#define KEY_F23 193
+#define KEY_F24 194
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PLAYCD 200
+#define KEY_PAUSECD 201
+#define KEY_PROG3 202
+#define KEY_PROG4 203
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DASHBOARD 204
+#define KEY_SUSPEND 205
+#define KEY_CLOSE 206
+#define KEY_PLAY 207
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FASTFORWARD 208
+#define KEY_BASSBOOST 209
+#define KEY_PRINT 210
+#define KEY_HP 211
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CAMERA 212
+#define KEY_SOUND 213
+#define KEY_QUESTION 214
+#define KEY_EMAIL 215
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CHAT 216
+#define KEY_SEARCH 217
+#define KEY_CONNECT 218
+#define KEY_FINANCE 219
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_SPORT 220
+#define KEY_SHOP 221
+#define KEY_ALTERASE 222
+#define KEY_CANCEL 223
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BRIGHTNESSDOWN 224
+#define KEY_BRIGHTNESSUP 225
+#define KEY_MEDIA 226
+#define KEY_SWITCHVIDEOMODE 227
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KBDILLUMTOGGLE 228
+#define KEY_KBDILLUMDOWN 229
+#define KEY_KBDILLUMUP 230
+#define KEY_SEND 231
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_REPLY 232
+#define KEY_FORWARDMAIL 233
+#define KEY_SAVE 234
+#define KEY_DOCUMENTS 235
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BATTERY 236
+#define KEY_BLUETOOTH 237
+#define KEY_WLAN 238
+#define KEY_UWB 239
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_UNKNOWN 240
+#define KEY_VIDEO_NEXT 241
+#define KEY_VIDEO_PREV 242
+#define KEY_BRIGHTNESS_CYCLE 243
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BRIGHTNESS_ZERO 244
+#define KEY_DISPLAY_OFF 245
+#define KEY_WIMAX 246
+#define KEY_RFKILL 247
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MICMUTE 248
+#define BTN_MISC 0x100
+#define BTN_0 0x100
+#define BTN_1 0x101
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_2 0x102
+#define BTN_3 0x103
+#define BTN_4 0x104
+#define BTN_5 0x105
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_6 0x106
+#define BTN_7 0x107
+#define BTN_8 0x108
+#define BTN_9 0x109
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_MOUSE 0x110
+#define BTN_LEFT 0x110
+#define BTN_RIGHT 0x111
+#define BTN_MIDDLE 0x112
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_SIDE 0x113
+#define BTN_EXTRA 0x114
+#define BTN_FORWARD 0x115
+#define BTN_BACK 0x116
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TASK 0x117
+#define BTN_JOYSTICK 0x120
+#define BTN_TRIGGER 0x120
+#define BTN_THUMB 0x121
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_THUMB2 0x122
+#define BTN_TOP 0x123
+#define BTN_TOP2 0x124
+#define BTN_PINKIE 0x125
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_BASE 0x126
+#define BTN_BASE2 0x127
+#define BTN_BASE3 0x128
+#define BTN_BASE4 0x129
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_BASE5 0x12a
+#define BTN_BASE6 0x12b
+#define BTN_DEAD 0x12f
+#define BTN_GAMEPAD 0x130
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_A 0x130
+#define BTN_B 0x131
+#define BTN_C 0x132
+#define BTN_X 0x133
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_Y 0x134
+#define BTN_Z 0x135
+#define BTN_TL 0x136
+#define BTN_TR 0x137
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TL2 0x138
+#define BTN_TR2 0x139
+#define BTN_SELECT 0x13a
+#define BTN_START 0x13b
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_MODE 0x13c
+#define BTN_THUMBL 0x13d
+#define BTN_THUMBR 0x13e
+#define BTN_DIGI 0x140
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TOOL_PEN 0x140
+#define BTN_TOOL_RUBBER 0x141
+#define BTN_TOOL_BRUSH 0x142
+#define BTN_TOOL_PENCIL 0x143
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TOOL_AIRBRUSH 0x144
+#define BTN_TOOL_FINGER 0x145
+#define BTN_TOOL_MOUSE 0x146
+#define BTN_TOOL_LENS 0x147
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TOOL_QUINTTAP 0x148
+#define BTN_TOUCH 0x14a
+#define BTN_STYLUS 0x14b
+#define BTN_STYLUS2 0x14c
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TOOL_DOUBLETAP 0x14d
+#define BTN_TOOL_TRIPLETAP 0x14e
+#define BTN_TOOL_QUADTAP 0x14f
+#define BTN_WHEEL 0x150
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_GEAR_DOWN 0x150
+#define BTN_GEAR_UP 0x151
+#define KEY_OK 0x160
+#define KEY_SELECT 0x161
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_GOTO 0x162
+#define KEY_CLEAR 0x163
+#define KEY_POWER2 0x164
+#define KEY_OPTION 0x165
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_INFO 0x166
+#define KEY_TIME 0x167
+#define KEY_VENDOR 0x168
+#define KEY_ARCHIVE 0x169
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PROGRAM 0x16a
+#define KEY_CHANNEL 0x16b
+#define KEY_FAVORITES 0x16c
+#define KEY_EPG 0x16d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PVR 0x16e
+#define KEY_MHP 0x16f
+#define KEY_LANGUAGE 0x170
+#define KEY_TITLE 0x171
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_SUBTITLE 0x172
+#define KEY_ANGLE 0x173
+#define KEY_ZOOM 0x174
+#define KEY_MODE 0x175
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KEYBOARD 0x176
+#define KEY_SCREEN 0x177
+#define KEY_PC 0x178
+#define KEY_TV 0x179
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_TV2 0x17a
+#define KEY_VCR 0x17b
+#define KEY_VCR2 0x17c
+#define KEY_SAT 0x17d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_SAT2 0x17e
+#define KEY_CD 0x17f
+#define KEY_TAPE 0x180
+#define KEY_RADIO 0x181
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_TUNER 0x182
+#define KEY_PLAYER 0x183
+#define KEY_TEXT 0x184
+#define KEY_DVD 0x185
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_AUX 0x186
+#define KEY_MP3 0x187
+#define KEY_AUDIO 0x188
+#define KEY_VIDEO 0x189
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DIRECTORY 0x18a
+#define KEY_LIST 0x18b
+#define KEY_MEMO 0x18c
+#define KEY_CALENDAR 0x18d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RED 0x18e
+#define KEY_GREEN 0x18f
+#define KEY_YELLOW 0x190
+#define KEY_BLUE 0x191
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CHANNELUP 0x192
+#define KEY_CHANNELDOWN 0x193
+#define KEY_FIRST 0x194
+#define KEY_LAST 0x195
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_AB 0x196
+#define KEY_NEXT 0x197
+#define KEY_RESTART 0x198
+#define KEY_SLOW 0x199
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_SHUFFLE 0x19a
+#define KEY_BREAK 0x19b
+#define KEY_PREVIOUS 0x19c
+#define KEY_DIGITS 0x19d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_TEEN 0x19e
+#define KEY_TWEN 0x19f
+#define KEY_VIDEOPHONE 0x1a0
+#define KEY_GAMES 0x1a1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_ZOOMIN 0x1a2
+#define KEY_ZOOMOUT 0x1a3
+#define KEY_ZOOMRESET 0x1a4
+#define KEY_WORDPROCESSOR 0x1a5
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_EDITOR 0x1a6
+#define KEY_SPREADSHEET 0x1a7
+#define KEY_GRAPHICSEDITOR 0x1a8
+#define KEY_PRESENTATION 0x1a9
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DATABASE 0x1aa
+#define KEY_NEWS 0x1ab
+#define KEY_VOICEMAIL 0x1ac
+#define KEY_ADDRESSBOOK 0x1ad
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MESSENGER 0x1ae
+#define KEY_DISPLAYTOGGLE 0x1af
+#define KEY_SPELLCHECK 0x1b0
+#define KEY_LOGOFF 0x1b1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DOLLAR 0x1b2
+#define KEY_EURO 0x1b3
+#define KEY_FRAMEBACK 0x1b4
+#define KEY_FRAMEFORWARD 0x1b5
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CONTEXT_MENU 0x1b6
+#define KEY_MEDIA_REPEAT 0x1b7
+#define KEY_10CHANNELSUP 0x1b8
+#define KEY_10CHANNELSDOWN 0x1b9
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_IMAGES 0x1ba
+#define KEY_DEL_EOL 0x1c0
+#define KEY_DEL_EOS 0x1c1
+#define KEY_INS_LINE 0x1c2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DEL_LINE 0x1c3
+#define KEY_FN 0x1d0
+#define KEY_FN_ESC 0x1d1
+#define KEY_FN_F1 0x1d2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_F2 0x1d3
+#define KEY_FN_F3 0x1d4
+#define KEY_FN_F4 0x1d5
+#define KEY_FN_F5 0x1d6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_F6 0x1d7
+#define KEY_FN_F7 0x1d8
+#define KEY_FN_F8 0x1d9
+#define KEY_FN_F9 0x1da
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_F10 0x1db
+#define KEY_FN_F11 0x1dc
+#define KEY_FN_F12 0x1dd
+#define KEY_FN_1 0x1de
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_2 0x1df
+#define KEY_FN_D 0x1e0
+#define KEY_FN_E 0x1e1
+#define KEY_FN_F 0x1e2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_S 0x1e3
+#define KEY_FN_B 0x1e4
+#define KEY_BRL_DOT1 0x1f1
+#define KEY_BRL_DOT2 0x1f2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BRL_DOT3 0x1f3
+#define KEY_BRL_DOT4 0x1f4
+#define KEY_BRL_DOT5 0x1f5
+#define KEY_BRL_DOT6 0x1f6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BRL_DOT7 0x1f7
+#define KEY_BRL_DOT8 0x1f8
+#define KEY_BRL_DOT9 0x1f9
+#define KEY_BRL_DOT10 0x1fa
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_NUMERIC_0 0x200
+#define KEY_NUMERIC_1 0x201
+#define KEY_NUMERIC_2 0x202
+#define KEY_NUMERIC_3 0x203
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_NUMERIC_4 0x204
+#define KEY_NUMERIC_5 0x205
+#define KEY_NUMERIC_6 0x206
+#define KEY_NUMERIC_7 0x207
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_NUMERIC_8 0x208
+#define KEY_NUMERIC_9 0x209
+#define KEY_NUMERIC_STAR 0x20a
+#define KEY_NUMERIC_POUND 0x20b
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CAMERA_SNAPSHOT 0x2fe
+#define KEY_CAMERA_FOCUS 0x210
+#define KEY_WPS_BUTTON 0x211
+#define KEY_TOUCHPAD_TOGGLE 0x212
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_TOUCHPAD_ON 0x213
+#define KEY_TOUCHPAD_OFF 0x214
+#define KEY_CAMERA_ZOOMIN 0x215
+#define KEY_CAMERA_ZOOMOUT 0x216
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CAMERA_UP 0x217
+#define KEY_CAMERA_DOWN 0x218
+#define KEY_CAMERA_LEFT 0x219
+#define KEY_CAMERA_RIGHT 0x21a
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY 0x2c0
+#define BTN_TRIGGER_HAPPY1 0x2c0
+#define BTN_TRIGGER_HAPPY2 0x2c1
+#define BTN_TRIGGER_HAPPY3 0x2c2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY4 0x2c3
+#define BTN_TRIGGER_HAPPY5 0x2c4
+#define BTN_TRIGGER_HAPPY6 0x2c5
+#define BTN_TRIGGER_HAPPY7 0x2c6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY8 0x2c7
+#define BTN_TRIGGER_HAPPY9 0x2c8
+#define BTN_TRIGGER_HAPPY10 0x2c9
+#define BTN_TRIGGER_HAPPY11 0x2ca
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY12 0x2cb
+#define BTN_TRIGGER_HAPPY13 0x2cc
+#define BTN_TRIGGER_HAPPY14 0x2cd
+#define BTN_TRIGGER_HAPPY15 0x2ce
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY16 0x2cf
+#define BTN_TRIGGER_HAPPY17 0x2d0
+#define BTN_TRIGGER_HAPPY18 0x2d1
+#define BTN_TRIGGER_HAPPY19 0x2d2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY20 0x2d3
+#define BTN_TRIGGER_HAPPY21 0x2d4
+#define BTN_TRIGGER_HAPPY22 0x2d5
+#define BTN_TRIGGER_HAPPY23 0x2d6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY24 0x2d7
+#define BTN_TRIGGER_HAPPY25 0x2d8
+#define BTN_TRIGGER_HAPPY26 0x2d9
+#define BTN_TRIGGER_HAPPY27 0x2da
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY28 0x2db
+#define BTN_TRIGGER_HAPPY29 0x2dc
+#define BTN_TRIGGER_HAPPY30 0x2dd
+#define BTN_TRIGGER_HAPPY31 0x2de
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY32 0x2df
+#define BTN_TRIGGER_HAPPY33 0x2e0
+#define BTN_TRIGGER_HAPPY34 0x2e1
+#define BTN_TRIGGER_HAPPY35 0x2e2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY36 0x2e3
+#define BTN_TRIGGER_HAPPY37 0x2e4
+#define BTN_TRIGGER_HAPPY38 0x2e5
+#define BTN_TRIGGER_HAPPY39 0x2e6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY40 0x2e7
+#define KEY_MIN_INTERESTING KEY_MUTE
+#define KEY_MAX 0x2ff
+#define KEY_CNT (KEY_MAX+1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define REL_X 0x00
+#define REL_Y 0x01
+#define REL_Z 0x02
+#define REL_RX 0x03
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define REL_RY 0x04
+#define REL_RZ 0x05
+#define REL_HWHEEL 0x06
+#define REL_DIAL 0x07
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define REL_WHEEL 0x08
+#define REL_MISC 0x09
+#define REL_MAX 0x0f
+#define REL_CNT (REL_MAX+1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_X 0x00
+#define ABS_Y 0x01
+#define ABS_Z 0x02
+#define ABS_RX 0x03
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_RY 0x04
+#define ABS_RZ 0x05
+#define ABS_THROTTLE 0x06
+#define ABS_RUDDER 0x07
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_WHEEL 0x08
+#define ABS_GAS 0x09
+#define ABS_BRAKE 0x0a
+#define ABS_HAT0X 0x10
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_HAT0Y 0x11
+#define ABS_HAT1X 0x12
+#define ABS_HAT1Y 0x13
+#define ABS_HAT2X 0x14
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_HAT2Y 0x15
+#define ABS_HAT3X 0x16
+#define ABS_HAT3Y 0x17
+#define ABS_PRESSURE 0x18
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_DISTANCE 0x19
+#define ABS_TILT_X 0x1a
+#define ABS_TILT_Y 0x1b
+#define ABS_TOOL_WIDTH 0x1c
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_VOLUME 0x20
+#define ABS_MISC 0x28
+#define ABS_MT_SLOT 0x2f
+#define ABS_MT_TOUCH_MAJOR 0x30
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_MT_TOUCH_MINOR 0x31
+#define ABS_MT_WIDTH_MAJOR 0x32
+#define ABS_MT_WIDTH_MINOR 0x33
+#define ABS_MT_ORIENTATION 0x34
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_MT_POSITION_X 0x35
+#define ABS_MT_POSITION_Y 0x36
+#define ABS_MT_TOOL_TYPE 0x37
+#define ABS_MT_BLOB_ID 0x38
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_MT_TRACKING_ID 0x39
+#define ABS_MT_PRESSURE 0x3a
+#define ABS_MT_DISTANCE 0x3b
+#define ABS_MAX 0x3f
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_CNT (ABS_MAX+1)
+#define SW_LID 0x00
+#define SW_TABLET_MODE 0x01
+#define SW_HEADPHONE_INSERT 0x02
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_RFKILL_ALL 0x03
+#define SW_RADIO SW_RFKILL_ALL
+#define SW_MICROPHONE_INSERT 0x04
+#define SW_DOCK 0x05
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_LINEOUT_INSERT 0x06
+#define SW_JACK_PHYSICAL_INSERT 0x07
+#define SW_VIDEOOUT_INSERT 0x08
+#define SW_CAMERA_LENS_COVER 0x09
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_KEYPAD_SLIDE 0x0a
+#define SW_FRONT_PROXIMITY 0x0b
+#define SW_ROTATE_LOCK 0x0c
+#define SW_LINEIN_INSERT 0x0d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_HPHL_OVERCURRENT 0x0e
+#define SW_HPHR_OVERCURRENT 0x0f
+#define SW_UNSUPPORT_INSERT 0x10
+#define SW_MAX 0x20
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_CNT (SW_MAX+1)
+#define MSC_SERIAL 0x00
+#define MSC_PULSELED 0x01
+#define MSC_GESTURE 0x02
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define MSC_RAW 0x03
+#define MSC_SCAN 0x04
+#define MSC_MAX 0x07
+#define MSC_CNT (MSC_MAX+1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define LED_NUML 0x00
+#define LED_CAPSL 0x01
+#define LED_SCROLLL 0x02
+#define LED_COMPOSE 0x03
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define LED_KANA 0x04
+#define LED_SLEEP 0x05
+#define LED_SUSPEND 0x06
+#define LED_MUTE 0x07
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define LED_MISC 0x08
+#define LED_MAIL 0x09
+#define LED_CHARGING 0x0a
+#define LED_MAX 0x0f
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define LED_CNT (LED_MAX+1)
+#define REP_DELAY 0x00
+#define REP_PERIOD 0x01
+#define REP_MAX 0x01
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define REP_CNT (REP_MAX+1)
+#define SND_CLICK 0x00
+#define SND_BELL 0x01
+#define SND_TONE 0x02
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SND_MAX 0x07
+#define SND_CNT (SND_MAX+1)
+#define ID_BUS 0
+#define ID_VENDOR 1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ID_PRODUCT 2
+#define ID_VERSION 3
+#define BUS_PCI 0x01
+#define BUS_ISAPNP 0x02
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_USB 0x03
+#define BUS_HIL 0x04
+#define BUS_BLUETOOTH 0x05
+#define BUS_VIRTUAL 0x06
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_ISA 0x10
+#define BUS_I8042 0x11
+#define BUS_XTKBD 0x12
+#define BUS_RS232 0x13
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_GAMEPORT 0x14
+#define BUS_PARPORT 0x15
+#define BUS_AMIGA 0x16
+#define BUS_ADB 0x17
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_I2C 0x18
+#define BUS_HOST 0x19
+#define BUS_GSC 0x1A
+#define BUS_ATARI 0x1B
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_SPI 0x1C
+#define MT_TOOL_FINGER 0
+#define MT_TOOL_PEN 1
+#define MT_TOOL_MAX 1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_STATUS_STOPPED 0x00
+#define FF_STATUS_PLAYING 0x01
+#define FF_STATUS_MAX 0x01
+struct ff_replay {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 length;
+ __u16 delay;
+};
+struct ff_trigger {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 button;
+ __u16 interval;
+};
+struct ff_envelope {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 attack_length;
+ __u16 attack_level;
+ __u16 fade_length;
+ __u16 fade_level;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct ff_constant_effect {
+ __s16 level;
+ struct ff_envelope envelope;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct ff_ramp_effect {
+ __s16 start_level;
+ __s16 end_level;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct ff_envelope envelope;
+};
+struct ff_condition_effect {
+ __u16 right_saturation;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 left_saturation;
+ __s16 right_coeff;
+ __s16 left_coeff;
+ __u16 deadband;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __s16 center;
+};
+struct ff_periodic_effect {
+ __u16 waveform;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 period;
+ __s16 magnitude;
+ __s16 offset;
+ __u16 phase;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct ff_envelope envelope;
+ __u32 custom_len;
+ __s16 __user *custom_data;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct ff_rumble_effect {
+ __u16 strong_magnitude;
+ __u16 weak_magnitude;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct ff_effect {
+ __u16 type;
+ __s16 id;
+ __u16 direction;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct ff_trigger trigger;
+ struct ff_replay replay;
+ union {
+ struct ff_constant_effect constant;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct ff_ramp_effect ramp;
+ struct ff_periodic_effect periodic;
+ struct ff_condition_effect condition[2];
+ struct ff_rumble_effect rumble;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ } u;
+};
+#define FF_RUMBLE 0x50
+#define FF_PERIODIC 0x51
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_CONSTANT 0x52
+#define FF_SPRING 0x53
+#define FF_FRICTION 0x54
+#define FF_DAMPER 0x55
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_INERTIA 0x56
+#define FF_RAMP 0x57
+#define FF_EFFECT_MIN FF_RUMBLE
+#define FF_EFFECT_MAX FF_RAMP
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_SQUARE 0x58
+#define FF_TRIANGLE 0x59
+#define FF_SINE 0x5a
+#define FF_SAW_UP 0x5b
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_SAW_DOWN 0x5c
+#define FF_CUSTOM 0x5d
+#define FF_WAVEFORM_MIN FF_SQUARE
+#define FF_WAVEFORM_MAX FF_CUSTOM
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_GAIN 0x60
+#define FF_AUTOCENTER 0x61
+#define FF_MAX 0x7f
+#define FF_CNT (FF_MAX+1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#endif
diff --git a/widget/gonk/libui/sha1.c b/widget/gonk/libui/sha1.c
new file mode 100644
index 000000000..40a4ecaa3
--- /dev/null
+++ b/widget/gonk/libui/sha1.c
@@ -0,0 +1,289 @@
+/* $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $ */
+/* $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $ */
+
+/*
+ * SHA-1 in C
+ * By Steve Reid <steve@edmweb.com>
+ * 100% Public Domain
+ *
+ * Test Vectors (from FIPS PUB 180-1)
+ * "abc"
+ * A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+ * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ * 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+ * A million repetitions of "a"
+ * 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+ */
+
+#define SHA1HANDSOFF /* Copies data before messing with it. */
+
+#include <sys/cdefs.h>
+
+#if defined(_KERNEL) || defined(_STANDALONE)
+__KERNEL_RCSID(0, "$NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $");
+
+#include <lib/libkern/libkern.h>
+
+#else
+
+#if defined(LIBC_SCCS) && !defined(lint)
+__RCSID("$NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+#include <assert.h>
+#include <string.h>
+
+#endif
+
+#include <sys/types.h>
+#include "sha1.h"
+
+#define _DIAGASSERT assert
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#if !HAVE_SHA1_H
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/*
+ * blk0() and blk() perform the initial expand.
+ * I got the idea of expanding during the round function from SSLeay
+ */
+#if BYTE_ORDER == LITTLE_ENDIAN
+# define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+ |(rol(block->l[i],8)&0x00FF00FF))
+#else
+# define blk0(i) block->l[i]
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/*
+ * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
+ */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+
+#if !defined(_KERNEL) && !defined(_STANDALONE)
+#if defined(__weak_alias)
+__weak_alias(SHA1Transform,_SHA1Transform)
+__weak_alias(SHA1Init,_SHA1Init)
+__weak_alias(SHA1Update,_SHA1Update)
+__weak_alias(SHA1Final,_SHA1Final)
+#endif
+#endif
+
+typedef union {
+ uint8_t c[64];
+ uint32_t l[16];
+} CHAR64LONG16;
+
+/* old sparc64 gcc could not compile this */
+#undef SPARC64_GCC_WORKAROUND
+#if defined(__sparc64__) && defined(__GNUC__) && __GNUC__ < 3
+#define SPARC64_GCC_WORKAROUND
+#endif
+
+#ifdef SPARC64_GCC_WORKAROUND
+void do_R01(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *);
+void do_R2(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *);
+void do_R3(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *);
+void do_R4(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *);
+
+#define nR0(v,w,x,y,z,i) R0(*v,*w,*x,*y,*z,i)
+#define nR1(v,w,x,y,z,i) R1(*v,*w,*x,*y,*z,i)
+#define nR2(v,w,x,y,z,i) R2(*v,*w,*x,*y,*z,i)
+#define nR3(v,w,x,y,z,i) R3(*v,*w,*x,*y,*z,i)
+#define nR4(v,w,x,y,z,i) R4(*v,*w,*x,*y,*z,i)
+
+void
+do_R01(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *block)
+{
+ nR0(a,b,c,d,e, 0); nR0(e,a,b,c,d, 1); nR0(d,e,a,b,c, 2); nR0(c,d,e,a,b, 3);
+ nR0(b,c,d,e,a, 4); nR0(a,b,c,d,e, 5); nR0(e,a,b,c,d, 6); nR0(d,e,a,b,c, 7);
+ nR0(c,d,e,a,b, 8); nR0(b,c,d,e,a, 9); nR0(a,b,c,d,e,10); nR0(e,a,b,c,d,11);
+ nR0(d,e,a,b,c,12); nR0(c,d,e,a,b,13); nR0(b,c,d,e,a,14); nR0(a,b,c,d,e,15);
+ nR1(e,a,b,c,d,16); nR1(d,e,a,b,c,17); nR1(c,d,e,a,b,18); nR1(b,c,d,e,a,19);
+}
+
+void
+do_R2(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *block)
+{
+ nR2(a,b,c,d,e,20); nR2(e,a,b,c,d,21); nR2(d,e,a,b,c,22); nR2(c,d,e,a,b,23);
+ nR2(b,c,d,e,a,24); nR2(a,b,c,d,e,25); nR2(e,a,b,c,d,26); nR2(d,e,a,b,c,27);
+ nR2(c,d,e,a,b,28); nR2(b,c,d,e,a,29); nR2(a,b,c,d,e,30); nR2(e,a,b,c,d,31);
+ nR2(d,e,a,b,c,32); nR2(c,d,e,a,b,33); nR2(b,c,d,e,a,34); nR2(a,b,c,d,e,35);
+ nR2(e,a,b,c,d,36); nR2(d,e,a,b,c,37); nR2(c,d,e,a,b,38); nR2(b,c,d,e,a,39);
+}
+
+void
+do_R3(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *block)
+{
+ nR3(a,b,c,d,e,40); nR3(e,a,b,c,d,41); nR3(d,e,a,b,c,42); nR3(c,d,e,a,b,43);
+ nR3(b,c,d,e,a,44); nR3(a,b,c,d,e,45); nR3(e,a,b,c,d,46); nR3(d,e,a,b,c,47);
+ nR3(c,d,e,a,b,48); nR3(b,c,d,e,a,49); nR3(a,b,c,d,e,50); nR3(e,a,b,c,d,51);
+ nR3(d,e,a,b,c,52); nR3(c,d,e,a,b,53); nR3(b,c,d,e,a,54); nR3(a,b,c,d,e,55);
+ nR3(e,a,b,c,d,56); nR3(d,e,a,b,c,57); nR3(c,d,e,a,b,58); nR3(b,c,d,e,a,59);
+}
+
+void
+do_R4(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *block)
+{
+ nR4(a,b,c,d,e,60); nR4(e,a,b,c,d,61); nR4(d,e,a,b,c,62); nR4(c,d,e,a,b,63);
+ nR4(b,c,d,e,a,64); nR4(a,b,c,d,e,65); nR4(e,a,b,c,d,66); nR4(d,e,a,b,c,67);
+ nR4(c,d,e,a,b,68); nR4(b,c,d,e,a,69); nR4(a,b,c,d,e,70); nR4(e,a,b,c,d,71);
+ nR4(d,e,a,b,c,72); nR4(c,d,e,a,b,73); nR4(b,c,d,e,a,74); nR4(a,b,c,d,e,75);
+ nR4(e,a,b,c,d,76); nR4(d,e,a,b,c,77); nR4(c,d,e,a,b,78); nR4(b,c,d,e,a,79);
+}
+#endif
+
+/*
+ * Hash a single 512-bit block. This is the core of the algorithm.
+ */
+void SHA1Transform(uint32_t state[5], const uint8_t buffer[64])
+{
+ uint32_t a, b, c, d, e;
+ CHAR64LONG16 *block;
+
+#ifdef SHA1HANDSOFF
+ CHAR64LONG16 workspace;
+#endif
+
+ _DIAGASSERT(buffer != 0);
+ _DIAGASSERT(state != 0);
+
+#ifdef SHA1HANDSOFF
+ block = &workspace;
+ (void)memcpy(block, buffer, 64);
+#else
+ block = (CHAR64LONG16 *)(void *)buffer;
+#endif
+
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+
+#ifdef SPARC64_GCC_WORKAROUND
+ do_R01(&a, &b, &c, &d, &e, block);
+ do_R2(&a, &b, &c, &d, &e, block);
+ do_R3(&a, &b, &c, &d, &e, block);
+ do_R4(&a, &b, &c, &d, &e, block);
+#else
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+#endif
+
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+}
+
+
+/*
+ * SHA1Init - Initialize new context
+ */
+void SHA1Init(SHA1_CTX *context)
+{
+
+ _DIAGASSERT(context != 0);
+
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/*
+ * Run your data through this.
+ */
+void SHA1Update(SHA1_CTX *context, const uint8_t *data, unsigned int len)
+{
+ unsigned int i, j;
+
+ _DIAGASSERT(context != 0);
+ _DIAGASSERT(data != 0);
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j)
+ context->count[1] += (len>>29)+1;
+ j = (j >> 3) & 63;
+ if ((j + len) > 63) {
+ (void)memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64)
+ SHA1Transform(context->state, &data[i]);
+ j = 0;
+ } else {
+ i = 0;
+ }
+ (void)memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/*
+ * Add padding and return the message digest.
+ */
+void SHA1Final(uint8_t digest[20], SHA1_CTX *context)
+{
+ unsigned int i;
+ uint8_t finalcount[8];
+
+ _DIAGASSERT(digest != 0);
+ _DIAGASSERT(context != 0);
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (uint8_t)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
+ }
+ SHA1Update(context, (const uint8_t *)"\200", 1);
+ while ((context->count[0] & 504) != 448)
+ SHA1Update(context, (const uint8_t *)"\0", 1);
+ SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
+
+ if (digest) {
+ for (i = 0; i < 20; i++)
+ digest[i] = (uint8_t)
+ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+ }
+}
+
+#endif /* HAVE_SHA1_H */
diff --git a/widget/gonk/libui/sha1.h b/widget/gonk/libui/sha1.h
new file mode 100644
index 000000000..f7ada46a5
--- /dev/null
+++ b/widget/gonk/libui/sha1.h
@@ -0,0 +1,31 @@
+/* $NetBSD: sha1.h,v 1.13 2005/12/26 18:41:36 perry Exp $ */
+
+/*
+ * SHA-1 in C
+ * By Steve Reid <steve@edmweb.com>
+ * 100% Public Domain
+ */
+
+#ifndef _SYS_SHA1_H_
+#define _SYS_SHA1_H_
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#define SHA1_DIGEST_LENGTH 20
+#define SHA1_DIGEST_STRING_LENGTH 41
+
+typedef struct {
+ uint32_t state[5];
+ uint32_t count[2];
+ u_char buffer[64];
+} SHA1_CTX;
+
+__BEGIN_DECLS
+void SHA1Transform(uint32_t[5], const u_char[64]);
+void SHA1Init(SHA1_CTX *);
+void SHA1Update(SHA1_CTX *, const u_char *, u_int);
+void SHA1Final(u_char[SHA1_DIGEST_LENGTH], SHA1_CTX *);
+__END_DECLS
+
+#endif /* _SYS_SHA1_H_ */
diff --git a/widget/gonk/moz.build b/widget/gonk/moz.build
new file mode 100644
index 000000000..d539dd9a0
--- /dev/null
+++ b/widget/gonk/moz.build
@@ -0,0 +1,96 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# Copyright 2013 Mozilla Foundation and Mozilla contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+EXPORTS += [
+ 'GeckoTouchDispatcher.h',
+ 'GonkPermission.h',
+ 'OrientationObserver.h',
+]
+
+DIRS += ['libdisplay', 'nativewindow']
+
+# libui files
+SOURCES += ['libui/' + src for src in [
+ 'EventHub.cpp',
+ 'Input.cpp',
+ 'InputApplication.cpp',
+ 'InputDevice.cpp',
+ 'InputDispatcher.cpp',
+ 'InputListener.cpp',
+ 'InputReader.cpp',
+ 'InputTransport.cpp',
+ 'InputWindow.cpp',
+ 'Keyboard.cpp',
+ 'KeyCharacterMap.cpp',
+ 'KeyLayoutMap.cpp',
+ 'PointerController.cpp',
+ 'sha1.c',
+ 'SpriteController.cpp',
+ 'Tokenizer.cpp',
+ 'VelocityControl.cpp',
+ 'VelocityTracker.cpp',
+ 'VirtualKeyMap.cpp',
+]]
+
+# HwcHAL files
+if CONFIG['ANDROID_VERSION'] >= '17':
+ SOURCES += [
+ 'hwchal/HwcHAL.cpp',
+ ]
+
+SOURCES += [
+ 'GeckoTouchDispatcher.cpp',
+ 'GfxInfo.cpp',
+ 'GonkClipboardData.cpp',
+ 'GonkMemoryPressureMonitoring.cpp',
+ 'GonkPermission.cpp',
+ 'HwcComposer2D.cpp',
+ 'HwcUtils.cpp',
+ 'nsAppShell.cpp',
+ 'nsClipboard.cpp',
+ 'nsIdleServiceGonk.cpp',
+ 'nsLookAndFeel.cpp',
+ 'nsScreenManagerGonk.cpp',
+ 'nsWidgetFactory.cpp',
+ 'nsWindow.cpp',
+ 'OrientationObserver.cpp',
+ 'ProcessOrientation.cpp',
+ 'WidgetTraceEvent.cpp'
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/system/android',
+ '/gfx/skia/skia/include/config',
+ '/gfx/skia/skia/include/core',
+ '/image',
+ '/widget',
+]
+
+DEFINES['HAVE_OFF64_T'] = True
+DEFINES['SK_BUILD_FOR_ANDROID_NDK'] = True
+DEFINES['HAVE_POSIX_CLOCKS'] = True
+
+LOCAL_INCLUDES += [
+ '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
+ 'frameworks/native/opengl/include',
+ 'hardware/libhardware/include',
+ 'hardware/libhardware_legacy/include',
+ ]
+]
diff --git a/widget/gonk/nativewindow/FakeSurfaceComposer.cpp b/widget/gonk/nativewindow/FakeSurfaceComposer.cpp
new file mode 100644
index 000000000..7e4a2a9d8
--- /dev/null
+++ b/widget/gonk/nativewindow/FakeSurfaceComposer.cpp
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FakeSurfaceComposer"
+//#define LOG_NDEBUG 0
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <cutils/atomic.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <private/android_filesystem_config.h>
+
+#include <gui/IDisplayEventConnection.h>
+#include <gui/GraphicBufferAlloc.h>
+#include <gui/Surface.h>
+#include <ui/DisplayInfo.h>
+
+#if ANDROID_VERSION >= 21
+#include <ui/Rect.h>
+#endif
+
+#include "../libdisplay/GonkDisplay.h"
+#include "../nsScreenManagerGonk.h"
+#include "FakeSurfaceComposer.h"
+#include "gfxPrefs.h"
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+namespace android {
+
+/* static */
+void FakeSurfaceComposer::instantiate() {
+ defaultServiceManager()->addService(
+ String16("SurfaceFlinger"), new FakeSurfaceComposer());
+}
+
+FakeSurfaceComposer::FakeSurfaceComposer()
+ : BnSurfaceComposer()
+{
+}
+
+FakeSurfaceComposer::~FakeSurfaceComposer()
+{
+}
+
+status_t FakeSurfaceComposer::onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags)
+{
+ switch (code) {
+ case CREATE_CONNECTION:
+ case CREATE_DISPLAY:
+ case SET_TRANSACTION_STATE:
+ case CAPTURE_SCREEN:
+ {
+ // codes that require permission check
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int pid = ipc->getCallingPid();
+ const int uid = ipc->getCallingUid();
+ // Accept request only when uid is root.
+ if (uid != AID_ROOT) {
+ ALOGE("Permission Denial: "
+ "can't access SurfaceFlinger pid=%d, uid=%d", pid, uid);
+ return PERMISSION_DENIED;
+ }
+ break;
+ }
+ }
+
+ return BnSurfaceComposer::onTransact(code, data, reply, flags);
+}
+
+sp<ISurfaceComposerClient> FakeSurfaceComposer::createConnection()
+{
+ return nullptr;
+}
+
+sp<IGraphicBufferAlloc> FakeSurfaceComposer::createGraphicBufferAlloc()
+{
+ sp<GraphicBufferAlloc> gba(new GraphicBufferAlloc());
+ return gba;
+}
+
+class DestroyDisplayRunnable : public Runnable {
+public:
+ DestroyDisplayRunnable(FakeSurfaceComposer* aComposer, ssize_t aIndex)
+ : mComposer(aComposer), mIndex(aIndex) { }
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+ Mutex::Autolock _l(mComposer->mStateLock);
+ RefPtr<nsScreenManagerGonk> screenManager =
+ nsScreenManagerGonk::GetInstance();
+ screenManager->RemoveScreen(GonkDisplay::DISPLAY_VIRTUAL);
+ mComposer->mDisplays.removeItemsAt(mIndex);
+ return NS_OK;
+ }
+ sp<FakeSurfaceComposer> mComposer;
+ ssize_t mIndex;
+};
+
+sp<IBinder> FakeSurfaceComposer::createDisplay(const String8& displayName,
+ bool secure)
+{
+#if ANDROID_VERSION >= 19
+ class DisplayToken : public BBinder {
+ sp<FakeSurfaceComposer> composer;
+ virtual ~DisplayToken() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+ // no more references, this display must be terminated
+ Mutex::Autolock _l(composer->mStateLock);
+ ssize_t idx = composer->mDisplays.indexOfKey(this);
+ if (idx >= 0) {
+ nsCOMPtr<nsIRunnable> task(new DestroyDisplayRunnable(composer.get(), idx));
+ NS_DispatchToMainThread(task);
+ }
+ }
+ public:
+ DisplayToken(const sp<FakeSurfaceComposer>& composer)
+ : composer(composer) {
+ }
+ };
+
+ sp<BBinder> token = new DisplayToken(this);
+
+ Mutex::Autolock _l(mStateLock);
+ DisplayDeviceState info(HWC_DISPLAY_VIRTUAL);
+ info.displayName = displayName;
+ info.displayId = GonkDisplay::DISPLAY_VIRTUAL;
+ info.isSecure = secure;
+ mDisplays.add(token, info);
+ return token;
+#else
+ return nullptr;
+#endif
+}
+
+#if ANDROID_VERSION >= 19
+void FakeSurfaceComposer::destroyDisplay(const sp<IBinder>& display)
+{
+ Mutex::Autolock _l(mStateLock);
+
+ ssize_t idx = mDisplays.indexOfKey(display);
+ if (idx < 0) {
+ ALOGW("destroyDisplay: invalid display token");
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> task(new DestroyDisplayRunnable(this, idx));
+ NS_DispatchToMainThread(task);
+}
+#endif
+
+sp<IBinder> FakeSurfaceComposer::getBuiltInDisplay(int32_t id)
+{
+ // support only primary display
+ if (uint32_t(id) != HWC_DISPLAY_PRIMARY) {
+ return NULL;
+ }
+
+ if (!mPrimaryDisplay.get()) {
+ mPrimaryDisplay = new BBinder();
+ }
+ return mPrimaryDisplay;
+}
+
+void FakeSurfaceComposer::setTransactionState(
+ const Vector<ComposerState>& state,
+ const Vector<DisplayState>& displays,
+ uint32_t flags)
+{
+ Mutex::Autolock _l(mStateLock);
+ size_t count = displays.size();
+ for (size_t i=0 ; i<count ; i++) {
+ const DisplayState& s(displays[i]);
+ setDisplayStateLocked(s);
+ }
+}
+
+uint32_t FakeSurfaceComposer::setDisplayStateLocked(const DisplayState& s)
+{
+ ssize_t dpyIdx = mDisplays.indexOfKey(s.token);
+ if (dpyIdx < 0) {
+ return 0;
+ }
+
+ uint32_t flags = 0;
+ DisplayDeviceState& disp(mDisplays.editValueAt(dpyIdx));
+
+ if (!disp.isValid()) {
+ return 0;
+ }
+
+ const uint32_t what = s.what;
+ if (what & DisplayState::eSurfaceChanged) {
+ if (disp.surface->asBinder() != s.surface->asBinder()) {
+ disp.surface = s.surface;
+ flags |= eDisplayTransactionNeeded;
+ }
+ }
+ if (what & DisplayState::eLayerStackChanged) {
+ if (disp.layerStack != s.layerStack) {
+ disp.layerStack = s.layerStack;
+ flags |= eDisplayTransactionNeeded;
+ }
+ }
+ if (what & DisplayState::eDisplayProjectionChanged) {
+ if (disp.orientation != s.orientation) {
+ disp.orientation = s.orientation;
+ flags |= eDisplayTransactionNeeded;
+ }
+ if (disp.frame != s.frame) {
+ disp.frame = s.frame;
+ flags |= eDisplayTransactionNeeded;
+ }
+ if (disp.viewport != s.viewport) {
+ disp.viewport = s.viewport;
+ flags |= eDisplayTransactionNeeded;
+ }
+ }
+#if ANDROID_VERSION >= 21
+ if (what & DisplayState::eDisplaySizeChanged) {
+ if (disp.width != s.width) {
+ disp.width = s.width;
+ flags |= eDisplayTransactionNeeded;
+ }
+ if (disp.height != s.height) {
+ disp.height = s.height;
+ flags |= eDisplayTransactionNeeded;
+ }
+ }
+#endif
+
+ if (what & DisplayState::eSurfaceChanged) {
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([&]() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+ RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
+ screenManager->AddScreen(GonkDisplay::DISPLAY_VIRTUAL, disp.surface.get());
+ });
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC);
+ }
+
+ return flags;
+}
+
+void FakeSurfaceComposer::bootFinished()
+{
+}
+
+bool FakeSurfaceComposer::authenticateSurfaceTexture(
+ const sp<IGraphicBufferProducer>& bufferProducer) const {
+ return false;
+}
+
+sp<IDisplayEventConnection> FakeSurfaceComposer::createDisplayEventConnection() {
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+// Capture screen into an IGraphiBufferProducer
+// ---------------------------------------------------------------------------
+
+class Barrier {
+public:
+ inline Barrier() : state(CLOSED) { }
+ inline ~Barrier() { }
+
+ // Release any threads waiting at the Barrier.
+ // Provides release semantics: preceding loads and stores will be visible
+ // to other threads before they wake up.
+ void open() {
+ Mutex::Autolock _l(lock);
+ state = OPENED;
+ cv.broadcast();
+ }
+
+ // Reset the Barrier, so wait() will block until open() has been called.
+ void close() {
+ Mutex::Autolock _l(lock);
+ state = CLOSED;
+ }
+
+ // Wait until the Barrier is OPEN.
+ // Provides acquire semantics: no subsequent loads or stores will occur
+ // until wait() returns.
+ void wait() const {
+ Mutex::Autolock _l(lock);
+ while (state == CLOSED) {
+ cv.wait(lock);
+ }
+ }
+private:
+ enum { OPENED, CLOSED };
+ mutable Mutex lock;
+ mutable Condition cv;
+ volatile int state;
+};
+
+/* The code below is here to handle b/8734824
+ *
+ * We create a IGraphicBufferProducer wrapper that forwards all calls
+ * to the calling binder thread, where they are executed. This allows
+ * the calling thread to be reused (on the other side) and not
+ * depend on having "enough" binder threads to handle the requests.
+ *
+ */
+
+class GraphicProducerWrapper : public BBinder, public MessageHandler {
+ sp<IGraphicBufferProducer> impl;
+ sp<Looper> looper;
+ status_t result;
+ bool exitPending;
+ bool exitRequested;
+ mutable Barrier barrier;
+ volatile int32_t memoryBarrier;
+ uint32_t code;
+ Parcel const* data;
+ Parcel* reply;
+
+ enum {
+ MSG_API_CALL,
+ MSG_EXIT
+ };
+
+ /*
+ * this is called by our "fake" BpGraphicBufferProducer. We package the
+ * data and reply Parcel and forward them to the calling thread.
+ */
+ virtual status_t transact(uint32_t code,
+ const Parcel& data, Parcel* reply, uint32_t flags) {
+ this->code = code;
+ this->data = &data;
+ this->reply = reply;
+ android_atomic_acquire_store(0, &memoryBarrier);
+ if (exitPending) {
+ // if we've exited, we run the message synchronously right here
+ handleMessage(Message(MSG_API_CALL));
+ } else {
+ barrier.close();
+ looper->sendMessage(this, Message(MSG_API_CALL));
+ barrier.wait();
+ }
+ return NO_ERROR;
+ }
+
+ /*
+ * here we run on the binder calling thread. All we've got to do is
+ * call the real BpGraphicBufferProducer.
+ */
+ virtual void handleMessage(const Message& message) {
+ android_atomic_release_load(&memoryBarrier);
+ if (message.what == MSG_API_CALL) {
+ impl->asBinder()->transact(code, data[0], reply);
+ barrier.open();
+ } else if (message.what == MSG_EXIT) {
+ exitRequested = true;
+ }
+ }
+
+public:
+ GraphicProducerWrapper(const sp<IGraphicBufferProducer>& impl) :
+ impl(impl), looper(new Looper(true)), result(NO_ERROR),
+ exitPending(false), exitRequested(false) {
+ }
+
+ status_t waitForResponse() {
+ do {
+ looper->pollOnce(-1);
+ } while (!exitRequested);
+ return result;
+ }
+
+ void exit(status_t result) {
+ this->result = result;
+ exitPending = true;
+ looper->sendMessage(this, Message(MSG_EXIT));
+ }
+};
+
+status_t
+FakeSurfaceComposer::captureScreen(const sp<IBinder>& display
+ , const sp<IGraphicBufferProducer>& producer
+#if ANDROID_VERSION >= 21
+ , Rect sourceCrop
+#endif
+ , uint32_t reqWidth
+ , uint32_t reqHeight
+ , uint32_t minLayerZ
+ , uint32_t maxLayerZ
+#if ANDROID_VERSION >= 21
+ , bool useIdentityTransform
+ , Rotation rotation
+#elif ANDROID_VERSION < 19
+ , bool isCpuConsumer
+#endif
+ )
+{
+ if (display == 0 || producer == 0) {
+ return BAD_VALUE;
+ }
+
+ // Limit only to primary display
+ if (display != mPrimaryDisplay) {
+ return BAD_VALUE;
+ }
+
+ // this creates a "fake" BBinder which will serve as a "fake" remote
+ // binder to receive the marshaled calls and forward them to the
+ // real remote (a BpGraphicBufferProducer)
+ sp<GraphicProducerWrapper> wrapper = new GraphicProducerWrapper(producer);
+ // the asInterface() call below creates our "fake" BpGraphicBufferProducer
+ // which does the marshaling work forwards to our "fake remote" above.
+ sp<IGraphicBufferProducer> fakeProducer = IGraphicBufferProducer::asInterface(wrapper);
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([&]() {
+ captureScreenImp(fakeProducer, reqWidth, reqHeight, wrapper.get());
+ });
+ NS_DispatchToMainThread(runnable);
+
+ status_t result = wrapper->waitForResponse();
+
+ return result;
+}
+
+class RunnableCallTask final : public Runnable
+{
+public:
+ explicit RunnableCallTask(nsIRunnable* aRunnable)
+ : mRunnable(aRunnable) {}
+
+ NS_IMETHOD Run() override
+ {
+ return mRunnable->Run();
+ }
+protected:
+ nsCOMPtr<nsIRunnable> mRunnable;
+};
+
+void
+FakeSurfaceComposer::captureScreenImp(const sp<IGraphicBufferProducer>& producer,
+ uint32_t reqWidth,
+ uint32_t reqHeight,
+ const sp<GraphicProducerWrapper>& wrapper)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(wrapper.get());
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+
+ // get screen geometry
+ nsIntRect screenBounds = screen->GetNaturalBounds().ToUnknownRect();
+ const uint32_t hw_w = screenBounds.width;
+ const uint32_t hw_h = screenBounds.height;
+
+ if (reqWidth > hw_w || reqHeight > hw_h) {
+ ALOGE("size mismatch (%d, %d) > (%d, %d)",
+ reqWidth, reqHeight, hw_w, hw_h);
+ static_cast<GraphicProducerWrapper*>(producer->asBinder().get())->exit(BAD_VALUE);
+ return;
+ }
+
+ reqWidth = (!reqWidth) ? hw_w : reqWidth;
+ reqHeight = (!reqHeight) ? hw_h : reqHeight;
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([screen, reqWidth, reqHeight, producer, wrapper]() {
+ // create a surface (because we're a producer, and we need to
+ // dequeue/queue a buffer)
+ sp<Surface> sur = new Surface(producer);
+ ANativeWindow* window = sur.get();
+ // The closure makes screen const and we can't call forget() on it.
+ RefPtr<nsScreenGonk> screenAlias = screen;
+
+ if (native_window_api_connect(window, NATIVE_WINDOW_API_EGL) != NO_ERROR) {
+ static_cast<GraphicProducerWrapper*>(producer->asBinder().get())->exit(BAD_VALUE);
+ NS_ReleaseOnMainThread(screenAlias.forget());
+ return;
+ }
+ uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
+
+ int err = 0;
+ err = native_window_set_buffers_dimensions(window, reqWidth, reqHeight);
+ err |= native_window_set_scaling_mode(window, NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
+ err |= native_window_set_buffers_format(window, HAL_PIXEL_FORMAT_RGBA_8888);
+ err |= native_window_set_usage(window, usage);
+
+ status_t result = NO_ERROR;
+ if (err == NO_ERROR) {
+ ANativeWindowBuffer* buffer;
+ result = native_window_dequeue_buffer_and_wait(window, &buffer);
+ if (result == NO_ERROR) {
+ nsresult rv = screen->MakeSnapshot(buffer);
+ if (rv != NS_OK) {
+ result = INVALID_OPERATION;
+ }
+ window->queueBuffer(window, buffer, -1);
+ }
+ } else {
+ result = BAD_VALUE;
+ }
+ native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
+ static_cast<GraphicProducerWrapper*>(producer->asBinder().get())->exit(result);
+ NS_ReleaseOnMainThread(screenAlias.forget());
+ });
+
+ layers::CompositorThreadHolder::Loop()->PostTask(
+ MakeAndAddRef<RunnableCallTask>(runnable));
+}
+
+#if ANDROID_VERSION >= 21
+void
+FakeSurfaceComposer::setPowerMode(const sp<IBinder>& display, int mode)
+{
+}
+
+status_t
+FakeSurfaceComposer::getDisplayConfigs(const sp<IBinder>& display, Vector<DisplayInfo>* configs)
+{
+ if (configs == NULL) {
+ return BAD_VALUE;
+ }
+
+ // Limit DisplayConfigs only to primary display
+ if (!display.get() || display != mPrimaryDisplay) {
+ return NAME_NOT_FOUND;
+ }
+
+ configs->clear();
+ DisplayInfo info = DisplayInfo();
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([&]() {
+ MOZ_ASSERT(NS_IsMainThread());
+ getPrimaryDisplayInfo(&info);
+ });
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC);
+
+ configs->push_back(info);
+ return NO_ERROR;
+}
+
+status_t
+FakeSurfaceComposer::getDisplayStats(const sp<IBinder>& display, DisplayStatInfo* stats)
+{
+ return INVALID_OPERATION;
+}
+
+int
+FakeSurfaceComposer::getActiveConfig(const sp<IBinder>& display)
+{
+ // Only support primary display.
+ if (display.get() && (display == mPrimaryDisplay)) {
+ return 0;
+ }
+ return INVALID_OPERATION;
+}
+
+status_t
+FakeSurfaceComposer::setActiveConfig(const sp<IBinder>& display, int id)
+{
+ return INVALID_OPERATION;
+}
+
+status_t
+FakeSurfaceComposer::clearAnimationFrameStats()
+{
+ return INVALID_OPERATION;
+}
+
+status_t
+FakeSurfaceComposer::getAnimationFrameStats(FrameStats* outStats) const
+{
+ return INVALID_OPERATION;
+}
+#else
+void
+FakeSurfaceComposer::blank(const sp<IBinder>& display)
+{
+}
+
+void
+FakeSurfaceComposer::unblank(const sp<IBinder>& display)
+{
+}
+
+status_t
+FakeSurfaceComposer::getDisplayInfo(const sp<IBinder>& display, DisplayInfo* info)
+{
+ if (info == NULL) {
+ return BAD_VALUE;
+ }
+
+ // Limit DisplayConfigs only to primary display
+ if (!display.get() || display != mPrimaryDisplay) {
+ return NAME_NOT_FOUND;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([&]() {
+ MOZ_ASSERT(NS_IsMainThread());
+ getPrimaryDisplayInfo(info);
+ });
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC);
+
+ return NO_ERROR;
+}
+#endif
+
+#define VSYNC_EVENT_PHASE_OFFSET_NS 0
+#define SF_VSYNC_EVENT_PHASE_OFFSET_NS 0
+
+void
+FakeSurfaceComposer::getPrimaryDisplayInfo(DisplayInfo* info)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+
+ // Implementation mimic android SurfaceFlinger::getDisplayConfigs().
+
+ class Density {
+ static int getDensityFromProperty(char const* propName) {
+ char property[PROPERTY_VALUE_MAX];
+ int density = 0;
+ if (property_get(propName, property, NULL) > 0) {
+ density = atoi(property);
+ }
+ return density;
+ }
+ public:
+ static int getEmuDensity() {
+ return getDensityFromProperty("qemu.sf.lcd_density"); }
+ static int getBuildDensity() {
+ return getDensityFromProperty("ro.sf.lcd_density"); }
+ };
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+
+ float xdpi = screen->GetDpi();
+ float ydpi = screen->GetDpi();
+ int fps = 60; // XXX set a value from hwc hal
+ nsIntRect screenBounds = screen->GetNaturalBounds().ToUnknownRect();
+
+ // The density of the device is provided by a build property
+ float density = Density::getBuildDensity() / 160.0f;
+ if (density == 0) {
+ // the build doesn't provide a density -- this is wrong!
+ // use xdpi instead
+ ALOGE("ro.sf.lcd_density must be defined as a build property");
+ density = xdpi / 160.0f;
+ }
+ info->density = density;
+ info->orientation = screen->EffectiveScreenRotation();
+
+ info->w = screenBounds.width;
+ info->h = screenBounds.height;
+ info->xdpi = xdpi;
+ info->ydpi = ydpi;
+ info->fps = fps;
+#if ANDROID_VERSION >= 21
+ info->appVsyncOffset = VSYNC_EVENT_PHASE_OFFSET_NS;
+
+ // This is how far in advance a buffer must be queued for
+ // presentation at a given time. If you want a buffer to appear
+ // on the screen at time N, you must submit the buffer before
+ // (N - presentationDeadline).
+ //
+ // Normally it's one full refresh period (to give SF a chance to
+ // latch the buffer), but this can be reduced by configuring a
+ // DispSync offset. Any additional delays introduced by the hardware
+ // composer or panel must be accounted for here.
+ //
+ // We add an additional 1ms to allow for processing time and
+ // differences between the ideal and actual refresh rate.
+ info->presentationDeadline =
+ (1e9 / fps) - SF_VSYNC_EVENT_PHASE_OFFSET_NS + 1000000;
+#endif
+ // All non-virtual displays are currently considered secure.
+ info->secure = true;
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/FakeSurfaceComposer.h b/widget/gonk/nativewindow/FakeSurfaceComposer.h
new file mode 100644
index 000000000..97a717444
--- /dev/null
+++ b/widget/gonk/nativewindow/FakeSurfaceComposer.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_FAKE_SURFACE_COMPOSER_H
+#define NATIVEWINDOW_FAKE_SURFACE_COMPOSER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/Looper.h>
+
+#include <binder/BinderService.h>
+
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/ISurfaceComposerClient.h>
+#include <hardware/hwcomposer.h>
+#include <private/gui/LayerState.h>
+#include <utils/KeyedVector.h>
+
+class nsIWidget;
+
+namespace android {
+
+// ---------------------------------------------------------------------------
+
+class GraphicProducerWrapper;
+class IGraphicBufferAlloc;
+
+enum {
+ eTransactionNeeded = 0x01,
+ eTraversalNeeded = 0x02,
+ eDisplayTransactionNeeded = 0x04,
+ eTransactionMask = 0x07
+};
+
+class FakeSurfaceComposer : public BinderService<FakeSurfaceComposer>,
+ public BnSurfaceComposer
+{
+public:
+ static char const* getServiceName() {
+ return "FakeSurfaceComposer";
+ }
+
+ // Instantiate FakeSurfaceComposer and register to service manager.
+ // If service manager is not present, wait until service manager becomes present.
+ static void instantiate();
+
+#if ANDROID_VERSION >= 19
+ virtual void destroyDisplay(const sp<android::IBinder>& display);
+#endif
+
+#if ANDROID_VERSION >= 21
+ virtual status_t captureScreen(const sp<IBinder>& display,
+ const sp<IGraphicBufferProducer>& producer,
+ Rect sourceCrop, uint32_t reqWidth, uint32_t reqHeight,
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform,
+ Rotation rotation = eRotateNone);
+#elif ANDROID_VERSION >= 19
+ virtual status_t captureScreen(const sp<IBinder>& display,
+ const sp<IGraphicBufferProducer>& producer,
+ uint32_t reqWidth, uint32_t reqHeight,
+ uint32_t minLayerZ, uint32_t maxLayerZ);
+#else
+ virtual status_t captureScreen(const sp<IBinder>& display,
+ const sp<IGraphicBufferProducer>& producer,
+ uint32_t reqWidth, uint32_t reqHeight,
+ uint32_t minLayerZ, uint32_t maxLayerZ, bool isCpuConsumer);
+#endif
+
+private:
+ FakeSurfaceComposer();
+ // We're reference counted, never destroy FakeSurfaceComposer directly
+ virtual ~FakeSurfaceComposer();
+
+ /* ------------------------------------------------------------------------
+ * IBinder interface
+ */
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags);
+
+ /* ------------------------------------------------------------------------
+ * ISurfaceComposer interface
+ */
+ virtual sp<ISurfaceComposerClient> createConnection();
+ virtual sp<IGraphicBufferAlloc> createGraphicBufferAlloc();
+ virtual sp<IBinder> createDisplay(const String8& displayName, bool secure);
+ virtual sp<IBinder> getBuiltInDisplay(int32_t id);
+ virtual void setTransactionState(const Vector<ComposerState>& state,
+ const Vector<DisplayState>& displays, uint32_t flags);
+ virtual void bootFinished();
+ virtual bool authenticateSurfaceTexture(
+ const sp<IGraphicBufferProducer>& bufferProducer) const;
+ virtual sp<IDisplayEventConnection> createDisplayEventConnection();
+#if ANDROID_VERSION >= 21
+ virtual void setPowerMode(const sp<IBinder>& display, int mode);
+ virtual status_t getDisplayConfigs(const sp<IBinder>& display, Vector<DisplayInfo>* configs);
+ virtual status_t getDisplayStats(const sp<IBinder>& display, DisplayStatInfo* stats);
+ virtual int getActiveConfig(const sp<IBinder>& display);
+ virtual status_t setActiveConfig(const sp<IBinder>& display, int id);
+ virtual status_t clearAnimationFrameStats();
+ virtual status_t getAnimationFrameStats(FrameStats* outStats) const;
+#elif ANDROID_VERSION >= 17
+ // called when screen needs to turn off
+ virtual void blank(const sp<IBinder>& display);
+ // called when screen is turning back on
+ virtual void unblank(const sp<IBinder>& display);
+ virtual status_t getDisplayInfo(const sp<IBinder>& display, DisplayInfo* info);
+#endif
+ void getPrimaryDisplayInfo(DisplayInfo* info);
+
+ /* ------------------------------------------------------------------------
+ * Transactions
+ */
+ uint32_t setDisplayStateLocked(const DisplayState& s);
+
+ void captureScreenImp(const sp<IGraphicBufferProducer>& producer,
+ uint32_t reqWidth,
+ uint32_t reqHeight,
+ const sp<GraphicProducerWrapper>& wrapper);
+
+ sp<IBinder> mPrimaryDisplay;
+
+ struct DisplayDeviceState {
+ enum {
+ NO_LAYER_STACK = 0xFFFFFFFF,
+ };
+ DisplayDeviceState()
+ : type(-1), displayId(-1), width(0), height(0) {
+ }
+ DisplayDeviceState(int type)
+ : type(type), displayId(-1), layerStack(NO_LAYER_STACK), orientation(0), width(0), height(0) {
+ viewport.makeInvalid();
+ frame.makeInvalid();
+ }
+ bool isValid() const { return type >= 0; }
+ int type;
+ int displayId;
+ sp<IGraphicBufferProducer> surface;
+ uint32_t layerStack;
+ Rect viewport;
+ Rect frame;
+ uint8_t orientation;
+ uint32_t width, height;
+ String8 displayName;
+ bool isSecure;
+ };
+
+ // access must be protected by mStateLock
+ mutable Mutex mStateLock;
+ DefaultKeyedVector<wp<IBinder>, DisplayDeviceState> mDisplays;
+
+ friend class DestroyDisplayRunnable;
+};
+
+// ---------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_FAKE_SURFACE_COMPOSER_H
diff --git a/widget/gonk/nativewindow/GonkBufferQueue.h b/widget/gonk/nativewindow/GonkBufferQueue.h
new file mode 100644
index 000000000..defdb0ae2
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueue.h
@@ -0,0 +1,22 @@
+/* Copyright 2013 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
+# include "GonkBufferQueueLL.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19
+# include "GonkBufferQueueKK.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+# include "GonkBufferQueueJB.h"
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueJB.cpp b/widget/gonk/nativewindow/GonkBufferQueueJB.cpp
new file mode 100644
index 000000000..81502f81e
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueJB.cpp
@@ -0,0 +1,1036 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GonkBufferQueue"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#define LOG_NDEBUG 0
+
+#define GL_GLEXT_PROTOTYPES
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <utils/Log.h>
+
+#include "mozilla/layers/GrallocTextureClient.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+
+#include "GonkBufferQueueJB.h"
+
+// Macros for including the GonkBufferQueue name in log messages
+#define ST_LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define ST_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ST_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ST_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define ST_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+#define ATRACE_BUFFER_INDEX(index)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+static const char* scalingModeName(int scalingMode) {
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP";
+ default: return "Unknown";
+ }
+}
+
+GonkBufferQueue::GonkBufferQueue(bool allowSynchronousMode,
+ const sp<IGraphicBufferAlloc>& allocator) :
+ mDefaultWidth(1),
+ mDefaultHeight(1),
+ mMaxAcquiredBufferCount(1),
+ mDefaultMaxBufferCount(2),
+ mOverrideMaxBufferCount(0),
+ mSynchronousMode(true),
+ mAllowSynchronousMode(allowSynchronousMode),
+ mConnectedApi(NO_CONNECTED_API),
+ mAbandoned(false),
+ mFrameCounter(0),
+ mBufferHasBeenQueued(false),
+ mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
+ mConsumerUsageBits(0),
+ mTransformHint(0)
+{
+ // Choose a name using the PID and a process-unique ID.
+ mConsumerName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ ST_LOGV("GonkBufferQueue");
+}
+
+GonkBufferQueue::~GonkBufferQueue() {
+ ST_LOGV("~GonkBufferQueue");
+}
+
+status_t GonkBufferQueue::setDefaultMaxBufferCountLocked(int count) {
+ if (count < 2 || count > NUM_BUFFER_SLOTS)
+ return BAD_VALUE;
+
+ mDefaultMaxBufferCount = count;
+ mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+bool GonkBufferQueue::isSynchronousMode() const {
+ Mutex::Autolock lock(mMutex);
+ return mSynchronousMode;
+}
+
+void GonkBufferQueue::setConsumerName(const String8& name) {
+ Mutex::Autolock lock(mMutex);
+ mConsumerName = name;
+}
+
+status_t GonkBufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock lock(mMutex);
+ mDefaultBufferFormat = defaultFormat;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setConsumerUsageBits(uint32_t usage) {
+ Mutex::Autolock lock(mMutex);
+ mConsumerUsageBits = usage;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setTransformHint(uint32_t hint) {
+ ST_LOGV("setTransformHint: %02x", hint);
+ Mutex::Autolock lock(mMutex);
+ mTransformHint = hint;
+ return NO_ERROR;
+}
+
+already_AddRefed<TextureClient>
+GonkBufferQueue::getTextureClientFromBuffer(ANativeWindowBuffer* buffer)
+{
+ Mutex::Autolock _l(mMutex);
+ if (buffer == NULL) {
+ ST_LOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return nullptr;
+ }
+
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) {
+ RefPtr<TextureClient> client(mSlots[i].mTextureClient);
+ return client.forget();
+ }
+ }
+ ST_LOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+ return nullptr;
+}
+
+int GonkBufferQueue::getSlotFromTextureClientLocked(
+ TextureClient* client) const
+{
+ if (client == NULL) {
+ ST_LOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return BAD_VALUE;
+ }
+
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mTextureClient == client) {
+ return i;
+ }
+ }
+ ST_LOGE("getSlotFromBufferLocked: unknown TextureClient: %p", client);
+ return BAD_VALUE;
+}
+
+
+status_t GonkBufferQueue::setBufferCount(int bufferCount) {
+ ST_LOGV("setBufferCount: count=%d", bufferCount);
+
+ sp<ConsumerListener> listener;
+ {
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("setBufferCount: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (bufferCount > NUM_BUFFER_SLOTS) {
+ ST_LOGE("setBufferCount: bufferCount too large (max %d)",
+ NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ // Error out if the user has dequeued buffers
+ int maxBufferCount = getMaxBufferCountLocked();
+ for (int i=0 ; i<maxBufferCount; i++) {
+ if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) {
+ ST_LOGE("setBufferCount: client owns some buffers");
+ return -EINVAL;
+ }
+ }
+
+ const int minBufferSlots = getMinMaxBufferCountLocked();
+ if (bufferCount == 0) {
+ mOverrideMaxBufferCount = 0;
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+ }
+
+ if (bufferCount < minBufferSlots) {
+ ST_LOGE("setBufferCount: requested buffer count (%d) is less than "
+ "minimum (%d)", bufferCount, minBufferSlots);
+ return BAD_VALUE;
+ }
+
+ // here we're guaranteed that the client doesn't have dequeued buffers
+ // and will release all of its buffer references.
+ //
+ // XXX: Should this use drainQueueAndFreeBuffersLocked instead?
+ freeAllBuffersLocked();
+ mOverrideMaxBufferCount = bufferCount;
+ mBufferHasBeenQueued = false;
+ mDequeueCondition.broadcast();
+ listener = mConsumerListener;
+ } // scope for lock
+
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return NO_ERROR;
+}
+
+int GonkBufferQueue::query(int what, int* outValue)
+{
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("query: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ int value;
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ value = mDefaultWidth;
+ break;
+ case NATIVE_WINDOW_HEIGHT:
+ value = mDefaultHeight;
+ break;
+ case NATIVE_WINDOW_FORMAT:
+ value = mDefaultBufferFormat;
+ break;
+ case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+ value = getMinUndequeuedBufferCountLocked();
+ break;
+ case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+ value = (mQueue.size() >= 2);
+ break;
+ default:
+ return BAD_VALUE;
+ }
+ outValue[0] = value;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
+ ST_LOGV("requestBuffer: slot=%d", slot);
+ Mutex::Autolock lock(mMutex);
+ if (mAbandoned) {
+ ST_LOGE("requestBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ int maxBufferCount = getMaxBufferCountLocked();
+ if (slot < 0 || maxBufferCount <= slot) {
+ ST_LOGE("requestBuffer: slot index out of range [0, %d]: %d",
+ maxBufferCount, slot);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+ // XXX: I vaguely recall there was some reason this can be valid, but
+ // for the life of me I can't recall under what circumstances that's
+ // the case.
+ ST_LOGE("requestBuffer: slot %d is not owned by the client (state=%d)",
+ slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+ mSlots[slot].mRequestBufferCalled = true;
+ *buf = mSlots[slot].mGraphicBuffer;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::dequeueBuffer(int *outBuf, sp<Fence>* outFence,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
+ ST_LOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage);
+
+ if ((w && !h) || (!w && h)) {
+ ST_LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h);
+ return BAD_VALUE;
+ }
+
+ status_t returnFlags(OK);
+ int buf = INVALID_BUFFER_SLOT;
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (format == 0) {
+ format = mDefaultBufferFormat;
+ }
+ // turn on usage bits the consumer requested
+ usage |= mConsumerUsageBits;
+
+ int found = -1;
+ int dequeuedCount = 0;
+ bool tryAgain = true;
+ while (tryAgain) {
+ if (mAbandoned) {
+ ST_LOGE("dequeueBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = getMaxBufferCountLocked();
+
+ // Free up any buffers that are in slots beyond the max buffer
+ // count.
+ //for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
+ // assert(mSlots[i].mBufferState == BufferSlot::FREE);
+ // if (mSlots[i].mGraphicBuffer != NULL) {
+ // freeBufferLocked(i);
+ // returnFlags |= IGraphicBufferProducer::RELEASE_ALL_BUFFERS;
+ // }
+ //}
+
+ // look for a free buffer to give to the client
+ found = INVALID_BUFFER_SLOT;
+ dequeuedCount = 0;
+ for (int i = 0; i < maxBufferCount; i++) {
+ const int state = mSlots[i].mBufferState;
+ if (state == BufferSlot::DEQUEUED) {
+ dequeuedCount++;
+ }
+
+ if (state == BufferSlot::FREE) {
+ /* We return the oldest of the free buffers to avoid
+ * stalling the producer if possible. This is because
+ * the consumer may still have pending reads of the
+ * buffers in flight.
+ */
+ if ((found < 0) ||
+ mSlots[i].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = i;
+ }
+ }
+ }
+
+ // clients are not allowed to dequeue more than one buffer
+ // if they didn't set a buffer count.
+ if (!mOverrideMaxBufferCount && dequeuedCount) {
+ ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without "
+ "setting the buffer count");
+ return -EINVAL;
+ }
+
+ // See whether a buffer has been queued since the last
+ // setBufferCount so we know whether to perform the min undequeued
+ // buffers check below.
+ if (mBufferHasBeenQueued) {
+ // make sure the client is not trying to dequeue more buffers
+ // than allowed.
+ const int newUndequeuedCount = maxBufferCount - (dequeuedCount+1);
+ const int minUndequeuedCount = getMinUndequeuedBufferCountLocked();
+ if (newUndequeuedCount < minUndequeuedCount) {
+ ST_LOGE("dequeueBuffer: min undequeued buffer count (%d) "
+ "exceeded (dequeued=%d undequeudCount=%d)",
+ minUndequeuedCount, dequeuedCount,
+ newUndequeuedCount);
+ return -EBUSY;
+ }
+ }
+
+ // If no buffer is found, wait for a buffer to be released or for
+ // the max buffer count to change.
+ tryAgain = found == INVALID_BUFFER_SLOT;
+ if (tryAgain) {
+ mDequeueCondition.wait(mMutex);
+ }
+ }
+
+
+ if (found == INVALID_BUFFER_SLOT) {
+ // This should not happen.
+ ST_LOGE("dequeueBuffer: no available buffer slots");
+ return -EBUSY;
+ }
+
+ buf = found;
+ *outBuf = found;
+
+ const bool useDefaultSize = !w && !h;
+ if (useDefaultSize) {
+ // use the default size
+ w = mDefaultWidth;
+ h = mDefaultHeight;
+ }
+
+ mSlots[buf].mBufferState = BufferSlot::DEQUEUED;
+
+ const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer);
+ if ((buffer == NULL) ||
+ (uint32_t(buffer->width) != w) ||
+ (uint32_t(buffer->height) != h) ||
+ (uint32_t(buffer->format) != format) ||
+ ((uint32_t(buffer->usage) & usage) != usage))
+ {
+ mSlots[buf].mAcquireCalled = false;
+ mSlots[buf].mGraphicBuffer = NULL;
+ mSlots[buf].mRequestBufferCalled = false;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ if (mSlots[buf].mTextureClient) {
+ mSlots[buf].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[buf].mTextureClient);
+ mSlots[buf].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ returnFlags |= IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION;
+ }
+
+ *outFence = mSlots[buf].mFence;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ } // end lock scope
+
+ sp<GraphicBuffer> graphicBuffer;
+ if (returnFlags & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+
+ usage |= GraphicBuffer::USAGE_HW_TEXTURE;
+ RefPtr<LayersIPCChannel> allocator = ImageBridgeChild::GetSingleton();
+ GrallocTextureData* texData = GrallocTextureData::Create(IntSize(w,h), format,
+ gfx::BackendType::NONE, usage,
+ allocator);
+ if (!texData) {
+ ST_LOGE("dequeueBuffer: failed to alloc gralloc buffer");
+ return -ENOMEM;
+ }
+ RefPtr<TextureClient> textureClient = new TextureClient(texData, TextureFlags::RECYCLE | TextureFlags::DEALLOCATE_CLIENT, allocator);
+ sp<GraphicBuffer> graphicBuffer = texData->GetGraphicBuffer();
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("dequeueBuffer: SurfaceTexture has been abandoned!");
+ return NO_INIT;
+ }
+
+ mSlots[buf].mGraphicBuffer = graphicBuffer;
+ mSlots[buf].mTextureClient = textureClient;
+ ST_LOGD("dequeueBuffer: returning slot=%d buf=%p ", buf,
+ mSlots[buf].mGraphicBuffer->handle);
+ //mSlots[*outBuf].mGraphicBuffer = graphicBuffer;
+ }
+ }
+
+ ST_LOGV("dequeueBuffer: returning slot=%d buf=%p flags=%#x", *outBuf,
+ mSlots[*outBuf].mGraphicBuffer->handle, returnFlags);
+
+ return returnFlags;
+}
+
+status_t GonkBufferQueue::setSynchronousMode(bool enabled) {
+ ST_LOGV("setSynchronousMode: enabled=%d", enabled);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("setSynchronousMode: BufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mSynchronousMode != enabled) {
+ mSynchronousMode = enabled;
+ mDequeueCondition.broadcast();
+ }
+ return OK;
+}
+
+status_t GonkBufferQueue::queueBuffer(int buf,
+ const QueueBufferInput& input, QueueBufferOutput* output) {
+
+ Rect crop;
+ uint32_t transform;
+ int scalingMode;
+ int64_t timestamp;
+ sp<Fence> fence;
+
+ input.deflate(&timestamp, &crop, &scalingMode, &transform, &fence);
+
+#if ANDROID_VERSION >= 18
+ if (fence == NULL) {
+ ST_LOGE("queueBuffer: fence is NULL");
+ return BAD_VALUE;
+ }
+#endif
+
+ ST_LOGV("queueBuffer: slot=%d time=%#llx crop=[%d,%d,%d,%d] tr=%#x "
+ "scale=%s",
+ buf, timestamp, crop.left, crop.top, crop.right, crop.bottom,
+ transform, scalingModeName(scalingMode));
+
+ sp<ConsumerListener> listener;
+
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+ if (mAbandoned) {
+ ST_LOGE("queueBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ int maxBufferCount = getMaxBufferCountLocked();
+ if (buf < 0 || buf >= maxBufferCount) {
+ ST_LOGE("queueBuffer: slot index out of range [0, %d]: %d",
+ maxBufferCount, buf);
+ return -EINVAL;
+ } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+ ST_LOGE("queueBuffer: slot %d is not owned by the client "
+ "(state=%d)", buf, mSlots[buf].mBufferState);
+ return -EINVAL;
+ } else if (!mSlots[buf].mRequestBufferCalled) {
+ ST_LOGE("queueBuffer: slot %d was enqueued without requesting a "
+ "buffer", buf);
+ return -EINVAL;
+ }
+
+ const sp<GraphicBuffer>& graphicBuffer(mSlots[buf].mGraphicBuffer);
+ Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
+ Rect croppedCrop;
+ crop.intersect(bufferRect, &croppedCrop);
+ if (croppedCrop != crop) {
+ ST_LOGE("queueBuffer: crop rect is not contained within the "
+ "buffer in slot %d", buf);
+ return -EINVAL;
+ }
+
+ if (mSynchronousMode) {
+ // In synchronous mode we queue all buffers in a FIFO.
+ mQueue.push_back(buf);
+ } else {
+ // In asynchronous mode we only keep the most recent buffer.
+ if (mQueue.empty()) {
+ mQueue.push_back(buf);
+ } else {
+ Fifo::iterator front(mQueue.begin());
+ // buffer currently queued is freed
+ mSlots[*front].mBufferState = BufferSlot::FREE;
+ // and we record the new buffer index in the queued list
+ *front = buf;
+ }
+ }
+ // always signals that an additional frame should be consumed
+ // to handle max acquired buffer count reached case.
+ listener = mConsumerListener;
+
+ mSlots[buf].mTimestamp = timestamp;
+ mSlots[buf].mCrop = crop;
+ mSlots[buf].mTransform = transform;
+ mSlots[buf].mFence = fence;
+
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
+ break;
+ default:
+ ST_LOGE("unknown scaling mode: %d (ignoring)", scalingMode);
+ scalingMode = mSlots[buf].mScalingMode;
+ break;
+ }
+
+ mSlots[buf].mBufferState = BufferSlot::QUEUED;
+ mSlots[buf].mScalingMode = scalingMode;
+ mFrameCounter++;
+ mSlots[buf].mFrameNumber = mFrameCounter;
+
+ mBufferHasBeenQueued = true;
+ mDequeueCondition.broadcast();
+
+ output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint,
+ mQueue.size());
+ } // scope for the lock
+
+ // call back without lock held
+ if (listener != 0) {
+ listener->onFrameAvailable();
+ }
+ return NO_ERROR;
+}
+
+#if ANDROID_VERSION == 17
+void GonkBufferQueue::cancelBuffer(int buf, sp<Fence> fence) {
+#else
+void GonkBufferQueue::cancelBuffer(int buf, const sp<Fence>& fence) {
+#endif
+
+ ST_LOGV("cancelBuffer: slot=%d", buf);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGW("cancelBuffer: GonkBufferQueue has been abandoned!");
+ return;
+ }
+
+ int maxBufferCount = getMaxBufferCountLocked();
+ if (buf < 0 || buf >= maxBufferCount) {
+ ST_LOGE("cancelBuffer: slot index out of range [0, %d]: %d",
+ maxBufferCount, buf);
+ return;
+ } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+ ST_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
+ buf, mSlots[buf].mBufferState);
+ return;
+#if ANDROID_VERSION >= 18
+ } else if (fence == NULL) {
+ ST_LOGE("cancelBuffer: fence is NULL");
+ return;
+#endif
+ }
+ mSlots[buf].mBufferState = BufferSlot::FREE;
+ mSlots[buf].mFrameNumber = 0;
+ mSlots[buf].mFence = fence;
+ mDequeueCondition.broadcast();
+}
+
+status_t GonkBufferQueue::connect(int api, QueueBufferOutput* output) {
+ ST_LOGV("connect: api=%d", api);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("connect: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mConsumerListener == NULL) {
+ ST_LOGE("connect: GonkBufferQueue has no consumer!");
+ return NO_INIT;
+ }
+
+ int err = NO_ERROR;
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ if (mConnectedApi != NO_CONNECTED_API) {
+ ST_LOGE("connect: already connected (cur=%d, req=%d)",
+ mConnectedApi, api);
+ err = -EINVAL;
+ } else {
+ mConnectedApi = api;
+ output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint,
+ mQueue.size());
+ }
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ mBufferHasBeenQueued = false;
+
+ return err;
+}
+
+status_t GonkBufferQueue::disconnect(int api) {
+ ST_LOGV("disconnect: api=%d", api);
+
+ int err = NO_ERROR;
+ sp<ConsumerListener> listener;
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ // it is not really an error to disconnect after the surface
+ // has been abandoned, it should just be a no-op.
+ return NO_ERROR;
+ }
+
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ if (mConnectedApi == api) {
+ freeAllBuffersLocked();
+ mConnectedApi = NO_CONNECTED_API;
+ mDequeueCondition.broadcast();
+ listener = mConsumerListener;
+ } else {
+ ST_LOGE("disconnect: connected to another api (cur=%d, req=%d)",
+ mConnectedApi, api);
+ err = -EINVAL;
+ }
+ break;
+ default:
+ ST_LOGE("disconnect: unknown API %d", api);
+ err = -EINVAL;
+ break;
+ }
+ }
+
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return err;
+}
+
+void GonkBufferQueue::dumpToString(String8& result) const
+{
+ char buffer[1024];
+ GonkBufferQueue::dumpToString(result, "", buffer, 1024);
+}
+
+void GonkBufferQueue::dumpToString(String8& result, const char* prefix,
+ char* buffer, size_t SIZE) const
+{
+ Mutex::Autolock _l(mMutex);
+
+ String8 fifo;
+ int fifoSize = 0;
+ Fifo::const_iterator i(mQueue.begin());
+ while (i != mQueue.end()) {
+ snprintf(buffer, SIZE, "%02d ", *i++);
+ fifoSize++;
+ fifo.append(buffer);
+ }
+
+ int maxBufferCount = getMaxBufferCountLocked();
+
+ snprintf(buffer, SIZE,
+ "%s-BufferQueue maxBufferCount=%d, mSynchronousMode=%d, default-size=[%dx%d], "
+ "default-format=%d, transform-hint=%02x, FIFO(%d)={%s}\n",
+ prefix, maxBufferCount, mSynchronousMode, mDefaultWidth,
+ mDefaultHeight, mDefaultBufferFormat, mTransformHint,
+ fifoSize, fifo.string());
+ result.append(buffer);
+
+
+ struct {
+ const char * operator()(int state) const {
+ switch (state) {
+ case BufferSlot::DEQUEUED: return "DEQUEUED";
+ case BufferSlot::QUEUED: return "QUEUED";
+ case BufferSlot::FREE: return "FREE";
+ case BufferSlot::ACQUIRED: return "ACQUIRED";
+ default: return "Unknown";
+ }
+ }
+ } stateName;
+
+ for (int i=0 ; i<maxBufferCount ; i++) {
+ const BufferSlot& slot(mSlots[i]);
+ snprintf(buffer, SIZE,
+ "%s%s[%02d] "
+ "state=%-8s, crop=[%d,%d,%d,%d], "
+ "xform=0x%02x, time=%#llx, scale=%s",
+ prefix, (slot.mBufferState == BufferSlot::ACQUIRED)?">":" ", i,
+ stateName(slot.mBufferState),
+ slot.mCrop.left, slot.mCrop.top, slot.mCrop.right,
+ slot.mCrop.bottom, slot.mTransform, slot.mTimestamp,
+ scalingModeName(slot.mScalingMode)
+ );
+ result.append(buffer);
+
+ const sp<GraphicBuffer>& buf(slot.mGraphicBuffer);
+ if (buf != NULL) {
+ snprintf(buffer, SIZE,
+ ", %p [%4ux%4u:%4u,%3X]",
+ buf->handle, buf->width, buf->height, buf->stride,
+ buf->format);
+ result.append(buffer);
+ }
+ result.append("\n");
+ }
+}
+
+void GonkBufferQueue::freeAllBuffersLocked()
+{
+ ALOGW_IF(!mQueue.isEmpty(),
+ "freeAllBuffersLocked called but mQueue is not empty");
+ mQueue.clear();
+ mBufferHasBeenQueued = false;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ mSlots[i].mGraphicBuffer = 0;
+ if (mSlots[i].mTextureClient) {
+ mSlots[i].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[i].mTextureClient);
+ mSlots[i].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[i].mNeedsCleanupOnRelease = true;
+ }
+ mSlots[i].mBufferState = BufferSlot::FREE;
+ mSlots[i].mFrameNumber = 0;
+ mSlots[i].mAcquireCalled = false;
+ // destroy fence as GonkBufferQueue now takes ownership
+ mSlots[i].mFence = Fence::NO_FENCE;
+ }
+}
+
+status_t GonkBufferQueue::acquireBuffer(BufferItem *buffer) {
+ Mutex::Autolock _l(mMutex);
+
+ // Check that the consumer doesn't currently have the maximum number of
+ // buffers acquired. We allow the max buffer count to be exceeded by one
+ // buffer, so that the consumer can successfully set up the newly acquired
+ // buffer before releasing the old one.
+ int numAcquiredBuffers = 0;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
+ numAcquiredBuffers++;
+ }
+ }
+ if (numAcquiredBuffers >= mMaxAcquiredBufferCount+1) {
+ ST_LOGE("acquireBuffer: max acquired buffer count reached: %d (max=%d)",
+ numAcquiredBuffers, mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+
+ // check if queue is empty
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ if (!mQueue.empty()) {
+ Fifo::iterator front(mQueue.begin());
+ int buf = *front;
+
+ // In android, when the buffer is aquired by BufferConsumer,
+ // BufferQueue releases a reference to the buffer and
+ // it's ownership moves to the BufferConsumer.
+ // In b2g, GonkBufferQueue continues to have a buffer ownership.
+ // It is necessary to free buffer via ImageBridgeChild.
+
+ //if (mSlots[buf].mAcquireCalled) {
+ // buffer->mGraphicBuffer = NULL;
+ //} else {
+ // buffer->mGraphicBuffer = mSlots[buf].mGraphicBuffer;
+ //}
+ buffer->mGraphicBuffer = mSlots[buf].mGraphicBuffer;
+ buffer->mCrop = mSlots[buf].mCrop;
+ buffer->mTransform = mSlots[buf].mTransform;
+ buffer->mScalingMode = mSlots[buf].mScalingMode;
+ buffer->mFrameNumber = mSlots[buf].mFrameNumber;
+ buffer->mTimestamp = mSlots[buf].mTimestamp;
+ buffer->mBuf = buf;
+ buffer->mFence = mSlots[buf].mFence;
+
+ mSlots[buf].mAcquireCalled = true;
+ mSlots[buf].mNeedsCleanupOnRelease = false;
+ mSlots[buf].mBufferState = BufferSlot::ACQUIRED;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+
+ mQueue.erase(front);
+ mDequeueCondition.broadcast();
+ } else {
+ return NO_BUFFER_AVAILABLE;
+ }
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::releaseBuffer(int buf, const sp<Fence>& fence) {
+ Mutex::Autolock _l(mMutex);
+
+#if ANDROID_VERSION == 17
+ if (buf == INVALID_BUFFER_SLOT) {
+#else
+ if (buf == INVALID_BUFFER_SLOT || fence == NULL) {
+#endif
+ return BAD_VALUE;
+ }
+
+ mSlots[buf].mFence = fence;
+
+ // The buffer can now only be released if its in the acquired state
+ if (mSlots[buf].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[buf].mBufferState = BufferSlot::FREE;
+ } else if (mSlots[buf].mNeedsCleanupOnRelease) {
+ ST_LOGV("releasing a stale buf %d its state was %d", buf, mSlots[buf].mBufferState);
+ mSlots[buf].mNeedsCleanupOnRelease = false;
+ return STALE_BUFFER_SLOT;
+ } else {
+ ST_LOGE("attempted to release buf %d but its state was %d", buf, mSlots[buf].mBufferState);
+ return -EINVAL;
+ }
+
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::consumerConnect(const sp<ConsumerListener>& consumerListener) {
+ ST_LOGV("consumerConnect");
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("consumerConnect: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (consumerListener == NULL) {
+ ST_LOGE("consumerConnect: consumerListener may not be NULL");
+ return BAD_VALUE;
+ }
+
+ mConsumerListener = consumerListener;
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::consumerDisconnect() {
+ ST_LOGV("consumerDisconnect");
+ Mutex::Autolock lock(mMutex);
+
+ if (mConsumerListener == NULL) {
+ ST_LOGE("consumerDisconnect: No consumer is connected!");
+ return -EINVAL;
+ }
+
+ mAbandoned = true;
+ mConsumerListener = NULL;
+ mQueue.clear();
+ freeAllBuffersLocked();
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::getReleasedBuffers(uint32_t* slotMask) {
+ ST_LOGV("getReleasedBuffers");
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("getReleasedBuffers: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ uint32_t mask = 0;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (!mSlots[i].mAcquireCalled) {
+ mask |= 1 << i;
+ }
+ }
+ *slotMask = mask;
+
+ ST_LOGV("getReleasedBuffers: returning mask %#x", mask);
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setDefaultBufferSize(uint32_t w, uint32_t h)
+{
+ ST_LOGV("setDefaultBufferSize: w=%d, h=%d", w, h);
+ if (!w || !h) {
+ ST_LOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)",
+ w, h);
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mMutex);
+ mDefaultWidth = w;
+ mDefaultHeight = h;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setDefaultMaxBufferCount(int bufferCount) {
+ Mutex::Autolock lock(mMutex);
+ return setDefaultMaxBufferCountLocked(bufferCount);
+}
+
+status_t GonkBufferQueue::setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
+ Mutex::Autolock lock(mMutex);
+ if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > MAX_MAX_ACQUIRED_BUFFERS) {
+ ST_LOGE("setMaxAcquiredBufferCount: invalid count specified: %d",
+ maxAcquiredBuffers);
+ return BAD_VALUE;
+ }
+ if (mConnectedApi != NO_CONNECTED_API) {
+ return INVALID_OPERATION;
+ }
+ mMaxAcquiredBufferCount = maxAcquiredBuffers;
+ return NO_ERROR;
+}
+
+int GonkBufferQueue::getMinMaxBufferCountLocked() const {
+ return getMinUndequeuedBufferCountLocked() + 1;
+}
+
+int GonkBufferQueue::getMinUndequeuedBufferCountLocked() const {
+ return mSynchronousMode ? mMaxAcquiredBufferCount :
+ mMaxAcquiredBufferCount + 1;
+}
+
+int GonkBufferQueue::getMaxBufferCountLocked() const {
+ int minMaxBufferCount = getMinMaxBufferCountLocked();
+
+ int maxBufferCount = mDefaultMaxBufferCount;
+ if (maxBufferCount < minMaxBufferCount) {
+ maxBufferCount = minMaxBufferCount;
+ }
+ if (mOverrideMaxBufferCount != 0) {
+ assert(mOverrideMaxBufferCount >= minMaxBufferCount);
+ maxBufferCount = mOverrideMaxBufferCount;
+ }
+
+ // Any buffers that are dequeued by the producer or sitting in the queue
+ // waiting to be consumed need to have their slots preserved. Such
+ // buffers will temporarily keep the max buffer count up until the slots
+ // no longer need to be preserved.
+ for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
+ BufferSlot::BufferState state = mSlots[i].mBufferState;
+ if (state == BufferSlot::QUEUED || state == BufferSlot::DEQUEUED) {
+ maxBufferCount = i + 1;
+ }
+ }
+
+ return maxBufferCount;
+}
+
+GonkBufferQueue::ProxyConsumerListener::ProxyConsumerListener(
+ const wp<GonkBufferQueue::ConsumerListener>& consumerListener):
+ mConsumerListener(consumerListener) {}
+
+GonkBufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {}
+
+void GonkBufferQueue::ProxyConsumerListener::onFrameAvailable() {
+ sp<GonkBufferQueue::ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkBufferQueue::ProxyConsumerListener::onBuffersReleased() {
+ sp<GonkBufferQueue::ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueJB.h b/widget/gonk/nativewindow/GonkBufferQueueJB.h
new file mode 100644
index 000000000..df0c4599f
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueJB.h
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUE_JB_H
+#define NATIVEWINDOW_GONKBUFFERQUEUE_JB_H
+
+#include <gui/IGraphicBufferAlloc.h>
+#if ANDROID_VERSION == 17
+#include <gui/ISurfaceTexture.h>
+#else
+#include <gui/IGraphicBufferProducer.h>
+#endif
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "mozilla/layers/LayersSurfaces.h"
+#include "mozilla/layers/TextureClient.h"
+
+#if ANDROID_VERSION == 17
+#define IGraphicBufferProducer ISurfaceTexture
+#endif
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+#if ANDROID_VERSION == 17
+class GonkBufferQueue : public BnSurfaceTexture {
+#else
+class GonkBufferQueue : public BnGraphicBufferProducer {
+#endif
+ typedef mozilla::layers::TextureClient TextureClient;
+
+public:
+ enum { MIN_UNDEQUEUED_BUFFERS = 2 };
+ enum { NUM_BUFFER_SLOTS = 32 };
+ enum { NO_CONNECTED_API = 0 };
+ enum { INVALID_BUFFER_SLOT = -1 };
+ enum { STALE_BUFFER_SLOT = 1, NO_BUFFER_AVAILABLE };
+
+ // When in async mode we reserve two slots in order to guarantee that the
+ // producer and consumer can run asynchronously.
+ enum { MAX_MAX_ACQUIRED_BUFFERS = NUM_BUFFER_SLOTS - 2 };
+
+ // ConsumerListener is the interface through which the GonkBufferQueue notifies
+ // the consumer of events that the consumer may wish to react to. Because
+ // the consumer will generally have a mutex that is locked during calls from
+ // the consumer to the GonkBufferQueue, these calls from the GonkBufferQueue to the
+ // consumer *MUST* be called only when the GonkBufferQueue mutex is NOT locked.
+ struct ConsumerListener : public virtual RefBase {
+ // onFrameAvailable is called from queueBuffer each time an additional
+ // frame becomes available for consumption. This means that frames that
+ // are queued while in asynchronous mode only trigger the callback if no
+ // previous frames are pending. Frames queued while in synchronous mode
+ // always trigger the callback.
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onFrameAvailable() = 0;
+
+ // onBuffersReleased is called to notify the buffer consumer that the
+ // GonkBufferQueue has released its references to one or more GraphicBuffers
+ // contained in its slots. The buffer consumer should then call
+ // GonkBufferQueue::getReleasedBuffers to retrieve the list of buffers
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onBuffersReleased() = 0;
+ };
+
+ // ProxyConsumerListener is a ConsumerListener implementation that keeps a weak
+ // reference to the actual consumer object. It forwards all calls to that
+ // consumer object so long as it exists.
+ //
+ // This class exists to avoid having a circular reference between the
+ // GonkBufferQueue object and the consumer object. The reason this can't be a weak
+ // reference in the GonkBufferQueue class is because we're planning to expose the
+ // consumer side of a GonkBufferQueue as a binder interface, which doesn't support
+ // weak references.
+ class ProxyConsumerListener : public GonkBufferQueue::ConsumerListener {
+ public:
+
+ ProxyConsumerListener(const wp<GonkBufferQueue::ConsumerListener>& consumerListener);
+ virtual ~ProxyConsumerListener();
+ virtual void onFrameAvailable();
+ virtual void onBuffersReleased();
+
+ private:
+
+ // mConsumerListener is a weak reference to the ConsumerListener. This is
+ // the raison d'etre of ProxyConsumerListener.
+ wp<GonkBufferQueue::ConsumerListener> mConsumerListener;
+ };
+
+
+ // GonkBufferQueue manages a pool of gralloc memory slots to be used by
+ // producers and consumers. allowSynchronousMode specifies whether or not
+ // synchronous mode can be enabled by the producer. allocator is used to
+ // allocate all the needed gralloc buffers.
+ GonkBufferQueue(bool allowSynchronousMode = true,
+ const sp<IGraphicBufferAlloc>& allocator = NULL);
+ virtual ~GonkBufferQueue();
+
+ // Query native window attributes. The "what" values are enumerated in
+ // window.h (e.g. NATIVE_WINDOW_FORMAT).
+ virtual int query(int what, int* value);
+
+ // setBufferCount updates the number of available buffer slots. If this
+ // method succeeds, buffer slots will be both unallocated and owned by
+ // the GonkBufferQueue object (i.e. they are not owned by the producer or
+ // consumer).
+ //
+ // This will fail if the producer has dequeued any buffers, or if
+ // bufferCount is invalid. bufferCount must generally be a value
+ // between the minimum undequeued buffer count and NUM_BUFFER_SLOTS
+ // (inclusive). It may also be set to zero (the default) to indicate
+ // that the producer does not wish to set a value. The minimum value
+ // can be obtained by calling query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ // ...).
+ //
+ // This may only be called by the producer. The consumer will be told
+ // to discard buffers through the onBuffersReleased callback.
+ virtual status_t setBufferCount(int bufferCount);
+
+ // requestBuffer returns the GraphicBuffer for slot N.
+ //
+ // In normal operation, this is called the first time slot N is returned
+ // by dequeueBuffer. It must be called again if dequeueBuffer returns
+ // flags indicating that previously-returned buffers are no longer valid.
+ virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
+
+ // dequeueBuffer gets the next buffer slot index for the producer to use.
+ // If a buffer slot is available then that slot index is written to the
+ // location pointed to by the buf argument and a status of OK is returned.
+ // If no slot is available then a status of -EBUSY is returned and buf is
+ // unmodified.
+ //
+ // The fence parameter will be updated to hold the fence associated with
+ // the buffer. The contents of the buffer must not be overwritten until the
+ // fence signals. If the fence is Fence::NO_FENCE, the buffer may be
+ // written immediately.
+ //
+ // The width and height parameters must be no greater than the minimum of
+ // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv).
+ // An error due to invalid dimensions might not be reported until
+ // updateTexImage() is called. If width and height are both zero, the
+ // default values specified by setDefaultBufferSize() are used instead.
+ //
+ // The pixel formats are enumerated in graphics.h, e.g.
+ // HAL_PIXEL_FORMAT_RGBA_8888. If the format is 0, the default format
+ // will be used.
+ //
+ // The usage argument specifies gralloc buffer usage flags. The values
+ // are enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER. These
+ // will be merged with the usage flags specified by setConsumerUsageBits.
+ //
+ // The return value may be a negative error value or a non-negative
+ // collection of flags. If the flags are set, the return values are
+ // valid, but additional actions must be performed.
+ //
+ // If IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION is set, the
+ // producer must discard cached GraphicBuffer references for the slot
+ // returned in buf.
+ // If IGraphicBufferProducer::RELEASE_ALL_BUFFERS is set, the producer
+ // must discard cached GraphicBuffer references for all slots.
+ //
+ // In both cases, the producer will need to call requestBuffer to get a
+ // GraphicBuffer handle for the returned slot.
+#if ANDROID_VERSION == 17
+ virtual status_t dequeueBuffer(int *buf, sp<Fence>& fence,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage) {
+ return dequeueBuffer(buf, &fence, width, height, format, usage);
+ }
+#endif
+
+ virtual status_t dequeueBuffer(int *buf, sp<Fence>* fence,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage);
+
+ // queueBuffer returns a filled buffer to the GonkBufferQueue.
+ //
+ // Additional data is provided in the QueueBufferInput struct. Notably,
+ // a timestamp must be provided for the buffer. The timestamp is in
+ // nanoseconds, and must be monotonically increasing. Its other semantics
+ // (zero point, etc) are producer-specific and should be documented by the
+ // producer.
+ //
+ // The caller may provide a fence that signals when all rendering
+ // operations have completed. Alternatively, NO_FENCE may be used,
+ // indicating that the buffer is ready immediately.
+ //
+ // Some values are returned in the output struct: the current settings
+ // for default width and height, the current transform hint, and the
+ // number of queued buffers.
+ virtual status_t queueBuffer(int buf,
+ const QueueBufferInput& input, QueueBufferOutput* output);
+
+ // cancelBuffer returns a dequeued buffer to the GonkBufferQueue, but doesn't
+ // queue it for use by the consumer.
+ //
+ // The buffer will not be overwritten until the fence signals. The fence
+ // will usually be the one obtained from dequeueBuffer.
+#if ANDROID_VERSION == 17
+ virtual void cancelBuffer(int buf, sp<Fence> fence);
+#else
+ virtual void cancelBuffer(int buf, const sp<Fence>& fence);
+#endif
+
+ // setSynchronousMode sets whether dequeueBuffer is synchronous or
+ // asynchronous. In synchronous mode, dequeueBuffer blocks until
+ // a buffer is available, the currently bound buffer can be dequeued and
+ // queued buffers will be acquired in order. In asynchronous mode,
+ // a queued buffer may be replaced by a subsequently queued buffer.
+ //
+ // The default mode is synchronous.
+ // This should be called only during initialization.
+ virtual status_t setSynchronousMode(bool enabled);
+
+ // connect attempts to connect a producer API to the GonkBufferQueue. This
+ // must be called before any other IGraphicBufferProducer methods are
+ // called except for getAllocator. A consumer must already be connected.
+ //
+ // This method will fail if connect was previously called on the
+ // GonkBufferQueue and no corresponding disconnect call was made (i.e. if
+ // it's still connected to a producer).
+ //
+ // APIs are enumerated in window.h (e.g. NATIVE_WINDOW_API_CPU).
+ virtual status_t connect(int api, QueueBufferOutput* output);
+
+ // disconnect attempts to disconnect a producer API from the GonkBufferQueue.
+ // Calling this method will cause any subsequent calls to other
+ // IGraphicBufferProducer methods to fail except for getAllocator and connect.
+ // Successfully calling connect after this will allow the other methods to
+ // succeed again.
+ //
+ // This method will fail if the the GonkBufferQueue is not currently
+ // connected to the specified producer API.
+ virtual status_t disconnect(int api);
+
+ // dump our state in a String
+ virtual void dumpToString(String8& result) const;
+ virtual void dumpToString(String8& result, const char* prefix, char* buffer, size_t SIZE) const;
+
+ // public facing structure for BufferSlot
+ struct BufferItem {
+
+ BufferItem()
+ :
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mFrameNumber(0),
+ mBuf(INVALID_BUFFER_SLOT) {
+ mCrop.makeInvalid();
+ }
+ // mGraphicBuffer points to the buffer allocated for this slot, or is NULL
+ // if the buffer in this slot has been acquired in the past (see
+ // BufferSlot.mAcquireCalled).
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued.
+ int64_t mTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot.
+ uint64_t mFrameNumber;
+
+ // mBuf is the slot index of this buffer
+ int mBuf;
+
+ // mFence is a fence that will signal when the buffer is idle.
+ sp<Fence> mFence;
+ };
+
+ // The following public functions are the consumer-facing interface
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the GonkBufferQueue. If no buffer is pending then it returns -EINVAL. If a
+ // buffer is successfully acquired, the information about the buffer is
+ // returned in BufferItem. If the buffer returned had previously been
+ // acquired then the BufferItem::mGraphicBuffer field of buffer is set to
+ // NULL and it is assumed that the consumer still holds a reference to the
+ // buffer.
+ status_t acquireBuffer(BufferItem *buffer);
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // GonkBufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ //
+ // Note that the dependencies on EGL will be removed once we switch to using
+ // the Android HW Sync HAL.
+ status_t releaseBuffer(int buf, const sp<Fence>& releaseFence);
+
+ // consumerConnect connects a consumer to the GonkBufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // GonkBufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the GonkBufferQueue by the producer to fail.
+ //
+ // consumer may not be NULL.
+ status_t consumerConnect(const sp<ConsumerListener>& consumer);
+
+ // consumerDisconnect disconnects a consumer from the GonkBufferQueue. All
+ // buffers will be freed and the GonkBufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the GonkBufferQueue by the producer to
+ // fail.
+ status_t consumerDisconnect();
+
+ // getReleasedBuffers sets the value pointed to by slotMask to a bit mask
+ // indicating which buffer slots have been released by the GonkBufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ status_t getReleasedBuffers(uint32_t* slotMask);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ status_t setDefaultMaxBufferCount(int bufferCount);
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the GonkBufferQueue.
+ status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
+
+ // isSynchronousMode returns whether the GonkBufferQueue is currently in
+ // synchronous mode.
+ bool isSynchronousMode() const;
+
+ // setConsumerName sets the name used in logging
+ void setConsumerName(const String8& name);
+
+ // setDefaultBufferFormat allows the GonkBufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ status_t setConsumerUsageBits(uint32_t usage);
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ status_t setTransformHint(uint32_t hint);
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ int getSlotFromTextureClientLocked(TextureClient* client) const;
+
+private:
+ // freeBufferLocked frees the GraphicBuffer and sync resources for the
+ // given slot.
+ //void freeBufferLocked(int index);
+
+ // freeAllBuffersLocked frees the GraphicBuffer and sync resources for
+ // all slots.
+ //void freeAllBuffersLocked();
+ void freeAllBuffersLocked();
+
+ // setDefaultMaxBufferCountLocked sets the maximum number of buffer slots
+ // that will be used if the producer does not override the buffer slot
+ // count. The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ // The initial default is 2.
+ status_t setDefaultMaxBufferCountLocked(int count);
+
+ // getMinBufferCountLocked returns the minimum number of buffers allowed
+ // given the current GonkBufferQueue state.
+ int getMinMaxBufferCountLocked() const;
+
+ // getMinUndequeuedBufferCountLocked returns the minimum number of buffers
+ // that must remain in a state other than DEQUEUED.
+ int getMinUndequeuedBufferCountLocked() const;
+
+ // getMaxBufferCountLocked returns the maximum number of buffers that can
+ // be allocated at once. This value depends upon the following member
+ // variables:
+ //
+ // mSynchronousMode
+ // mMaxAcquiredBufferCount
+ // mDefaultMaxBufferCount
+ // mOverrideMaxBufferCount
+ //
+ // Any time one of these member variables is changed while a producer is
+ // connected, mDequeueCondition must be broadcast.
+ int getMaxBufferCountLocked() const;
+
+ struct BufferSlot {
+
+ BufferSlot()
+ : mBufferState(BufferSlot::FREE),
+ mRequestBufferCalled(false),
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mFrameNumber(0),
+ mAcquireCalled(false),
+ mNeedsCleanupOnRelease(false) {
+ mCrop.makeInvalid();
+ }
+
+ // mGraphicBuffer points to the buffer allocated for this slot or is NULL
+ // if no buffer has been allocated.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mTextureClient is a thin abstraction over remotely allocated GraphicBuffer.
+ RefPtr<TextureClient> mTextureClient;
+
+ // BufferState represents the different states in which a buffer slot
+ // can be. All slots are initially FREE.
+ enum BufferState {
+ // FREE indicates that the buffer is available to be dequeued
+ // by the producer. The buffer may be in use by the consumer for
+ // a finite time, so the buffer must not be modified until the
+ // associated fence is signaled.
+ //
+ // The slot is "owned" by GonkBufferQueue. It transitions to DEQUEUED
+ // when dequeueBuffer is called.
+ FREE = 0,
+
+ // DEQUEUED indicates that the buffer has been dequeued by the
+ // producer, but has not yet been queued or canceled. The
+ // producer may modify the buffer's contents as soon as the
+ // associated ready fence is signaled.
+ //
+ // The slot is "owned" by the producer. It can transition to
+ // QUEUED (via queueBuffer) or back to FREE (via cancelBuffer).
+ DEQUEUED = 1,
+
+ // QUEUED indicates that the buffer has been filled by the
+ // producer and queued for use by the consumer. The buffer
+ // contents may continue to be modified for a finite time, so
+ // the contents must not be accessed until the associated fence
+ // is signaled.
+ //
+ // The slot is "owned" by GonkBufferQueue. It can transition to
+ // ACQUIRED (via acquireBuffer) or to FREE (if another buffer is
+ // queued in asynchronous mode).
+ QUEUED = 2,
+
+ // ACQUIRED indicates that the buffer has been acquired by the
+ // consumer. As with QUEUED, the contents must not be accessed
+ // by the consumer until the fence is signaled.
+ //
+ // The slot is "owned" by the consumer. It transitions to FREE
+ // when releaseBuffer is called.
+ ACQUIRED = 3
+ };
+
+ // mBufferState is the current state of this buffer slot.
+ BufferState mBufferState;
+
+ // mRequestBufferCalled is used for validating that the producer did
+ // call requestBuffer() when told to do so. Technically this is not
+ // needed but useful for debugging and catching producer bugs.
+ bool mRequestBufferCalled;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ // (example: NATIVE_WINDOW_TRANSFORM_ROT_90)
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ // (example: NATIVE_WINDOW_SCALING_MODE_FREEZE)
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued.
+ int64_t mTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot. This
+ // is used to dequeue buffers in LRU order (useful because buffers
+ // may be released before their release fence is signaled).
+ uint64_t mFrameNumber;
+
+ // mEglFence is the EGL sync object that must signal before the buffer
+ // associated with this buffer slot may be dequeued. It is initialized
+ // to EGL_NO_SYNC_KHR when the buffer is created and may be set to a
+ // new sync object in releaseBuffer. (This is deprecated in favor of
+ // mFence, below.)
+ //EGLSyncKHR mEglFence;
+
+ // mFence is a fence which will signal when work initiated by the
+ // previous owner of the buffer is finished. When the buffer is FREE,
+ // the fence indicates when the consumer has finished reading
+ // from the buffer, or when the producer has finished writing if it
+ // called cancelBuffer after queueing some writes. When the buffer is
+ // QUEUED, it indicates when the producer has finished filling the
+ // buffer. When the buffer is DEQUEUED or ACQUIRED, the fence has been
+ // passed to the consumer or producer along with ownership of the
+ // buffer, and mFence is set to NO_FENCE.
+ sp<Fence> mFence;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates whether this buffer needs to be cleaned up by the
+ // consumer. This is set when a buffer in ACQUIRED state is freed.
+ // It causes releaseBuffer to return STALE_BUFFER_SLOT.
+ bool mNeedsCleanupOnRelease;
+ };
+
+ // mSlots is the array of buffer slots that must be mirrored on the
+ // producer side. This allows buffer ownership to be transferred between
+ // the producer and consumer without sending a GraphicBuffer over binder.
+ // The entire array is initialized to NULL at construction time, and
+ // buffers are allocated for a slot when requestBuffer is called with
+ // that slot's index.
+ BufferSlot mSlots[NUM_BUFFER_SLOTS];
+
+ // mDefaultWidth holds the default width of allocated buffers. It is used
+ // in dequeueBuffer() if a width and height of zero is specified.
+ uint32_t mDefaultWidth;
+
+ // mDefaultHeight holds the default height of allocated buffers. It is used
+ // in dequeueBuffer() if a width and height of zero is specified.
+ uint32_t mDefaultHeight;
+
+ // mMaxAcquiredBufferCount is the number of buffers that the consumer may
+ // acquire at one time. It defaults to 1 and can be changed by the
+ // consumer via the setMaxAcquiredBufferCount method, but this may only be
+ // done when no producer is connected to the GonkBufferQueue.
+ //
+ // This value is used to derive the value returned for the
+ // MIN_UNDEQUEUED_BUFFERS query by the producer.
+ int mMaxAcquiredBufferCount;
+
+ // mDefaultMaxBufferCount is the default limit on the number of buffers
+ // that will be allocated at one time. This default limit is set by the
+ // consumer. The limit (as opposed to the default limit) may be
+ // overridden by the producer.
+ int mDefaultMaxBufferCount;
+
+ // mOverrideMaxBufferCount is the limit on the number of buffers that will
+ // be allocated at one time. This value is set by the image producer by
+ // calling setBufferCount. The default is zero, which means the producer
+ // doesn't care about the number of buffers in the pool. In that case
+ // mDefaultMaxBufferCount is used as the limit.
+ int mOverrideMaxBufferCount;
+
+ // mGraphicBufferAlloc is the connection to SurfaceFlinger that is used to
+ // allocate new GraphicBuffer objects.
+ sp<IGraphicBufferAlloc> mGraphicBufferAlloc;
+
+ // mConsumerListener is used to notify the connected consumer of
+ // asynchronous events that it may wish to react to. It is initially set
+ // to NULL and is written by consumerConnect and consumerDisconnect.
+ sp<ConsumerListener> mConsumerListener;
+
+ // mSynchronousMode whether we're in synchronous mode or not
+ bool mSynchronousMode;
+
+ // mAllowSynchronousMode whether we allow synchronous mode or not. Set
+ // when the GonkBufferQueue is created (by the consumer).
+ const bool mAllowSynchronousMode;
+
+ // mConnectedApi indicates the producer API that is currently connected
+ // to this GonkBufferQueue. It defaults to NO_CONNECTED_API (= 0), and gets
+ // updated by the connect and disconnect methods.
+ int mConnectedApi;
+
+ // mDequeueCondition condition used for dequeueBuffer in synchronous mode
+ mutable Condition mDequeueCondition;
+
+ // mQueue is a FIFO of queued buffers used in synchronous mode
+ typedef Vector<int> Fifo;
+ Fifo mQueue;
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume image buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the
+ // consumerDisconnect method. A GonkBufferQueue that has been abandoned will
+ // return the NO_INIT error from all IGraphicBufferProducer methods
+ // capable of returning an error.
+ bool mAbandoned;
+
+ // mConsumerName is a string used to identify the GonkBufferQueue in log
+ // messages. It is set by the setConsumerName method.
+ String8 mConsumerName;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkBufferQueue objects. It must be locked whenever the
+ // member variables are accessed.
+ mutable Mutex mMutex;
+
+ // mFrameCounter is the free running counter, incremented on every
+ // successful queueBuffer call.
+ uint64_t mFrameCounter;
+
+ // mBufferHasBeenQueued is true once a buffer has been queued. It is
+ // reset when something causes all buffers to be freed (e.g. changing the
+ // buffer count).
+ bool mBufferHasBeenQueued;
+
+ // mDefaultBufferFormat can be set so it will override
+ // the buffer format when it isn't specified in dequeueBuffer
+ uint32_t mDefaultBufferFormat;
+
+ // mConsumerUsageBits contains flags the consumer wants for GraphicBuffers
+ uint32_t mConsumerUsageBits;
+
+ // mTransformHint is used to optimize for screen rotations
+ uint32_t mTransformHint;
+
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_BUFFERQUEUE_H
diff --git a/widget/gonk/nativewindow/GonkBufferQueueKK.cpp b/widget/gonk/nativewindow/GonkBufferQueueKK.cpp
new file mode 100644
index 000000000..0c5cdfeb9
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueKK.cpp
@@ -0,0 +1,1265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GonkBufferQueue"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define GL_GLEXT_PROTOTYPES
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <utils/Log.h>
+#include <utils/Trace.h>
+#include <utils/CallStack.h>
+#include <cutils/compiler.h>
+
+#include "mozilla/layers/GrallocTextureClient.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "GonkBufferQueueKK.h"
+
+#define ATRACE_BUFFER_INDEX(index)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+static const char* scalingModeName(int scalingMode) {
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP";
+ default: return "Unknown";
+ }
+}
+
+GonkBufferQueue::GonkBufferQueue(bool allowSynchronousMode,
+ const sp<IGraphicBufferAlloc>& allocator) :
+ mDefaultWidth(1),
+ mDefaultHeight(1),
+ mMaxAcquiredBufferCount(1),
+ mDefaultMaxBufferCount(2),
+ mOverrideMaxBufferCount(0),
+ mSynchronousMode(true),
+ mConsumerControlledByApp(false),
+ mDequeueBufferCannotBlock(false),
+ mUseAsyncBuffer(true),
+ mConnectedApi(NO_CONNECTED_API),
+ mAbandoned(false),
+ mFrameCounter(0),
+ mBufferHasBeenQueued(false),
+ mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
+ mConsumerUsageBits(0),
+ mTransformHint(0)
+{
+ // Choose a name using the PID and a process-unique ID.
+ mConsumerName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ ALOGV("GonkBufferQueue");
+}
+
+GonkBufferQueue::~GonkBufferQueue() {
+ ALOGV("~GonkBufferQueue");
+}
+
+status_t GonkBufferQueue::setDefaultMaxBufferCountLocked(int count) {
+ if (count < 2 || count > NUM_BUFFER_SLOTS)
+ return BAD_VALUE;
+
+ mDefaultMaxBufferCount = count;
+ mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+void GonkBufferQueue::setConsumerName(const String8& name) {
+ Mutex::Autolock lock(mMutex);
+ mConsumerName = name;
+}
+
+status_t GonkBufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock lock(mMutex);
+ mDefaultBufferFormat = defaultFormat;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setConsumerUsageBits(uint32_t usage) {
+ Mutex::Autolock lock(mMutex);
+ mConsumerUsageBits = usage;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setTransformHint(uint32_t hint) {
+ ALOGV("setTransformHint: %02x", hint);
+ Mutex::Autolock lock(mMutex);
+ mTransformHint = hint;
+ return NO_ERROR;
+}
+
+already_AddRefed<TextureClient>
+GonkBufferQueue::getTextureClientFromBuffer(ANativeWindowBuffer* buffer)
+{
+ Mutex::Autolock _l(mMutex);
+ if (buffer == NULL) {
+ ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return nullptr;
+ }
+
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) {
+ RefPtr<TextureClient> client(mSlots[i].mTextureClient);
+ return client.forget();
+ }
+ }
+ ALOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+ return nullptr;
+}
+
+int GonkBufferQueue::getSlotFromTextureClientLocked(
+ TextureClient* client) const
+{
+ if (client == NULL) {
+ ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return BAD_VALUE;
+ }
+
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mTextureClient == client) {
+ return i;
+ }
+ }
+ ALOGE("getSlotFromBufferLocked: unknown TextureClient: %p", client);
+ return BAD_VALUE;
+}
+
+status_t GonkBufferQueue::setBufferCount(int bufferCount) {
+ ALOGV("setBufferCount: count=%d", bufferCount);
+
+ sp<IConsumerListener> listener;
+ {
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("setBufferCount: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (bufferCount > NUM_BUFFER_SLOTS) {
+ ALOGE("setBufferCount: bufferCount too large (max %d)",
+ NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ // Error out if the user has dequeued buffers
+ for (int i=0 ; i<NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) {
+ ALOGE("setBufferCount: client owns some buffers");
+ return -EINVAL;
+ }
+ }
+
+ if (bufferCount == 0) {
+ mOverrideMaxBufferCount = 0;
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+ }
+
+ // fine to assume async to false before we're setting the buffer count
+ const int minBufferSlots = getMinMaxBufferCountLocked(false);
+ if (bufferCount < minBufferSlots) {
+ ALOGE("setBufferCount: requested buffer count (%d) is less than "
+ "minimum (%d)", bufferCount, minBufferSlots);
+ return BAD_VALUE;
+ }
+
+ // here we're guaranteed that the client doesn't have dequeued buffers
+ // and will release all of its buffer references. We don't clear the
+ // queue, however, so currently queued buffers still get displayed.
+ // XXX: Should this use drainQueueAndFreeBuffersLocked instead?
+ freeAllBuffersLocked();
+ mOverrideMaxBufferCount = bufferCount;
+ mDequeueCondition.broadcast();
+ listener = mConsumerListener;
+ } // scope for lock
+
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return NO_ERROR;
+}
+
+int GonkBufferQueue::query(int what, int* outValue)
+{
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("query: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ int value;
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ value = mDefaultWidth;
+ break;
+ case NATIVE_WINDOW_HEIGHT:
+ value = mDefaultHeight;
+ break;
+ case NATIVE_WINDOW_FORMAT:
+ value = mDefaultBufferFormat;
+ break;
+ case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+ value = getMinUndequeuedBufferCount(false);
+ break;
+ case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+ value = (mQueue.size() >= 2);
+ break;
+ case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
+ value = mConsumerUsageBits;
+ break;
+ default:
+ return BAD_VALUE;
+ }
+ outValue[0] = value;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
+ ATRACE_CALL();
+ ALOGV("requestBuffer: slot=%d", slot);
+ Mutex::Autolock lock(mMutex);
+ if (mAbandoned) {
+ ALOGE("requestBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (slot < 0 || slot >= NUM_BUFFER_SLOTS) {
+ ALOGE("requestBuffer: slot index out of range [0, %d]: %d",
+ NUM_BUFFER_SLOTS, slot);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+ ALOGE("requestBuffer: slot %d is not owned by the client (state=%d)",
+ slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+ mSlots[slot].mRequestBufferCalled = true;
+ *buf = mSlots[slot].mGraphicBuffer;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::dequeueBuffer(int *outBuf, sp<Fence>* outFence, bool async,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
+ ATRACE_CALL();
+ ALOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage);
+
+ if ((w && !h) || (!w && h)) {
+ ALOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h);
+ return BAD_VALUE;
+ }
+
+ status_t returnFlags(OK);
+ int buf = INVALID_BUFFER_SLOT;
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (format == 0) {
+ format = mDefaultBufferFormat;
+ }
+ // turn on usage bits the consumer requested
+ usage |= mConsumerUsageBits;
+
+ int found = -1;
+ bool tryAgain = true;
+ while (tryAgain) {
+ if (mAbandoned) {
+ ALOGE("dequeueBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = getMaxBufferCountLocked(async);
+ if (async && mOverrideMaxBufferCount) {
+ // FIXME: some drivers are manually setting the buffer-count (which they
+ // shouldn't), so we do this extra test here to handle that case.
+ // This is TEMPORARY, until we get this fixed.
+ if (mOverrideMaxBufferCount < maxBufferCount) {
+ ALOGE("dequeueBuffer: async mode is invalid with buffercount override");
+ return BAD_VALUE;
+ }
+ }
+
+ // Free up any buffers that are in slots beyond the max buffer
+ // count.
+ //for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
+ // assert(mSlots[i].mBufferState == BufferSlot::FREE);
+ // if (mSlots[i].mGraphicBuffer != NULL) {
+ // freeBufferLocked(i);
+ // returnFlags |= IGraphicBufferProducer::RELEASE_ALL_BUFFERS;
+ // }
+ //}
+
+ // look for a free buffer to give to the client
+ found = INVALID_BUFFER_SLOT;
+ int dequeuedCount = 0;
+ int acquiredCount = 0;
+ for (int i = 0; i < maxBufferCount; i++) {
+ const int state = mSlots[i].mBufferState;
+ switch (state) {
+ case BufferSlot::DEQUEUED:
+ dequeuedCount++;
+ break;
+ case BufferSlot::ACQUIRED:
+ acquiredCount++;
+ break;
+ case BufferSlot::FREE:
+ /* We return the oldest of the free buffers to avoid
+ * stalling the producer if possible. This is because
+ * the consumer may still have pending reads of the
+ * buffers in flight.
+ */
+ if ((found < 0) ||
+ mSlots[i].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = i;
+ }
+ break;
+ }
+ }
+
+ // clients are not allowed to dequeue more than one buffer
+ // if they didn't set a buffer count.
+ if (!mOverrideMaxBufferCount && dequeuedCount) {
+ ALOGE("dequeueBuffer: can't dequeue multiple buffers without "
+ "setting the buffer count");
+ return -EINVAL;
+ }
+
+ // See whether a buffer has been queued since the last
+ // setBufferCount so we know whether to perform the min undequeued
+ // buffers check below.
+ if (mBufferHasBeenQueued) {
+ // make sure the client is not trying to dequeue more buffers
+ // than allowed.
+ const int newUndequeuedCount = maxBufferCount - (dequeuedCount+1);
+ const int minUndequeuedCount = getMinUndequeuedBufferCount(async);
+ if (newUndequeuedCount < minUndequeuedCount) {
+ ALOGE("dequeueBuffer: min undequeued buffer count (%d) "
+ "exceeded (dequeued=%d undequeudCount=%d)",
+ minUndequeuedCount, dequeuedCount,
+ newUndequeuedCount);
+ return -EBUSY;
+ }
+ }
+
+ // If no buffer is found, wait for a buffer to be released or for
+ // the max buffer count to change.
+ tryAgain = found == INVALID_BUFFER_SLOT;
+ if (tryAgain) {
+ // return an error if we're in "cannot block" mode (producer and consumer
+ // are controlled by the application) -- however, the consumer is allowed
+ // to acquire briefly an extra buffer (which could cause us to have to wait here)
+ // and that's okay because we know the wait will be brief (it happens
+ // if we dequeue a buffer while the consumer has acquired one but not released
+ // the old one yet -- for e.g.: see GLConsumer::updateTexImage()).
+ if (mDequeueBufferCannotBlock && (acquiredCount <= mMaxAcquiredBufferCount)) {
+ ALOGE("dequeueBuffer: would block! returning an error instead.");
+ return WOULD_BLOCK;
+ }
+ mDequeueCondition.wait(mMutex);
+ }
+ }
+
+
+ if (found == INVALID_BUFFER_SLOT) {
+ // This should not happen.
+ ALOGE("dequeueBuffer: no available buffer slots");
+ return -EBUSY;
+ }
+
+ buf = found;
+ *outBuf = found;
+
+ const bool useDefaultSize = !w && !h;
+ if (useDefaultSize) {
+ // use the default size
+ w = mDefaultWidth;
+ h = mDefaultHeight;
+ }
+
+ mSlots[buf].mBufferState = BufferSlot::DEQUEUED;
+
+ const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer);
+ if ((buffer == NULL) ||
+ (uint32_t(buffer->width) != w) ||
+ (uint32_t(buffer->height) != h) ||
+ (uint32_t(buffer->format) != format) ||
+ ((uint32_t(buffer->usage) & usage) != usage))
+ {
+ mSlots[buf].mAcquireCalled = false;
+ mSlots[buf].mGraphicBuffer = NULL;
+ mSlots[buf].mRequestBufferCalled = false;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ if (mSlots[buf].mTextureClient) {
+ mSlots[buf].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[buf].mTextureClient);
+ mSlots[buf].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ returnFlags |= IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION;
+ }
+
+
+ if (CC_UNLIKELY(mSlots[buf].mFence == NULL)) {
+ ALOGE("dequeueBuffer: about to return a NULL fence from mSlot. "
+ "buf=%d, w=%d, h=%d, format=%d",
+ buf, buffer->width, buffer->height, buffer->format);
+ }
+ *outFence = mSlots[buf].mFence;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ } // end lock scope
+
+ if (returnFlags & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+
+ RefPtr<LayersIPCChannel> allocator = ImageBridgeChild::GetSingleton();
+ usage |= GraphicBuffer::USAGE_HW_TEXTURE;
+ GrallocTextureData* texData = GrallocTextureData::Create(IntSize(w, h), format,
+ gfx::BackendType::NONE, usage,
+ allocator);
+ if (!texData) {
+ return -ENOMEM;
+ }
+
+ RefPtr<TextureClient> textureClient = new TextureClient(texData, TextureFlags::RECYCLE | TextureFlags::DEALLOCATE_CLIENT, allocator);
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("dequeueBuffer: SurfaceTexture has been abandoned!");
+ return NO_INIT;
+ }
+
+ mSlots[buf].mGraphicBuffer = texData->GetGraphicBuffer();
+ mSlots[buf].mTextureClient = textureClient;
+ ALOGD("dequeueBuffer: returning slot=%d buf=%p ", buf,
+ mSlots[buf].mGraphicBuffer->handle);
+
+ }
+
+ }
+
+ ALOGV("dequeueBuffer: returning slot=%d/%llu buf=%p flags=%#x", *outBuf,
+ mSlots[*outBuf].mFrameNumber,
+ mSlots[*outBuf].mGraphicBuffer->handle, returnFlags);
+
+ return returnFlags;
+}
+
+status_t GonkBufferQueue::setSynchronousMode(bool enabled) {
+ ALOGV("setSynchronousMode: enabled=%d", enabled);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("setSynchronousMode: BufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mSynchronousMode != enabled) {
+ mSynchronousMode = enabled;
+ mDequeueCondition.broadcast();
+ }
+ return OK;
+}
+
+status_t GonkBufferQueue::queueBuffer(int buf,
+ const QueueBufferInput& input, QueueBufferOutput* output) {
+ ATRACE_CALL();
+
+ Rect crop;
+ uint32_t transform;
+ int scalingMode;
+ int64_t timestamp;
+ bool isAutoTimestamp;
+ bool async;
+ sp<Fence> fence;
+
+ input.deflate(&timestamp, &isAutoTimestamp, &crop, &scalingMode, &transform,
+ &async, &fence);
+
+ if (fence == NULL) {
+ ALOGE("queueBuffer: fence is NULL");
+ return BAD_VALUE;
+ }
+
+ ALOGV("queueBuffer: slot=%d time=%#llx crop=[%d,%d,%d,%d] tr=%#x "
+ "scale=%s",
+ buf, timestamp, crop.left, crop.top, crop.right, crop.bottom,
+ transform, scalingModeName(scalingMode));
+
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
+ case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
+ break;
+ default:
+ ALOGE("unknown scaling mode: %d", scalingMode);
+ return -EINVAL;
+ }
+
+ sp<IConsumerListener> listener;
+
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("queueBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = getMaxBufferCountLocked(async);
+ if (async && mOverrideMaxBufferCount) {
+ // FIXME: some drivers are manually setting the buffer-count (which they
+ // shouldn't), so we do this extra test here to handle that case.
+ // This is TEMPORARY, until we get this fixed.
+ if (mOverrideMaxBufferCount < maxBufferCount) {
+ ALOGE("queueBuffer: async mode is invalid with buffercount override");
+ return BAD_VALUE;
+ }
+ }
+ if (buf < 0 || buf >= maxBufferCount) {
+ ALOGE("queueBuffer: slot index out of range [0, %d]: %d",
+ maxBufferCount, buf);
+ return -EINVAL;
+ } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+ ALOGE("queueBuffer: slot %d is not owned by the client "
+ "(state=%d)", buf, mSlots[buf].mBufferState);
+ return -EINVAL;
+ } else if (!mSlots[buf].mRequestBufferCalled) {
+ ALOGE("queueBuffer: slot %d was enqueued without requesting a "
+ "buffer", buf);
+ return -EINVAL;
+ }
+
+ ALOGV("queueBuffer: slot=%d/%llu time=%#llx crop=[%d,%d,%d,%d] "
+ "tr=%#x scale=%s",
+ buf, mFrameCounter + 1, timestamp,
+ crop.left, crop.top, crop.right, crop.bottom,
+ transform, scalingModeName(scalingMode));
+
+ const sp<GraphicBuffer>& graphicBuffer(mSlots[buf].mGraphicBuffer);
+ Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
+ Rect croppedCrop;
+ crop.intersect(bufferRect, &croppedCrop);
+ if (croppedCrop != crop) {
+ ALOGE("queueBuffer: crop rect is not contained within the "
+ "buffer in slot %d", buf);
+ return -EINVAL;
+ }
+
+ mSlots[buf].mFence = fence;
+ mSlots[buf].mBufferState = BufferSlot::QUEUED;
+ mFrameCounter++;
+ mSlots[buf].mFrameNumber = mFrameCounter;
+
+ BufferItem item;
+ item.mAcquireCalled = mSlots[buf].mAcquireCalled;
+ item.mGraphicBuffer = mSlots[buf].mGraphicBuffer;
+ item.mCrop = crop;
+ item.mTransform = transform & ~NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+ item.mTransformToDisplayInverse = bool(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
+ item.mScalingMode = scalingMode;
+ item.mTimestamp = timestamp;
+ item.mIsAutoTimestamp = isAutoTimestamp;
+ item.mFrameNumber = mFrameCounter;
+ item.mBuf = buf;
+ item.mFence = fence;
+ item.mIsDroppable = mDequeueBufferCannotBlock || async;
+
+ if (mQueue.empty()) {
+ // when the queue is empty, we can ignore "mDequeueBufferCannotBlock", and
+ // simply queue this buffer.
+ mQueue.push_back(item);
+ } else {
+ // when the queue is not empty, we need to look at the front buffer
+ // state and see if we need to replace it.
+ Fifo::iterator front(mQueue.begin());
+ if (front->mIsDroppable || !mSynchronousMode) {
+ // buffer slot currently queued is marked free if still tracked
+ if (stillTracking(front)) {
+ mSlots[front->mBuf].mBufferState = BufferSlot::FREE;
+ // reset the frame number of the freed buffer so that it is the first in
+ // line to be dequeued again.
+ mSlots[front->mBuf].mFrameNumber = 0;
+ }
+ // and we record the new buffer in the queued list
+ *front = item;
+ } else {
+ mQueue.push_back(item);
+ }
+ }
+ // always signals that an additional frame should be consumed
+ // to handle max acquired buffer count reached case.
+ listener = mConsumerListener;
+
+ mBufferHasBeenQueued = true;
+ mDequeueCondition.broadcast();
+
+ output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint,
+ mQueue.size());
+
+ } // scope for the lock
+
+ // call back without lock held
+ if (listener != 0) {
+ listener->onFrameAvailable();
+ }
+ return NO_ERROR;
+}
+
+void GonkBufferQueue::cancelBuffer(int buf, const sp<Fence>& fence) {
+ ATRACE_CALL();
+ ALOGV("cancelBuffer: slot=%d", buf);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGW("cancelBuffer: GonkBufferQueue has been abandoned!");
+ return;
+ }
+
+ if (buf < 0 || buf >= NUM_BUFFER_SLOTS) {
+ ALOGE("cancelBuffer: slot index out of range [0, %d]: %d",
+ NUM_BUFFER_SLOTS, buf);
+ return;
+ } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+ ALOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
+ buf, mSlots[buf].mBufferState);
+ return;
+ } else if (fence == NULL) {
+ ALOGE("cancelBuffer: fence is NULL");
+ return;
+ }
+ mSlots[buf].mBufferState = BufferSlot::FREE;
+ mSlots[buf].mFrameNumber = 0;
+ mSlots[buf].mFence = fence;
+ mDequeueCondition.broadcast();
+}
+
+
+status_t GonkBufferQueue::connect(const sp<IBinder>& token,
+ int api, bool producerControlledByApp, QueueBufferOutput* output) {
+ ATRACE_CALL();
+ ALOGV("connect: api=%d producerControlledByApp=%s", api,
+ producerControlledByApp ? "true" : "false");
+ Mutex::Autolock lock(mMutex);
+
+retry:
+ if (mAbandoned) {
+ ALOGE("connect: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mConsumerListener == NULL) {
+ ALOGE("connect: GonkBufferQueue has no consumer!");
+ return NO_INIT;
+ }
+
+ if (mConnectedApi != NO_CONNECTED_API) {
+ ALOGE("connect: already connected (cur=%d, req=%d)",
+ mConnectedApi, api);
+ return -EINVAL;
+ }
+
+ // If we disconnect and reconnect quickly, we can be in a state where our slots are
+ // empty but we have many buffers in the queue. This can cause us to run out of
+ // memory if we outrun the consumer. Wait here if it looks like we have too many
+ // buffers queued up.
+ int maxBufferCount = getMaxBufferCountLocked(false); // worst-case, i.e. largest value
+ if (mQueue.size() > (size_t) maxBufferCount) {
+ // TODO: make this bound tighter?
+ ALOGV("queue size is %d, waiting", mQueue.size());
+ mDequeueCondition.wait(mMutex);
+ goto retry;
+ }
+
+ int err = NO_ERROR;
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ mConnectedApi = api;
+ output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint, mQueue.size());
+
+ // set-up a death notification so that we can disconnect
+ // automatically when/if the remote producer dies.
+ if (token != NULL && token->remoteBinder() != NULL) {
+ status_t err = token->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));
+ if (err == NO_ERROR) {
+ mConnectedProducerToken = token;
+ } else {
+ ALOGE("linkToDeath failed: %s (%d)", strerror(-err), err);
+ }
+ }
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ mBufferHasBeenQueued = false;
+ mDequeueBufferCannotBlock = mConsumerControlledByApp && producerControlledByApp;
+
+ return err;
+}
+
+void GonkBufferQueue::binderDied(const wp<IBinder>& who) {
+ // If we're here, it means that a producer we were connected to died.
+ // We're GUARANTEED that we still are connected to it because it has no other way
+ // to get disconnected -- or -- we wouldn't be here because we're removing this
+ // callback upon disconnect. Therefore, it's okay to read mConnectedApi without
+ // synchronization here.
+ int api = mConnectedApi;
+ this->disconnect(api);
+}
+
+status_t GonkBufferQueue::disconnect(int api) {
+ ATRACE_CALL();
+ ALOGV("disconnect: api=%d", api);
+
+ int err = NO_ERROR;
+ sp<IConsumerListener> listener;
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ // it is not really an error to disconnect after the surface
+ // has been abandoned, it should just be a no-op.
+ return NO_ERROR;
+ }
+
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ if (mConnectedApi == api) {
+ freeAllBuffersLocked();
+ mConnectedApi = NO_CONNECTED_API;
+ mDequeueCondition.broadcast();
+ listener = mConsumerListener;
+ } else {
+ ALOGE("disconnect: connected to another api (cur=%d, req=%d)",
+ mConnectedApi, api);
+ err = -EINVAL;
+ }
+ break;
+ default:
+ ALOGE("disconnect: unknown API %d", api);
+ err = -EINVAL;
+ break;
+ }
+ }
+
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return err;
+}
+
+void GonkBufferQueue::dumpToString(String8& result, const char* prefix) const {
+ Mutex::Autolock _l(mMutex);
+
+ String8 fifo;
+ int fifoSize = 0;
+ Fifo::const_iterator i(mQueue.begin());
+ while (i != mQueue.end()) {
+ fifo.appendFormat("%02d:%p crop=[%d,%d,%d,%d], "
+ "xform=0x%02x, time=%#llx, scale=%s\n",
+ i->mBuf, i->mGraphicBuffer.get(),
+ i->mCrop.left, i->mCrop.top, i->mCrop.right,
+ i->mCrop.bottom, i->mTransform, i->mTimestamp,
+ scalingModeName(i->mScalingMode)
+ );
+ i++;
+ fifoSize++;
+ }
+
+
+ result.appendFormat(
+ "%s-BufferQueue mMaxAcquiredBufferCount=%d, mDequeueBufferCannotBlock=%d, default-size=[%dx%d], "
+ "default-format=%d, transform-hint=%02x, FIFO(%d)={%s}\n",
+ prefix, mMaxAcquiredBufferCount, mDequeueBufferCannotBlock, mDefaultWidth,
+ mDefaultHeight, mDefaultBufferFormat, mTransformHint,
+ fifoSize, fifo.string());
+
+ struct {
+ const char * operator()(int state) const {
+ switch (state) {
+ case BufferSlot::DEQUEUED: return "DEQUEUED";
+ case BufferSlot::QUEUED: return "QUEUED";
+ case BufferSlot::FREE: return "FREE";
+ case BufferSlot::ACQUIRED: return "ACQUIRED";
+ default: return "Unknown";
+ }
+ }
+ } stateName;
+
+ // just trim the free buffers to not spam the dump
+ int maxBufferCount = 0;
+ for (int i=NUM_BUFFER_SLOTS-1 ; i>=0 ; i--) {
+ const BufferSlot& slot(mSlots[i]);
+ if ((slot.mBufferState != BufferSlot::FREE) || (slot.mGraphicBuffer != NULL)) {
+ maxBufferCount = i+1;
+ break;
+ }
+ }
+
+ for (int i=0 ; i<maxBufferCount ; i++) {
+ const BufferSlot& slot(mSlots[i]);
+ const sp<GraphicBuffer>& buf(slot.mGraphicBuffer);
+ result.appendFormat(
+ "%s%s[%02d:%p] state=%-8s",
+ prefix, (slot.mBufferState == BufferSlot::ACQUIRED)?">":" ", i, buf.get(),
+ stateName(slot.mBufferState)
+ );
+
+ if (buf != NULL) {
+ result.appendFormat(
+ ", %p [%4ux%4u:%4u,%3X]",
+ buf->handle, buf->width, buf->height, buf->stride,
+ buf->format);
+ }
+ result.append("\n");
+ }
+}
+
+void GonkBufferQueue::freeAllBuffersLocked()
+{
+ ALOGW_IF(!mQueue.isEmpty(),
+ "freeAllBuffersLocked called but mQueue is not empty");
+ mQueue.clear();
+ mBufferHasBeenQueued = false;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ mSlots[i].mGraphicBuffer = 0;
+ if (mSlots[i].mTextureClient) {
+ mSlots[i].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[i].mTextureClient);
+ mSlots[i].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[i].mNeedsCleanupOnRelease = true;
+ }
+ mSlots[i].mBufferState = BufferSlot::FREE;
+ mSlots[i].mFrameNumber = 0;
+ mSlots[i].mAcquireCalled = false;
+ // destroy fence as GonkBufferQueue now takes ownership
+ mSlots[i].mFence = Fence::NO_FENCE;
+ }
+}
+
+status_t GonkBufferQueue::acquireBuffer(BufferItem *buffer, nsecs_t expectedPresent) {
+ ATRACE_CALL();
+ Mutex::Autolock _l(mMutex);
+
+ // Check that the consumer doesn't currently have the maximum number of
+ // buffers acquired. We allow the max buffer count to be exceeded by one
+ // buffer, so that the consumer can successfully set up the newly acquired
+ // buffer before releasing the old one.
+ int numAcquiredBuffers = 0;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
+ numAcquiredBuffers++;
+ }
+ }
+ if (numAcquiredBuffers >= mMaxAcquiredBufferCount+1) {
+ ALOGE("acquireBuffer: max acquired buffer count reached: %d (max=%d)",
+ numAcquiredBuffers, mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+
+ // check if queue is empty
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ if (mQueue.empty()) {
+ return NO_BUFFER_AVAILABLE;
+ }
+
+ Fifo::iterator front(mQueue.begin());
+
+ // If expectedPresent is specified, we may not want to return a buffer yet.
+ // If it's specified and there's more than one buffer queued, we may
+ // want to drop a buffer.
+ if (expectedPresent != 0) {
+ const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second
+
+ // The "expectedPresent" argument indicates when the buffer is expected
+ // to be presented on-screen. If the buffer's desired-present time
+ // is earlier (less) than expectedPresent, meaning it'll be displayed
+ // on time or possibly late if we show it ASAP, we acquire and return
+ // it. If we don't want to display it until after the expectedPresent
+ // time, we return PRESENT_LATER without acquiring it.
+ //
+ // To be safe, we don't defer acquisition if expectedPresent is
+ // more than one second in the future beyond the desired present time
+ // (i.e. we'd be holding the buffer for a long time).
+ //
+ // NOTE: code assumes monotonic time values from the system clock are
+ // positive.
+
+ // Start by checking to see if we can drop frames. We skip this check
+ // if the timestamps are being auto-generated by Surface -- if the
+ // app isn't generating timestamps explicitly, they probably don't
+ // want frames to be discarded based on them.
+ while (mQueue.size() > 1 && !mQueue[0].mIsAutoTimestamp) {
+ // If entry[1] is timely, drop entry[0] (and repeat). We apply
+ // an additional criteria here: we only drop the earlier buffer if
+ // our desiredPresent falls within +/- 1 second of the expected
+ // present. Otherwise, bogus desiredPresent times (e.g. 0 or
+ // a small relative timestamp), which normally mean "ignore the
+ // timestamp and acquire immediately", would cause us to drop
+ // frames.
+ //
+ // We may want to add an additional criteria: don't drop the
+ // earlier buffer if entry[1]'s fence hasn't signaled yet.
+ //
+ // (Vector front is [0], back is [size()-1])
+ const BufferItem& bi(mQueue[1]);
+ nsecs_t desiredPresent = bi.mTimestamp;
+ if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
+ desiredPresent > expectedPresent) {
+ // This buffer is set to display in the near future, or
+ // desiredPresent is garbage. Either way we don't want to
+ // drop the previous buffer just to get this on screen sooner.
+ ALOGV("pts nodrop: des=%lld expect=%lld (%lld) now=%lld",
+ desiredPresent, expectedPresent, desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ break;
+ }
+ ALOGV("pts drop: queue1des=%lld expect=%lld size=%d",
+ desiredPresent, expectedPresent, mQueue.size());
+ if (stillTracking(front)) {
+ // front buffer is still in mSlots, so mark the slot as free
+ mSlots[front->mBuf].mBufferState = BufferSlot::FREE;
+ }
+ mQueue.erase(front);
+ front = mQueue.begin();
+ }
+
+ // See if the front buffer is due.
+ nsecs_t desiredPresent = front->mTimestamp;
+ if (desiredPresent > expectedPresent &&
+ desiredPresent < expectedPresent + MAX_REASONABLE_NSEC) {
+ ALOGV("pts defer: des=%lld expect=%lld (%lld) now=%lld",
+ desiredPresent, expectedPresent, desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ return PRESENT_LATER;
+ }
+
+ ALOGV("pts accept: des=%lld expect=%lld (%lld) now=%lld",
+ desiredPresent, expectedPresent, desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ }
+
+ int buf = front->mBuf;
+ buffer->mGraphicBuffer = mSlots[buf].mGraphicBuffer;
+ buffer->mFrameNumber = mSlots[buf].mFrameNumber;
+ buffer->mBuf = buf;
+ buffer->mFence = mSlots[buf].mFence;
+ ATRACE_BUFFER_INDEX(buf);
+
+ ALOGV("acquireBuffer: acquiring { slot=%d/%llu, buffer=%p }",
+ front->mBuf, front->mFrameNumber,
+ front->mGraphicBuffer->handle);
+ // if front buffer still being tracked update slot state
+ if (stillTracking(front)) {
+ mSlots[buf].mAcquireCalled = true;
+ mSlots[buf].mNeedsCleanupOnRelease = false;
+ mSlots[buf].mBufferState = BufferSlot::ACQUIRED;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ }
+
+ // If the buffer has previously been acquired by the consumer, set
+ // mGraphicBuffer to NULL to avoid unnecessarily remapping this
+ // buffer on the consumer side.
+ //if (buffer->mAcquireCalled) {
+ // buffer->mGraphicBuffer = NULL;
+ //}
+
+ mQueue.erase(front);
+ mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& fence) {
+ ATRACE_CALL();
+
+ if (buf == INVALID_BUFFER_SLOT || fence == NULL) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock _l(mMutex);
+
+ // If the frame number has changed because buffer has been reallocated,
+ // we can ignore this releaseBuffer for the old buffer.
+ //if (frameNumber != mSlots[buf].mFrameNumber) {
+ // return STALE_BUFFER_SLOT;
+ //}
+
+
+ // Internal state consistency checks:
+ // Make sure this buffers hasn't been queued while we were owning it (acquired)
+ Fifo::iterator front(mQueue.begin());
+ Fifo::const_iterator const end(mQueue.end());
+ while (front != end) {
+ if (front->mBuf == buf) {
+ LOG_ALWAYS_FATAL("[%s] received new buffer(#%lld) on slot #%d that has not yet been "
+ "acquired", mConsumerName.string(), frameNumber, buf);
+ break; // never reached
+ }
+ front++;
+ }
+
+ // The buffer can now only be released if its in the acquired state
+ if (mSlots[buf].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[buf].mFence = fence;
+ mSlots[buf].mBufferState = BufferSlot::FREE;
+ } else if (mSlots[buf].mNeedsCleanupOnRelease) {
+ ALOGV("releasing a stale buf %d its state was %d", buf, mSlots[buf].mBufferState);
+ mSlots[buf].mNeedsCleanupOnRelease = false;
+ return STALE_BUFFER_SLOT;
+ } else {
+ ALOGE("attempted to release buf %d but its state was %d", buf, mSlots[buf].mBufferState);
+ return -EINVAL;
+ }
+
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::consumerConnect(const sp<IConsumerListener>& consumerListener,
+ bool controlledByApp) {
+ ALOGV("consumerConnect controlledByApp=%s",
+ controlledByApp ? "true" : "false");
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("consumerConnect: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (consumerListener == NULL) {
+ ALOGE("consumerConnect: consumerListener may not be NULL");
+ return BAD_VALUE;
+ }
+
+ mConsumerListener = consumerListener;
+ mConsumerControlledByApp = controlledByApp;
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::consumerDisconnect() {
+ ALOGV("consumerDisconnect");
+ Mutex::Autolock lock(mMutex);
+
+ if (mConsumerListener == NULL) {
+ ALOGE("consumerDisconnect: No consumer is connected!");
+ return -EINVAL;
+ }
+
+ mAbandoned = true;
+ mConsumerListener = NULL;
+ mQueue.clear();
+ freeAllBuffersLocked();
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::getReleasedBuffers(uint32_t* slotMask) {
+ ALOGV("getReleasedBuffers");
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("getReleasedBuffers: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ uint32_t mask = 0;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (!mSlots[i].mAcquireCalled) {
+ mask |= 1 << i;
+ }
+ }
+
+ // Remove buffers in flight (on the queue) from the mask where acquire has
+ // been called, as the consumer will not receive the buffer address, so
+ // it should not free these slots.
+ Fifo::iterator front(mQueue.begin());
+ while (front != mQueue.end()) {
+ if (front->mAcquireCalled)
+ mask &= ~(1 << front->mBuf);
+ front++;
+ }
+
+ *slotMask = mask;
+
+ ALOGV("getReleasedBuffers: returning mask %#x", mask);
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setDefaultBufferSize(uint32_t w, uint32_t h) {
+ ALOGV("setDefaultBufferSize: w=%d, h=%d", w, h);
+ if (!w || !h) {
+ ALOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)",
+ w, h);
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mMutex);
+ mDefaultWidth = w;
+ mDefaultHeight = h;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setDefaultMaxBufferCount(int bufferCount) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+ return setDefaultMaxBufferCountLocked(bufferCount);
+}
+
+status_t GonkBufferQueue::disableAsyncBuffer() {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+ if (mConsumerListener != NULL) {
+ ALOGE("disableAsyncBuffer: consumer already connected!");
+ return INVALID_OPERATION;
+ }
+ mUseAsyncBuffer = false;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+ if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > MAX_MAX_ACQUIRED_BUFFERS) {
+ ALOGE("setMaxAcquiredBufferCount: invalid count specified: %d",
+ maxAcquiredBuffers);
+ return BAD_VALUE;
+ }
+ if (mConnectedApi != NO_CONNECTED_API) {
+ return INVALID_OPERATION;
+ }
+ mMaxAcquiredBufferCount = maxAcquiredBuffers;
+ return NO_ERROR;
+}
+
+int GonkBufferQueue::getMinUndequeuedBufferCount(bool async) const {
+ // if dequeueBuffer is allowed to error out, we don't have to
+ // add an extra buffer.
+ if (!mUseAsyncBuffer)
+ return mMaxAcquiredBufferCount;
+
+ // we're in async mode, or we want to prevent the app to
+ // deadlock itself, we throw-in an extra buffer to guarantee it.
+ if (mDequeueBufferCannotBlock || async || !mSynchronousMode)
+ return mMaxAcquiredBufferCount + 1;
+
+ return mMaxAcquiredBufferCount;
+}
+
+int GonkBufferQueue::getMinMaxBufferCountLocked(bool async) const {
+ return getMinUndequeuedBufferCount(async) + 1;
+}
+
+int GonkBufferQueue::getMaxBufferCountLocked(bool async) const {
+ int minMaxBufferCount = getMinMaxBufferCountLocked(async);
+
+ int maxBufferCount = mDefaultMaxBufferCount;
+ if (maxBufferCount < minMaxBufferCount) {
+ maxBufferCount = minMaxBufferCount;
+ }
+ if (mOverrideMaxBufferCount != 0) {
+ assert(mOverrideMaxBufferCount >= minMaxBufferCount);
+ maxBufferCount = mOverrideMaxBufferCount;
+ }
+
+ // Any buffers that are dequeued by the producer or sitting in the queue
+ // waiting to be consumed need to have their slots preserved. Such
+ // buffers will temporarily keep the max buffer count up until the slots
+ // no longer need to be preserved.
+ for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
+ BufferSlot::BufferState state = mSlots[i].mBufferState;
+ if (state == BufferSlot::QUEUED || state == BufferSlot::DEQUEUED) {
+ maxBufferCount = i + 1;
+ }
+ }
+
+ return maxBufferCount;
+}
+
+bool GonkBufferQueue::stillTracking(const BufferItem *item) const {
+ const BufferSlot &slot = mSlots[item->mBuf];
+
+ ALOGV("stillTracking?: item: { slot=%d/%llu, buffer=%p }, "
+ "slot: { slot=%d/%llu, buffer=%p }",
+ item->mBuf, item->mFrameNumber,
+ (item->mGraphicBuffer.get() ? item->mGraphicBuffer->handle : 0),
+ item->mBuf, slot.mFrameNumber,
+ (slot.mGraphicBuffer.get() ? slot.mGraphicBuffer->handle : 0));
+
+ // Compare item with its original buffer slot. We can check the slot
+ // as the buffer would not be moved to a different slot by the producer.
+ return (slot.mGraphicBuffer != NULL &&
+ item->mGraphicBuffer->handle == slot.mGraphicBuffer->handle);
+}
+
+GonkBufferQueue::ProxyConsumerListener::ProxyConsumerListener(
+ const wp<ConsumerListener>& consumerListener):
+ mConsumerListener(consumerListener) {}
+
+GonkBufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {}
+
+void GonkBufferQueue::ProxyConsumerListener::onFrameAvailable() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkBufferQueue::ProxyConsumerListener::onBuffersReleased() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueKK.h b/widget/gonk/nativewindow/GonkBufferQueueKK.h
new file mode 100644
index 000000000..01905427d
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueKK.h
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUE_KK_H
+#define NATIVEWINDOW_GONKBUFFERQUEUE_KK_H
+
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/IGraphicBufferProducer.h>
+#include "IGonkGraphicBufferConsumer.h"
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "mozilla/layers/LayersSurfaces.h"
+#include "mozilla/layers/TextureClient.h"
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class GonkBufferQueue : public BnGraphicBufferProducer,
+ public BnGonkGraphicBufferConsumer,
+ private IBinder::DeathRecipient
+{
+ typedef mozilla::layers::TextureClient TextureClient;
+
+public:
+ enum { MIN_UNDEQUEUED_BUFFERS = 2 };
+ enum { NUM_BUFFER_SLOTS = 32 };
+ enum { NO_CONNECTED_API = 0 };
+ enum { INVALID_BUFFER_SLOT = -1 };
+ enum { STALE_BUFFER_SLOT = 1, NO_BUFFER_AVAILABLE, PRESENT_LATER };
+
+ // When in async mode we reserve two slots in order to guarantee that the
+ // producer and consumer can run asynchronously.
+ enum { MAX_MAX_ACQUIRED_BUFFERS = NUM_BUFFER_SLOTS - 2 };
+
+ // for backward source compatibility
+ typedef ::android::ConsumerListener ConsumerListener;
+
+ // ProxyConsumerListener is a ConsumerListener implementation that keeps a weak
+ // reference to the actual consumer object. It forwards all calls to that
+ // consumer object so long as it exists.
+ //
+ // This class exists to avoid having a circular reference between the
+ // GonkBufferQueue object and the consumer object. The reason this can't be a weak
+ // reference in the GonkBufferQueue class is because we're planning to expose the
+ // consumer side of a GonkBufferQueue as a binder interface, which doesn't support
+ // weak references.
+ class ProxyConsumerListener : public BnConsumerListener {
+ public:
+ ProxyConsumerListener(const wp<ConsumerListener>& consumerListener);
+ virtual ~ProxyConsumerListener();
+ virtual void onFrameAvailable();
+ virtual void onBuffersReleased();
+ private:
+ // mConsumerListener is a weak reference to the IConsumerListener. This is
+ // the raison d'etre of ProxyConsumerListener.
+ wp<ConsumerListener> mConsumerListener;
+ };
+
+
+ // BufferQueue manages a pool of gralloc memory slots to be used by
+ // producers and consumers. allocator is used to allocate all the
+ // needed gralloc buffers.
+ GonkBufferQueue(bool allowSynchronousMode = true,
+ const sp<IGraphicBufferAlloc>& allocator = NULL);
+ virtual ~GonkBufferQueue();
+
+ /*
+ * IBinder::DeathRecipient interface
+ */
+
+ virtual void binderDied(const wp<IBinder>& who);
+
+ /*
+ * IGraphicBufferProducer interface
+ */
+
+ // Query native window attributes. The "what" values are enumerated in
+ // window.h (e.g. NATIVE_WINDOW_FORMAT).
+ virtual int query(int what, int* value);
+
+ // setBufferCount updates the number of available buffer slots. If this
+ // method succeeds, buffer slots will be both unallocated and owned by
+ // the GonkBufferQueue object (i.e. they are not owned by the producer or
+ // consumer).
+ //
+ // This will fail if the producer has dequeued any buffers, or if
+ // bufferCount is invalid. bufferCount must generally be a value
+ // between the minimum undequeued buffer count and NUM_BUFFER_SLOTS
+ // (inclusive). It may also be set to zero (the default) to indicate
+ // that the producer does not wish to set a value. The minimum value
+ // can be obtained by calling query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ // ...).
+ //
+ // This may only be called by the producer. The consumer will be told
+ // to discard buffers through the onBuffersReleased callback.
+ virtual status_t setBufferCount(int bufferCount);
+
+ // requestBuffer returns the GraphicBuffer for slot N.
+ //
+ // In normal operation, this is called the first time slot N is returned
+ // by dequeueBuffer. It must be called again if dequeueBuffer returns
+ // flags indicating that previously-returned buffers are no longer valid.
+ virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
+
+ // dequeueBuffer gets the next buffer slot index for the producer to use.
+ // If a buffer slot is available then that slot index is written to the
+ // location pointed to by the buf argument and a status of OK is returned.
+ // If no slot is available then a status of -EBUSY is returned and buf is
+ // unmodified.
+ //
+ // The fence parameter will be updated to hold the fence associated with
+ // the buffer. The contents of the buffer must not be overwritten until the
+ // fence signals. If the fence is Fence::NO_FENCE, the buffer may be
+ // written immediately.
+ //
+ // The width and height parameters must be no greater than the minimum of
+ // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv).
+ // An error due to invalid dimensions might not be reported until
+ // updateTexImage() is called. If width and height are both zero, the
+ // default values specified by setDefaultBufferSize() are used instead.
+ //
+ // The pixel formats are enumerated in graphics.h, e.g.
+ // HAL_PIXEL_FORMAT_RGBA_8888. If the format is 0, the default format
+ // will be used.
+ //
+ // The usage argument specifies gralloc buffer usage flags. The values
+ // are enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER. These
+ // will be merged with the usage flags specified by setConsumerUsageBits.
+ //
+ // The return value may be a negative error value or a non-negative
+ // collection of flags. If the flags are set, the return values are
+ // valid, but additional actions must be performed.
+ //
+ // If IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION is set, the
+ // producer must discard cached GraphicBuffer references for the slot
+ // returned in buf.
+ // If IGraphicBufferProducer::RELEASE_ALL_BUFFERS is set, the producer
+ // must discard cached GraphicBuffer references for all slots.
+ //
+ // In both cases, the producer will need to call requestBuffer to get a
+ // GraphicBuffer handle for the returned slot.
+ virtual status_t dequeueBuffer(int *buf, sp<Fence>* fence, bool async,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage);
+
+ // queueBuffer returns a filled buffer to the GonkBufferQueue.
+ //
+ // Additional data is provided in the QueueBufferInput struct. Notably,
+ // a timestamp must be provided for the buffer. The timestamp is in
+ // nanoseconds, and must be monotonically increasing. Its other semantics
+ // (zero point, etc) are producer-specific and should be documented by the
+ // producer.
+ //
+ // The caller may provide a fence that signals when all rendering
+ // operations have completed. Alternatively, NO_FENCE may be used,
+ // indicating that the buffer is ready immediately.
+ //
+ // Some values are returned in the output struct: the current settings
+ // for default width and height, the current transform hint, and the
+ // number of queued buffers.
+ virtual status_t queueBuffer(int buf,
+ const QueueBufferInput& input, QueueBufferOutput* output);
+
+ // cancelBuffer returns a dequeued buffer to the GonkBufferQueue, but doesn't
+ // queue it for use by the consumer.
+ //
+ // The buffer will not be overwritten until the fence signals. The fence
+ // will usually be the one obtained from dequeueBuffer.
+ virtual void cancelBuffer(int buf, const sp<Fence>& fence);
+
+ // setSynchronousMode sets whether dequeueBuffer is synchronous or
+ // asynchronous. In synchronous mode, dequeueBuffer blocks until
+ // a buffer is available, the currently bound buffer can be dequeued and
+ // queued buffers will be acquired in order. In asynchronous mode,
+ // a queued buffer may be replaced by a subsequently queued buffer.
+ //
+ // The default mode is synchronous.
+ // This should be called only during initialization.
+ virtual status_t setSynchronousMode(bool enabled);
+
+ // connect attempts to connect a producer API to the GonkBufferQueue. This
+ // must be called before any other IGraphicBufferProducer methods are
+ // called except for getAllocator. A consumer must already be connected.
+ //
+ // This method will fail if connect was previously called on the
+ // GonkBufferQueue and no corresponding disconnect call was made (i.e. if
+ // it's still connected to a producer).
+ //
+ // APIs are enumerated in window.h (e.g. NATIVE_WINDOW_API_CPU).
+ virtual status_t connect(const sp<IBinder>& token,
+ int api, bool producerControlledByApp, QueueBufferOutput* output);
+
+ // disconnect attempts to disconnect a producer API from the GonkBufferQueue.
+ // Calling this method will cause any subsequent calls to other
+ // IGraphicBufferProducer methods to fail except for getAllocator and connect.
+ // Successfully calling connect after this will allow the other methods to
+ // succeed again.
+ //
+ // This method will fail if the the GonkBufferQueue is not currently
+ // connected to the specified producer API.
+ virtual status_t disconnect(int api);
+
+ /*
+ * IGraphicBufferConsumer interface
+ */
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the GonkBufferQueue. If no buffer is pending then it returns -EINVAL. If a
+ // buffer is successfully acquired, the information about the buffer is
+ // returned in BufferItem. If the buffer returned had previously been
+ // acquired then the BufferItem::mGraphicBuffer field of buffer is set to
+ // NULL and it is assumed that the consumer still holds a reference to the
+ // buffer.
+ //
+ // If presentWhen is nonzero, it indicates the time when the buffer will
+ // be displayed on screen. If the buffer's timestamp is farther in the
+ // future, the buffer won't be acquired, and PRESENT_LATER will be
+ // returned. The presentation time is in nanoseconds, and the time base
+ // is CLOCK_MONOTONIC.
+ virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen);
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // GonkBufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use. frameNumber is used to indentify the exact buffer returned.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ //
+ // Note that the dependencies on EGL will be removed once we switch to using
+ // the Android HW Sync HAL.
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
+ const sp<Fence>& releaseFence);
+
+ // consumerConnect connects a consumer to the GonkBufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // GonkBufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the GonkBufferQueue by the producer to fail.
+ // controlledByApp indicates whether the consumer is controlled by
+ // the application.
+ //
+ // consumer may not be NULL.
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp);
+
+ // consumerDisconnect disconnects a consumer from the GonkBufferQueue. All
+ // buffers will be freed and the GonkBufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the GonkBufferQueue by the producer to
+ // fail.
+ virtual status_t consumerDisconnect();
+
+ // getReleasedBuffers sets the value pointed to by slotMask to a bit mask
+ // indicating which buffer slots have been released by the GonkBufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ virtual status_t getReleasedBuffers(uint32_t* slotMask);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ virtual status_t setDefaultMaxBufferCount(int bufferCount);
+
+ // disableAsyncBuffer disables the extra buffer used in async mode
+ // (when both producer and consumer have set their "isControlledByApp"
+ // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
+ //
+ // This can only be called before consumerConnect().
+ virtual status_t disableAsyncBuffer();
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the GonkBufferQueue.
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
+
+ // setConsumerName sets the name used in logging
+ virtual void setConsumerName(const String8& name);
+
+ // setDefaultBufferFormat allows the GonkBufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ virtual status_t setConsumerUsageBits(uint32_t usage);
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ virtual status_t setTransformHint(uint32_t hint);
+
+ // dump our state in a String
+ virtual void dumpToString(String8& result, const char* prefix) const;
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ int getSlotFromTextureClientLocked(TextureClient* client) const;
+
+private:
+ // freeBufferLocked frees the GraphicBuffer and sync resources for the
+ // given slot.
+ //void freeBufferLocked(int index);
+
+ // freeAllBuffersLocked frees the GraphicBuffer and sync resources for
+ // all slots.
+ void freeAllBuffersLocked();
+
+ // setDefaultMaxBufferCountLocked sets the maximum number of buffer slots
+ // that will be used if the producer does not override the buffer slot
+ // count. The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ // The initial default is 2.
+ status_t setDefaultMaxBufferCountLocked(int count);
+
+ // getMinUndequeuedBufferCount returns the minimum number of buffers
+ // that must remain in a state other than DEQUEUED.
+ // The async parameter tells whether we're in asynchronous mode.
+ int getMinUndequeuedBufferCount(bool async) const;
+
+ // getMinBufferCountLocked returns the minimum number of buffers allowed
+ // given the current GonkBufferQueue state.
+ // The async parameter tells whether we're in asynchronous mode.
+ int getMinMaxBufferCountLocked(bool async) const;
+
+ // getMaxBufferCountLocked returns the maximum number of buffers that can
+ // be allocated at once. This value depends upon the following member
+ // variables:
+ //
+ // mDequeueBufferCannotBlock
+ // mMaxAcquiredBufferCount
+ // mDefaultMaxBufferCount
+ // mOverrideMaxBufferCount
+ // async parameter
+ //
+ // Any time one of these member variables is changed while a producer is
+ // connected, mDequeueCondition must be broadcast.
+ int getMaxBufferCountLocked(bool async) const;
+
+ // stillTracking returns true iff the buffer item is still being tracked
+ // in one of the slots.
+ bool stillTracking(const BufferItem *item) const;
+
+ struct BufferSlot {
+
+ BufferSlot()
+ : mBufferState(BufferSlot::FREE),
+ mRequestBufferCalled(false),
+ mFrameNumber(0),
+ mAcquireCalled(false),
+ mNeedsCleanupOnRelease(false) {
+ }
+
+ // mGraphicBuffer points to the buffer allocated for this slot or is NULL
+ // if no buffer has been allocated.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mTextureClient is a thin abstraction over remotely allocated GraphicBuffer.
+ RefPtr<TextureClient> mTextureClient;
+
+ // BufferState represents the different states in which a buffer slot
+ // can be. All slots are initially FREE.
+ enum BufferState {
+ // FREE indicates that the buffer is available to be dequeued
+ // by the producer. The buffer may be in use by the consumer for
+ // a finite time, so the buffer must not be modified until the
+ // associated fence is signaled.
+ //
+ // The slot is "owned" by GonkBufferQueue. It transitions to DEQUEUED
+ // when dequeueBuffer is called.
+ FREE = 0,
+
+ // DEQUEUED indicates that the buffer has been dequeued by the
+ // producer, but has not yet been queued or canceled. The
+ // producer may modify the buffer's contents as soon as the
+ // associated ready fence is signaled.
+ //
+ // The slot is "owned" by the producer. It can transition to
+ // QUEUED (via queueBuffer) or back to FREE (via cancelBuffer).
+ DEQUEUED = 1,
+
+ // QUEUED indicates that the buffer has been filled by the
+ // producer and queued for use by the consumer. The buffer
+ // contents may continue to be modified for a finite time, so
+ // the contents must not be accessed until the associated fence
+ // is signaled.
+ //
+ // The slot is "owned" by GonkBufferQueue. It can transition to
+ // ACQUIRED (via acquireBuffer) or to FREE (if another buffer is
+ // queued in asynchronous mode).
+ QUEUED = 2,
+
+ // ACQUIRED indicates that the buffer has been acquired by the
+ // consumer. As with QUEUED, the contents must not be accessed
+ // by the consumer until the fence is signaled.
+ //
+ // The slot is "owned" by the consumer. It transitions to FREE
+ // when releaseBuffer is called.
+ ACQUIRED = 3
+ };
+
+ // mBufferState is the current state of this buffer slot.
+ BufferState mBufferState;
+
+ // mRequestBufferCalled is used for validating that the producer did
+ // call requestBuffer() when told to do so. Technically this is not
+ // needed but useful for debugging and catching producer bugs.
+ bool mRequestBufferCalled;
+
+ // mFrameNumber is the number of the queued frame for this slot. This
+ // is used to dequeue buffers in LRU order (useful because buffers
+ // may be released before their release fence is signaled).
+ uint64_t mFrameNumber;
+
+ // mFence is a fence which will signal when work initiated by the
+ // previous owner of the buffer is finished. When the buffer is FREE,
+ // the fence indicates when the consumer has finished reading
+ // from the buffer, or when the producer has finished writing if it
+ // called cancelBuffer after queueing some writes. When the buffer is
+ // QUEUED, it indicates when the producer has finished filling the
+ // buffer. When the buffer is DEQUEUED or ACQUIRED, the fence has been
+ // passed to the consumer or producer along with ownership of the
+ // buffer, and mFence is set to NO_FENCE.
+ sp<Fence> mFence;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates whether this buffer needs to be cleaned up by the
+ // consumer. This is set when a buffer in ACQUIRED state is freed.
+ // It causes releaseBuffer to return STALE_BUFFER_SLOT.
+ bool mNeedsCleanupOnRelease;
+ };
+
+ // mSlots is the array of buffer slots that must be mirrored on the
+ // producer side. This allows buffer ownership to be transferred between
+ // the producer and consumer without sending a GraphicBuffer over binder.
+ // The entire array is initialized to NULL at construction time, and
+ // buffers are allocated for a slot when requestBuffer is called with
+ // that slot's index.
+ BufferSlot mSlots[NUM_BUFFER_SLOTS];
+
+ // mDefaultWidth holds the default width of allocated buffers. It is used
+ // in dequeueBuffer() if a width and height of zero is specified.
+ uint32_t mDefaultWidth;
+
+ // mDefaultHeight holds the default height of allocated buffers. It is used
+ // in dequeueBuffer() if a width and height of zero is specified.
+ uint32_t mDefaultHeight;
+
+ // mMaxAcquiredBufferCount is the number of buffers that the consumer may
+ // acquire at one time. It defaults to 1 and can be changed by the
+ // consumer via the setMaxAcquiredBufferCount method, but this may only be
+ // done when no producer is connected to the GonkBufferQueue.
+ //
+ // This value is used to derive the value returned for the
+ // MIN_UNDEQUEUED_BUFFERS query by the producer.
+ int mMaxAcquiredBufferCount;
+
+ // mDefaultMaxBufferCount is the default limit on the number of buffers
+ // that will be allocated at one time. This default limit is set by the
+ // consumer. The limit (as opposed to the default limit) may be
+ // overridden by the producer.
+ int mDefaultMaxBufferCount;
+
+ // mOverrideMaxBufferCount is the limit on the number of buffers that will
+ // be allocated at one time. This value is set by the image producer by
+ // calling setBufferCount. The default is zero, which means the producer
+ // doesn't care about the number of buffers in the pool. In that case
+ // mDefaultMaxBufferCount is used as the limit.
+ int mOverrideMaxBufferCount;
+
+ // mGraphicBufferAlloc is the connection to SurfaceFlinger that is used to
+ // allocate new GraphicBuffer objects.
+ sp<IGraphicBufferAlloc> mGraphicBufferAlloc;
+
+ // mConsumerListener is used to notify the connected consumer of
+ // asynchronous events that it may wish to react to. It is initially set
+ // to NULL and is written by consumerConnect and consumerDisconnect.
+ sp<IConsumerListener> mConsumerListener;
+
+ // mSynchronousMode whether we're in synchronous mode or not
+ bool mSynchronousMode;
+
+ // mConsumerControlledByApp whether the connected consumer is controlled by the
+ // application.
+ bool mConsumerControlledByApp;
+
+ // mDequeueBufferCannotBlock whether dequeueBuffer() isn't allowed to block.
+ // this flag is set during connect() when both consumer and producer are controlled
+ // by the application.
+ bool mDequeueBufferCannotBlock;
+
+ // mUseAsyncBuffer whether an extra buffer is used in async mode to prevent
+ // dequeueBuffer() from ever blocking.
+ bool mUseAsyncBuffer;
+
+ // mConnectedApi indicates the producer API that is currently connected
+ // to this GonkBufferQueue. It defaults to NO_CONNECTED_API (= 0), and gets
+ // updated by the connect and disconnect methods.
+ int mConnectedApi;
+
+ // mDequeueCondition condition used for dequeueBuffer in synchronous mode
+ mutable Condition mDequeueCondition;
+
+ // mQueue is a FIFO of queued buffers used in synchronous mode
+ typedef Vector<BufferItem> Fifo;
+ Fifo mQueue;
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume image buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the
+ // consumerDisconnect method. A GonkBufferQueue that has been abandoned will
+ // return the NO_INIT error from all IGraphicBufferProducer methods
+ // capable of returning an error.
+ bool mAbandoned;
+
+ // mConsumerName is a string used to identify the GonkBufferQueue in log
+ // messages. It is set by the setConsumerName method.
+ String8 mConsumerName;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkBufferQueue objects. It must be locked whenever the
+ // member variables are accessed.
+ mutable Mutex mMutex;
+
+ // mFrameCounter is the free running counter, incremented on every
+ // successful queueBuffer call, and buffer allocation.
+ uint64_t mFrameCounter;
+
+ // mBufferHasBeenQueued is true once a buffer has been queued. It is
+ // reset when something causes all buffers to be freed (e.g. changing the
+ // buffer count).
+ bool mBufferHasBeenQueued;
+
+ // mDefaultBufferFormat can be set so it will override
+ // the buffer format when it isn't specified in dequeueBuffer
+ uint32_t mDefaultBufferFormat;
+
+ // mConsumerUsageBits contains flags the consumer wants for GraphicBuffers
+ uint32_t mConsumerUsageBits;
+
+ // mTransformHint is used to optimize for screen rotations
+ uint32_t mTransformHint;
+
+ // mConnectedProducerToken is used to set a binder death notification on the producer
+ sp<IBinder> mConnectedProducerToken;
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_BUFFERQUEUE_H
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.cpp
new file mode 100644
index 000000000..7df72bf68
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.cpp
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GonkBufferItem.h"
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <system/window.h>
+
+namespace android {
+
+GonkBufferItem::GonkBufferItem() :
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mIsAutoTimestamp(false),
+ mFrameNumber(0),
+ mSlot(INVALID_BUFFER_SLOT),
+ mIsDroppable(false),
+ mAcquireCalled(false),
+ mTransformToDisplayInverse(false) {
+ mCrop.makeInvalid();
+}
+
+GonkBufferItem::operator IGonkGraphicBufferConsumer::BufferItem() const {
+ IGonkGraphicBufferConsumer::BufferItem bufferItem;
+ bufferItem.mGraphicBuffer = mGraphicBuffer;
+ bufferItem.mFence = mFence;
+ bufferItem.mCrop = mCrop;
+ bufferItem.mTransform = mTransform;
+ bufferItem.mScalingMode = mScalingMode;
+ bufferItem.mTimestamp = mTimestamp;
+ bufferItem.mIsAutoTimestamp = mIsAutoTimestamp;
+ bufferItem.mFrameNumber = mFrameNumber;
+ bufferItem.mBuf = mSlot;
+ bufferItem.mIsDroppable = mIsDroppable;
+ bufferItem.mAcquireCalled = mAcquireCalled;
+ bufferItem.mTransformToDisplayInverse = mTransformToDisplayInverse;
+ return bufferItem;
+}
+
+size_t GonkBufferItem::getPodSize() const {
+ size_t c = sizeof(mCrop) +
+ sizeof(mTransform) +
+ sizeof(mScalingMode) +
+ sizeof(mTimestamp) +
+ sizeof(mIsAutoTimestamp) +
+ sizeof(mFrameNumber) +
+ sizeof(mSlot) +
+ sizeof(mIsDroppable) +
+ sizeof(mAcquireCalled) +
+ sizeof(mTransformToDisplayInverse);
+ return c;
+}
+
+size_t GonkBufferItem::getFlattenedSize() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ if (mFence != 0) {
+ c += mFence->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ return sizeof(int32_t) + c + getPodSize();
+}
+
+size_t GonkBufferItem::getFdCount() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFdCount();
+ }
+ if (mFence != 0) {
+ c += mFence->getFdCount();
+ }
+ return c;
+}
+
+status_t GonkBufferItem::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const {
+
+ // make sure we have enough space
+ if (count < GonkBufferItem::getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ // content flags are stored first
+ uint32_t& flags = *static_cast<uint32_t*>(buffer);
+
+ // advance the pointer
+ FlattenableUtils::advance(buffer, size, sizeof(uint32_t));
+
+ flags = 0;
+ if (mGraphicBuffer != 0) {
+ status_t err = mGraphicBuffer->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 1;
+ }
+ if (mFence != 0) {
+ status_t err = mFence->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 2;
+ }
+
+ // check we have enough space (in case flattening the fence/graphicbuffer lied to us)
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, mCrop);
+ FlattenableUtils::write(buffer, size, mTransform);
+ FlattenableUtils::write(buffer, size, mScalingMode);
+ FlattenableUtils::write(buffer, size, mTimestamp);
+ FlattenableUtils::write(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::write(buffer, size, mFrameNumber);
+ FlattenableUtils::write(buffer, size, mSlot);
+ FlattenableUtils::write(buffer, size, mIsDroppable);
+ FlattenableUtils::write(buffer, size, mAcquireCalled);
+ FlattenableUtils::write(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferItem::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+
+ if (size < sizeof(uint32_t))
+ return NO_MEMORY;
+
+ uint32_t flags = 0;
+ FlattenableUtils::read(buffer, size, flags);
+
+ if (flags & 1) {
+ mGraphicBuffer = new GraphicBuffer();
+ status_t err = mGraphicBuffer->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ if (flags & 2) {
+ mFence = new Fence();
+ status_t err = mFence->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ // check we have enough space
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, mCrop);
+ FlattenableUtils::read(buffer, size, mTransform);
+ FlattenableUtils::read(buffer, size, mScalingMode);
+ FlattenableUtils::read(buffer, size, mTimestamp);
+ FlattenableUtils::read(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::read(buffer, size, mFrameNumber);
+ FlattenableUtils::read(buffer, size, mSlot);
+ FlattenableUtils::read(buffer, size, mIsDroppable);
+ FlattenableUtils::read(buffer, size, mAcquireCalled);
+ FlattenableUtils::read(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+const char* GonkBufferItem::scalingModeName(uint32_t scalingMode) {
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP";
+ default: return "Unknown";
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.h
new file mode 100644
index 000000000..b2d6d3068
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERITEM_LL_H
+#define NATIVEWINDOW_GONKBUFFERITEM_LL_H
+
+#include "IGonkGraphicBufferConsumerLL.h"
+
+#include <ui/Rect.h>
+
+#include <utils/Flattenable.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class Fence;
+class GraphicBuffer;
+
+class GonkBufferItem : public Flattenable<GonkBufferItem> {
+ friend class Flattenable<GonkBufferItem>;
+ size_t getPodSize() const;
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ public:
+ // The default value of mBuf, used to indicate this doesn't correspond to a slot.
+ enum { INVALID_BUFFER_SLOT = -1 };
+ GonkBufferItem();
+ operator IGonkGraphicBufferConsumer::BufferItem() const;
+
+ static const char* scalingModeName(uint32_t scalingMode);
+
+ // mGraphicBuffer points to the buffer allocated for this slot, or is NULL
+ // if the buffer in this slot has been acquired in the past (see
+ // BufferSlot.mAcquireCalled).
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence that will signal when the buffer is idle.
+ sp<Fence> mFence;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ // refer to NATIVE_WINDOW_TRANSFORM_* in <window.h>
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ // refer to NATIVE_WINDOW_SCALING_* in <window.h>
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued. This value
+ // is guaranteed to be monotonically increasing for each newly
+ // acquired buffer.
+ int64_t mTimestamp;
+
+ // mIsAutoTimestamp indicates whether mTimestamp was generated
+ // automatically when the buffer was queued.
+ bool mIsAutoTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot.
+ uint64_t mFrameNumber;
+
+ // mSlot is the slot index of this buffer (default INVALID_BUFFER_SLOT).
+ int mSlot;
+
+ // mIsDroppable whether this buffer was queued with the
+ // property that it can be replaced by a new buffer for the purpose of
+ // making sure dequeueBuffer() won't block.
+ // i.e.: was the BufferQueue in "mDequeueBufferCannotBlock" when this buffer
+ // was queued.
+ bool mIsDroppable;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates this buffer must be transformed by the inverse transform of the screen
+ // it is displayed onto. This is applied after mTransform.
+ bool mTransformToDisplayInverse;
+};
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.cpp
new file mode 100644
index 000000000..1d7eb2702
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.cpp
@@ -0,0 +1,559 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#define LOG_TAG "GonkBufferQueueConsumer"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include "GonkBufferItem.h"
+#include "GonkBufferQueueConsumer.h"
+#include "GonkBufferQueueCore.h"
+#include <gui/IConsumerListener.h>
+#include <gui/IProducerListener.h>
+
+namespace android {
+
+GonkBufferQueueConsumer::GonkBufferQueueConsumer(const sp<GonkBufferQueueCore>& core) :
+ mCore(core),
+ mSlots(core->mSlots),
+ mConsumerName() {}
+
+GonkBufferQueueConsumer::~GonkBufferQueueConsumer() {}
+
+status_t GonkBufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
+ nsecs_t expectedPresent) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+
+ // Check that the consumer doesn't currently have the maximum number of
+ // buffers acquired. We allow the max buffer count to be exceeded by one
+ // buffer so that the consumer can successfully set up the newly acquired
+ // buffer before releasing the old one.
+ int numAcquiredBuffers = 0;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == GonkBufferSlot::ACQUIRED) {
+ ++numAcquiredBuffers;
+ }
+ }
+ if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
+ ALOGE("acquireBuffer: max acquired buffer count reached: %d (max %d)",
+ numAcquiredBuffers, mCore->mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+
+ // Check if the queue is empty.
+ // In asynchronous mode the list is guaranteed to be one buffer deep,
+ // while in synchronous mode we use the oldest buffer.
+ if (mCore->mQueue.empty()) {
+ return NO_BUFFER_AVAILABLE;
+ }
+
+ GonkBufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
+
+ // If expectedPresent is specified, we may not want to return a buffer yet.
+ // If it's specified and there's more than one buffer queued, we may want
+ // to drop a buffer.
+ if (expectedPresent != 0) {
+ const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second
+
+ // The 'expectedPresent' argument indicates when the buffer is expected
+ // to be presented on-screen. If the buffer's desired present time is
+ // earlier (less) than expectedPresent -- meaning it will be displayed
+ // on time or possibly late if we show it as soon as possible -- we
+ // acquire and return it. If we don't want to display it until after the
+ // expectedPresent time, we return PRESENT_LATER without acquiring it.
+ //
+ // To be safe, we don't defer acquisition if expectedPresent is more
+ // than one second in the future beyond the desired present time
+ // (i.e., we'd be holding the buffer for a long time).
+ //
+ // NOTE: Code assumes monotonic time values from the system clock
+ // are positive.
+
+ // Start by checking to see if we can drop frames. We skip this check if
+ // the timestamps are being auto-generated by Surface. If the app isn't
+ // generating timestamps explicitly, it probably doesn't want frames to
+ // be discarded based on them.
+ while (mCore->mQueue.size() > 1 && !mCore->mQueue[0].mIsAutoTimestamp) {
+ // If entry[1] is timely, drop entry[0] (and repeat). We apply an
+ // additional criterion here: we only drop the earlier buffer if our
+ // desiredPresent falls within +/- 1 second of the expected present.
+ // Otherwise, bogus desiredPresent times (e.g., 0 or a small
+ // relative timestamp), which normally mean "ignore the timestamp
+ // and acquire immediately", would cause us to drop frames.
+ //
+ // We may want to add an additional criterion: don't drop the
+ // earlier buffer if entry[1]'s fence hasn't signaled yet.
+ const BufferItem& bufferItem(mCore->mQueue[1]);
+ nsecs_t desiredPresent = bufferItem.mTimestamp;
+ if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
+ desiredPresent > expectedPresent) {
+ // This buffer is set to display in the near future, or
+ // desiredPresent is garbage. Either way we don't want to drop
+ // the previous buffer just to get this on the screen sooner.
+ ALOGV("acquireBuffer: nodrop desire=%" PRId64 " expect=%"
+ PRId64 " (%" PRId64 ") now=%" PRId64,
+ desiredPresent, expectedPresent,
+ desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ break;
+ }
+
+ ALOGV("acquireBuffer: drop desire=%" PRId64 " expect=%" PRId64
+ " size=%zu",
+ desiredPresent, expectedPresent, mCore->mQueue.size());
+ if (mCore->stillTracking(front)) {
+ // Front buffer is still in mSlots, so mark the slot as free
+ mSlots[front->mSlot].mBufferState = GonkBufferSlot::FREE;
+ }
+ mCore->mQueue.erase(front);
+ front = mCore->mQueue.begin();
+ }
+
+ // See if the front buffer is due
+ nsecs_t desiredPresent = front->mTimestamp;
+ if (desiredPresent > expectedPresent &&
+ desiredPresent < expectedPresent + MAX_REASONABLE_NSEC) {
+ ALOGV("acquireBuffer: defer desire=%" PRId64 " expect=%" PRId64
+ " (%" PRId64 ") now=%" PRId64,
+ desiredPresent, expectedPresent,
+ desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ return PRESENT_LATER;
+ }
+
+ ALOGV("acquireBuffer: accept desire=%" PRId64 " expect=%" PRId64 " "
+ "(%" PRId64 ") now=%" PRId64, desiredPresent, expectedPresent,
+ desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ }
+
+ int slot = front->mSlot;
+ //*outBuffer = *front;
+ outBuffer->mGraphicBuffer = mSlots[slot].mGraphicBuffer;
+ outBuffer->mFrameNumber = mSlots[slot].mFrameNumber;
+ outBuffer->mBuf = slot;
+ outBuffer->mFence = mSlots[slot].mFence;
+
+ ATRACE_BUFFER_INDEX(slot);
+
+ ALOGV("acquireBuffer: acquiring { slot=%d/%" PRIu64 " buffer=%p }",
+ slot, front->mFrameNumber, front->mGraphicBuffer->handle);
+ // If the front buffer is still being tracked, update its slot state
+ if (mCore->stillTracking(front)) {
+ mSlots[slot].mAcquireCalled = true;
+ mSlots[slot].mNeedsCleanupOnRelease = false;
+ mSlots[slot].mBufferState = GonkBufferSlot::ACQUIRED;
+ mSlots[slot].mFence = Fence::NO_FENCE;
+ }
+
+ // If the buffer has previously been acquired by the consumer, set
+ // mGraphicBuffer to NULL to avoid unnecessarily remapping this buffer
+ // on the consumer side
+ //if (outBuffer->mAcquireCalled) {
+ // outBuffer->mGraphicBuffer = NULL;
+ //}
+
+ mCore->mQueue.erase(front);
+
+ // We might have freed a slot while dropping old buffers, or the producer
+ // may be blocked waiting for the number of buffers in the queue to
+ // decrease.
+ mCore->mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::detachBuffer(int slot) {
+ ATRACE_CALL();
+ ATRACE_BUFFER_INDEX(slot);
+ ALOGV("detachBuffer(C): slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("detachBuffer(C): GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("detachBuffer(C): slot index %d out of range [0, %d)",
+ slot, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::ACQUIRED) {
+ ALOGE("detachBuffer(C): slot %d is not owned by the consumer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+
+ mCore->freeBufferLocked(slot);
+ mCore->mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::attachBuffer(int* outSlot,
+ const sp<android::GraphicBuffer>& buffer) {
+ ATRACE_CALL();
+
+ if (outSlot == NULL) {
+ ALOGE("attachBuffer(P): outSlot must not be NULL");
+ return BAD_VALUE;
+ } else if (buffer == NULL) {
+ ALOGE("attachBuffer(P): cannot attach NULL buffer");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ // Make sure we don't have too many acquired buffers and find a free slot
+ // to put the buffer into (the oldest if there are multiple).
+ int numAcquiredBuffers = 0;
+ int found = GonkBufferQueueCore::INVALID_BUFFER_SLOT;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == GonkBufferSlot::ACQUIRED) {
+ ++numAcquiredBuffers;
+ } else if (mSlots[s].mBufferState == GonkBufferSlot::FREE) {
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT ||
+ mSlots[s].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = s;
+ }
+ }
+ }
+
+ if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
+ ALOGE("attachBuffer(P): max acquired buffer count reached: %d "
+ "(max %d)", numAcquiredBuffers,
+ mCore->mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) {
+ ALOGE("attachBuffer(P): could not find free buffer slot");
+ return NO_MEMORY;
+ }
+
+ *outSlot = found;
+ ATRACE_BUFFER_INDEX(*outSlot);
+ ALOGV("attachBuffer(C): returning slot %d", *outSlot);
+
+ mSlots[*outSlot].mGraphicBuffer = buffer;
+ mSlots[*outSlot].mBufferState = GonkBufferSlot::ACQUIRED;
+ mSlots[*outSlot].mAttachedByConsumer = true;
+ mSlots[*outSlot].mNeedsCleanupOnRelease = false;
+ mSlots[*outSlot].mFence = Fence::NO_FENCE;
+ mSlots[*outSlot].mFrameNumber = 0;
+
+ // mAcquireCalled tells GonkBufferQueue that it doesn't need to send a valid
+ // GraphicBuffer pointer on the next acquireBuffer call, which decreases
+ // Binder traffic by not un/flattening the GraphicBuffer. However, it
+ // requires that the consumer maintain a cached copy of the slot <--> buffer
+ // mappings, which is why the consumer doesn't need the valid pointer on
+ // acquire.
+ //
+ // The StreamSplitter is one of the primary users of the attach/detach
+ // logic, and while it is running, all buffers it acquires are immediately
+ // detached, and all buffers it eventually releases are ones that were
+ // attached (as opposed to having been obtained from acquireBuffer), so it
+ // doesn't make sense to maintain the slot/buffer mappings, which would
+ // become invalid for every buffer during detach/attach. By setting this to
+ // false, the valid GraphicBuffer pointer will always be sent with acquire
+ // for attached buffers.
+ mSlots[*outSlot].mAcquireCalled = false;
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
+ const sp<Fence>& releaseFence) {
+ ATRACE_CALL();
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS ||
+ releaseFence == NULL) {
+ return BAD_VALUE;
+ }
+
+ sp<IProducerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ // If the frame number has changed because the buffer has been reallocated,
+ // we can ignore this releaseBuffer for the old buffer
+ //if (frameNumber != mSlots[slot].mFrameNumber) {
+ // return STALE_BUFFER_SLOT;
+ //}
+
+ // Make sure this buffer hasn't been queued while acquired by the consumer
+ GonkBufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
+ while (current != mCore->mQueue.end()) {
+ if (current->mSlot == slot) {
+ ALOGE("releaseBuffer: buffer slot %d pending release is "
+ "currently queued", slot);
+ return BAD_VALUE;
+ }
+ ++current;
+ }
+
+ if (mSlots[slot].mBufferState == GonkBufferSlot::ACQUIRED) {
+ mSlots[slot].mFence = releaseFence;
+ mSlots[slot].mBufferState = GonkBufferSlot::FREE;
+ listener = mCore->mConnectedProducerListener;
+ ALOGV("releaseBuffer: releasing slot %d", slot);
+ } else if (mSlots[slot].mNeedsCleanupOnRelease) {
+ ALOGV("releaseBuffer: releasing a stale buffer slot %d "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ mSlots[slot].mNeedsCleanupOnRelease = false;
+ return STALE_BUFFER_SLOT;
+ } else {
+ ALOGV("releaseBuffer: attempted to release buffer slot %d "
+ "but its state was %d", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+
+ mCore->mDequeueCondition.broadcast();
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onBufferReleased();
+ }
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::connect(
+ const sp<IConsumerListener>& consumerListener, bool controlledByApp) {
+ ATRACE_CALL();
+
+ if (consumerListener == NULL) {
+ ALOGE("connect(C): consumerListener may not be NULL");
+ return BAD_VALUE;
+ }
+
+ ALOGV("connect(C): controlledByApp=%s",
+ controlledByApp ? "true" : "false");
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("connect(C): GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ mCore->mConsumerListener = consumerListener;
+ mCore->mConsumerControlledByApp = controlledByApp;
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::disconnect() {
+ ATRACE_CALL();
+
+ ALOGV("disconnect(C)");
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mConsumerListener == NULL) {
+ ALOGE("disconnect(C): no consumer is connected");
+ return BAD_VALUE;
+ }
+
+ mCore->mIsAbandoned = true;
+ mCore->mConsumerListener = NULL;
+ mCore->mQueue.clear();
+ mCore->freeAllBuffersLocked();
+ mCore->mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::getReleasedBuffers(uint64_t *outSlotMask) {
+ ATRACE_CALL();
+
+ if (outSlotMask == NULL) {
+ ALOGE("getReleasedBuffers: outSlotMask may not be NULL");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("getReleasedBuffers: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ uint64_t mask = 0;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (!mSlots[s].mAcquireCalled) {
+ mask |= (1ULL << s);
+ }
+ }
+
+ // Remove from the mask queued buffers for which acquire has been called,
+ // since the consumer will not receive their buffer addresses and so must
+ // retain their cached information
+ GonkBufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
+ while (current != mCore->mQueue.end()) {
+ if (current->mAcquireCalled) {
+ mask &= ~(1ULL << current->mSlot);
+ }
+ ++current;
+ }
+
+ ALOGV("getReleasedBuffers: returning mask %#" PRIx64, mask);
+ *outSlotMask = mask;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setDefaultBufferSize(uint32_t width,
+ uint32_t height) {
+ ATRACE_CALL();
+
+ if (width == 0 || height == 0) {
+ ALOGV("setDefaultBufferSize: dimensions cannot be 0 (width=%u "
+ "height=%u)", width, height);
+ return BAD_VALUE;
+ }
+
+ ALOGV("setDefaultBufferSize: width=%u height=%u", width, height);
+
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mDefaultWidth = width;
+ mCore->mDefaultHeight = height;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setDefaultMaxBufferCount(int bufferCount) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+ return mCore->setDefaultMaxBufferCountLocked(bufferCount);
+}
+
+status_t GonkBufferQueueConsumer::disableAsyncBuffer() {
+ ATRACE_CALL();
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mConsumerListener != NULL) {
+ ALOGE("disableAsyncBuffer: consumer already connected");
+ return INVALID_OPERATION;
+ }
+
+ ALOGV("disableAsyncBuffer");
+ mCore->mUseAsyncBuffer = false;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setMaxAcquiredBufferCount(
+ int maxAcquiredBuffers) {
+ ATRACE_CALL();
+
+ if (maxAcquiredBuffers < 1 ||
+ maxAcquiredBuffers > GonkBufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) {
+ ALOGE("setMaxAcquiredBufferCount: invalid count %d",
+ maxAcquiredBuffers);
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mConnectedApi != GonkBufferQueueCore::NO_CONNECTED_API) {
+ ALOGE("setMaxAcquiredBufferCount: producer is already connected");
+ return INVALID_OPERATION;
+ }
+
+ ALOGV("setMaxAcquiredBufferCount: %d", maxAcquiredBuffers);
+ mCore->mMaxAcquiredBufferCount = maxAcquiredBuffers;
+ return NO_ERROR;
+}
+
+void GonkBufferQueueConsumer::setConsumerName(const String8& name) {
+ ATRACE_CALL();
+ ALOGV("setConsumerName: '%s'", name.string());
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mConsumerName = name;
+ mConsumerName = name;
+}
+
+status_t GonkBufferQueueConsumer::setDefaultBufferFormat(uint32_t defaultFormat) {
+ ATRACE_CALL();
+ ALOGV("setDefaultBufferFormat: %u", defaultFormat);
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mDefaultBufferFormat = defaultFormat;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setConsumerUsageBits(uint32_t usage) {
+ ATRACE_CALL();
+ ALOGV("setConsumerUsageBits: %#x", usage);
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mConsumerUsageBits = usage;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setTransformHint(uint32_t hint) {
+ ATRACE_CALL();
+ ALOGV("setTransformHint: %#x", hint);
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mTransformHint = hint;
+ return NO_ERROR;
+}
+
+sp<NativeHandle> GonkBufferQueueConsumer::getSidebandStream() const {
+ return mCore->mSidebandStream;
+}
+
+void GonkBufferQueueConsumer::dumpToString(String8& result, const char* prefix) const {
+ mCore->dump(result, prefix);
+}
+
+already_AddRefed<GonkBufferSlot::TextureClient>
+GonkBufferQueueConsumer::getTextureClientFromBuffer(ANativeWindowBuffer* buffer)
+{
+ Mutex::Autolock _l(mCore->mMutex);
+ if (buffer == NULL) {
+ ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return nullptr;
+ }
+
+ for (int i = 0; i < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) {
+ RefPtr<TextureClient> client(mSlots[i].mTextureClient);
+ return client.forget();
+ }
+ }
+ ALOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+ return nullptr;
+}
+
+int
+GonkBufferQueueConsumer::getSlotFromTextureClientLocked(GonkBufferSlot::TextureClient* client) const
+{
+ if (client == NULL) {
+ ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return BAD_VALUE;
+ }
+
+ for (int i = 0; i < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mTextureClient == client) {
+ return i;
+ }
+ }
+ ALOGE("getSlotFromBufferLocked: unknown TextureClient: %p", client);
+ return BAD_VALUE;
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.h
new file mode 100644
index 000000000..a97cfab42
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUECONSUMER_LL_H
+#define NATIVEWINDOW_GONKBUFFERQUEUECONSUMER_LL_H
+
+#include "GonkBufferQueueDefs.h"
+#include "IGonkGraphicBufferConsumerLL.h"
+
+namespace android {
+
+class GonkBufferQueueCore;
+
+class GonkBufferQueueConsumer : public BnGonkGraphicBufferConsumer {
+
+public:
+ GonkBufferQueueConsumer(const sp<GonkBufferQueueCore>& core);
+ virtual ~GonkBufferQueueConsumer();
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the GonkBufferQueue. If no buffer is pending then it returns
+ // NO_BUFFER_AVAILABLE. If a buffer is successfully acquired, the
+ // information about the buffer is returned in BufferItem. If the buffer
+ // returned had previously been acquired then the BufferItem::mGraphicBuffer
+ // field of buffer is set to NULL and it is assumed that the consumer still
+ // holds a reference to the buffer.
+ //
+ // If expectedPresent is nonzero, it indicates the time when the buffer
+ // will be displayed on screen. If the buffer's timestamp is farther in the
+ // future, the buffer won't be acquired, and PRESENT_LATER will be
+ // returned. The presentation time is in nanoseconds, and the time base
+ // is CLOCK_MONOTONIC.
+ virtual status_t acquireBuffer(BufferItem* outBuffer,
+ nsecs_t expectedPresent);
+
+ // See IGonkGraphicBufferConsumer::detachBuffer
+ virtual status_t detachBuffer(int slot);
+
+ // See IGonkGraphicBufferConsumer::attachBuffer
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer);
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // GonkBufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use. frameNumber is used to indentify the exact buffer returned.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ virtual status_t releaseBuffer(int slot, uint64_t frameNumber,
+ const sp<Fence>& releaseFence);
+
+ // connect connects a consumer to the GonkBufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // GonkBufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the GonkBufferQueue by the producer to fail.
+ // controlledByApp indicates whether the consumer is controlled by
+ // the application.
+ //
+ // consumerListener may not be NULL.
+ virtual status_t connect(const sp<IConsumerListener>& consumerListener,
+ bool controlledByApp);
+
+ // disconnect disconnects a consumer from the GonkBufferQueue. All
+ // buffers will be freed and the GonkBufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the GonkBufferQueue by the producer to
+ // fail.
+ virtual status_t disconnect();
+
+ // getReleasedBuffers sets the value pointed to by outSlotMask to a bit mask
+ // indicating which buffer slots have been released by the GonkBufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ virtual status_t getReleasedBuffers(uint64_t* outSlotMask);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ virtual status_t setDefaultBufferSize(uint32_t width, uint32_t height);
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ virtual status_t setDefaultMaxBufferCount(int bufferCount);
+
+ // disableAsyncBuffer disables the extra buffer used in async mode
+ // (when both producer and consumer have set their "isControlledByApp"
+ // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
+ //
+ // This can only be called before connect().
+ virtual status_t disableAsyncBuffer();
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the GonkBufferQueue.
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
+
+ // setConsumerName sets the name used in logging
+ virtual void setConsumerName(const String8& name);
+
+ // setDefaultBufferFormat allows the GonkBufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ virtual status_t setConsumerUsageBits(uint32_t usage);
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ virtual status_t setTransformHint(uint32_t hint);
+
+ // Retrieve the sideband buffer stream, if any.
+ virtual sp<NativeHandle> getSidebandStream() const;
+
+ // dump our state in a String
+ virtual void dumpToString(String8& result, const char* prefix) const;
+
+ // Added by mozilla
+ virtual already_AddRefed<GonkBufferSlot::TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ virtual int getSlotFromTextureClientLocked(GonkBufferSlot::TextureClient* client) const;
+
+ // Functions required for backwards compatibility.
+ // These will be modified/renamed in IGonkGraphicBufferConsumer and will be
+ // removed from this class at that time. See b/13306289.
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer,
+ bool controlledByApp) {
+ return connect(consumer, controlledByApp);
+ }
+
+ virtual status_t consumerDisconnect() { return disconnect(); }
+
+ // End functions required for backwards compatibility
+
+private:
+ sp<GonkBufferQueueCore> mCore;
+
+ // This references mCore->mSlots. Lock mCore->mMutex while accessing.
+ GonkBufferQueueDefs::SlotsType& mSlots;
+
+ // This is a cached copy of the name stored in the GonkBufferQueueCore.
+ // It's updated during setConsumerName.
+ String8 mConsumerName;
+
+}; // class GonkBufferQueueConsumer
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.cpp
new file mode 100644
index 000000000..9e8e337f6
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GonkBufferQueueCore"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include <inttypes.h>
+
+#include "GonkBufferItem.h"
+#include "GonkBufferQueueCore.h"
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/IProducerListener.h>
+#include <gui/ISurfaceComposer.h>
+#include <private/gui/ComposerService.h>
+
+#include <cutils/compiler.h>
+#include "mozilla/layers/GrallocTextureClient.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+
+template <typename T>
+static inline T max(T a, T b) { return a > b ? a : b; }
+
+namespace android {
+
+static String8 getUniqueName() {
+ static volatile int32_t counter = 0;
+ return String8::format("unnamed-%d-%d", getpid(),
+ android_atomic_inc(&counter));
+}
+
+GonkBufferQueueCore::GonkBufferQueueCore(const sp<IGraphicBufferAlloc>& allocator) :
+ mAllocator(allocator),
+ mMutex(),
+ mIsAbandoned(false),
+ mConsumerControlledByApp(false),
+ mConsumerName(getUniqueName()),
+ mConsumerListener(),
+ mConsumerUsageBits(0),
+ mConnectedApi(NO_CONNECTED_API),
+ mConnectedProducerListener(),
+ mSlots(),
+ mQueue(),
+ mOverrideMaxBufferCount(0),
+ mDequeueCondition(),
+ mUseAsyncBuffer(true),
+ mDequeueBufferCannotBlock(false),
+ mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
+ mDefaultWidth(1),
+ mDefaultHeight(1),
+ mDefaultMaxBufferCount(2),
+ mMaxAcquiredBufferCount(1),
+ mBufferHasBeenQueued(false),
+ mFrameCounter(0),
+ mTransformHint(0),
+ mIsAllocating(false),
+ mIsAllocatingCondition()
+{
+ ALOGV("GonkBufferQueueCore");
+}
+
+GonkBufferQueueCore::~GonkBufferQueueCore() {}
+
+void GonkBufferQueueCore::dump(String8& result, const char* prefix) const {
+ Mutex::Autolock lock(mMutex);
+
+ String8 fifo;
+ Fifo::const_iterator current(mQueue.begin());
+ while (current != mQueue.end()) {
+ fifo.appendFormat("%02d:%p crop=[%d,%d,%d,%d], "
+ "xform=0x%02x, time=%#" PRIx64 ", scale=%s\n",
+ current->mSlot, current->mGraphicBuffer.get(),
+ current->mCrop.left, current->mCrop.top, current->mCrop.right,
+ current->mCrop.bottom, current->mTransform, current->mTimestamp,
+ GonkBufferItem::scalingModeName(current->mScalingMode));
+ ++current;
+ }
+
+ result.appendFormat("%s-GonkBufferQueue mMaxAcquiredBufferCount=%d, "
+ "mDequeueBufferCannotBlock=%d, default-size=[%dx%d], "
+ "default-format=%d, transform-hint=%02x, FIFO(%zu)={%s}\n",
+ prefix, mMaxAcquiredBufferCount, mDequeueBufferCannotBlock,
+ mDefaultWidth, mDefaultHeight, mDefaultBufferFormat, mTransformHint,
+ mQueue.size(), fifo.string());
+
+ // Trim the free buffers so as to not spam the dump
+ int maxBufferCount = 0;
+ for (int s = GonkBufferQueueDefs::NUM_BUFFER_SLOTS - 1; s >= 0; --s) {
+ const GonkBufferSlot& slot(mSlots[s]);
+ if (slot.mBufferState != GonkBufferSlot::FREE ||
+ slot.mGraphicBuffer != NULL) {
+ maxBufferCount = s + 1;
+ break;
+ }
+ }
+
+ for (int s = 0; s < maxBufferCount; ++s) {
+ const GonkBufferSlot& slot(mSlots[s]);
+ const sp<GraphicBuffer>& buffer(slot.mGraphicBuffer);
+ result.appendFormat("%s%s[%02d:%p] state=%-8s", prefix,
+ (slot.mBufferState == GonkBufferSlot::ACQUIRED) ? ">" : " ",
+ s, buffer.get(),
+ GonkBufferSlot::bufferStateName(slot.mBufferState));
+
+ if (buffer != NULL) {
+ result.appendFormat(", %p [%4ux%4u:%4u,%3X]", buffer->handle,
+ buffer->width, buffer->height, buffer->stride,
+ buffer->format);
+ }
+
+ result.append("\n");
+ }
+}
+
+int GonkBufferQueueCore::getMinUndequeuedBufferCountLocked(bool async) const {
+ // If dequeueBuffer is allowed to error out, we don't have to add an
+ // extra buffer.
+ if (!mUseAsyncBuffer) {
+ return mMaxAcquiredBufferCount;
+ }
+
+ if (mDequeueBufferCannotBlock || async) {
+ return mMaxAcquiredBufferCount + 1;
+ }
+
+ return mMaxAcquiredBufferCount;
+}
+
+int GonkBufferQueueCore::getMinMaxBufferCountLocked(bool async) const {
+ return getMinUndequeuedBufferCountLocked(async) + 1;
+}
+
+int GonkBufferQueueCore::getMaxBufferCountLocked(bool async) const {
+ int minMaxBufferCount = getMinMaxBufferCountLocked(async);
+
+ int maxBufferCount = max(mDefaultMaxBufferCount, minMaxBufferCount);
+ if (mOverrideMaxBufferCount != 0) {
+ assert(mOverrideMaxBufferCount >= minMaxBufferCount);
+ maxBufferCount = mOverrideMaxBufferCount;
+ }
+
+ // Any buffers that are dequeued by the producer or sitting in the queue
+ // waiting to be consumed need to have their slots preserved. Such buffers
+ // will temporarily keep the max buffer count up until the slots no longer
+ // need to be preserved.
+ for (int s = maxBufferCount; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ GonkBufferSlot::BufferState state = mSlots[s].mBufferState;
+ if (state == GonkBufferSlot::QUEUED || state == GonkBufferSlot::DEQUEUED) {
+ maxBufferCount = s + 1;
+ }
+ }
+
+ return maxBufferCount;
+}
+
+status_t GonkBufferQueueCore::setDefaultMaxBufferCountLocked(int count) {
+ const int minBufferCount = 2;
+ if (count < minBufferCount || count > GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGV("setDefaultMaxBufferCount: invalid count %d, should be in "
+ "[%d, %d]",
+ count, minBufferCount, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ ALOGV("setDefaultMaxBufferCount: setting count to %d", count);
+ mDefaultMaxBufferCount = count;
+ mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+void GonkBufferQueueCore::freeBufferLocked(int slot) {
+ ALOGV("freeBufferLocked: slot %d", slot);
+
+ if (mSlots[slot].mTextureClient) {
+ mSlots[slot].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[slot].mTextureClient);
+ mSlots[slot].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ mSlots[slot].mGraphicBuffer.clear();
+ if (mSlots[slot].mBufferState == GonkBufferSlot::ACQUIRED) {
+ mSlots[slot].mNeedsCleanupOnRelease = true;
+ }
+ mSlots[slot].mBufferState = GonkBufferSlot::FREE;
+ mSlots[slot].mFrameNumber = UINT32_MAX;
+ mSlots[slot].mAcquireCalled = false;
+
+ // Destroy fence as GonkBufferQueue now takes ownership
+ mSlots[slot].mFence = Fence::NO_FENCE;
+}
+
+void GonkBufferQueueCore::freeAllBuffersLocked() {
+ ALOGW_IF(!mQueue.isEmpty(),
+ "freeAllBuffersLocked called but mQueue is not empty");
+ mQueue.clear();
+ mBufferHasBeenQueued = false;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ freeBufferLocked(s);
+ }
+}
+
+bool GonkBufferQueueCore::stillTracking(const GonkBufferItem* item) const {
+ const GonkBufferSlot& slot = mSlots[item->mSlot];
+
+ ALOGV("stillTracking: item { slot=%d/%" PRIu64 " buffer=%p } "
+ "slot { slot=%d/%" PRIu64 " buffer=%p }",
+ item->mSlot, item->mFrameNumber,
+ (item->mGraphicBuffer.get() ? item->mGraphicBuffer->handle : 0),
+ item->mSlot, slot.mFrameNumber,
+ (slot.mGraphicBuffer.get() ? slot.mGraphicBuffer->handle : 0));
+
+ // Compare item with its original buffer slot. We can check the slot as
+ // the buffer would not be moved to a different slot by the producer.
+ return (slot.mGraphicBuffer != NULL) &&
+ (item->mGraphicBuffer->handle == slot.mGraphicBuffer->handle);
+}
+
+void GonkBufferQueueCore::waitWhileAllocatingLocked() const {
+ ATRACE_CALL();
+ while (mIsAllocating) {
+ mIsAllocatingCondition.wait(mMutex);
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.h
new file mode 100644
index 000000000..936e11686
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUECORE_LL_H
+#define NATIVEWINDOW_GONKBUFFERQUEUECORE_LL_H
+
+#include "GonkBufferQueueDefs.h"
+#include "GonkBufferSlot.h"
+
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+#include <utils/NativeHandle.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+#include <utils/Trace.h>
+#include <utils/Vector.h>
+
+#include "mozilla/layers/TextureClient.h"
+
+#define ATRACE_BUFFER_INDEX(index)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace android {
+
+class GonkBufferItem;
+class IConsumerListener;
+class IGraphicBufferAlloc;
+class IProducerListener;
+
+class GonkBufferQueueCore : public virtual RefBase {
+
+ friend class GonkBufferQueueProducer;
+ friend class GonkBufferQueueConsumer;
+
+public:
+ // Used as a placeholder slot number when the value isn't pointing to an
+ // existing buffer.
+ enum { INVALID_BUFFER_SLOT = -1 }; // TODO: Extract from IGBC::BufferItem
+
+ // We reserve two slots in order to guarantee that the producer and
+ // consumer can run asynchronously.
+ enum { MAX_MAX_ACQUIRED_BUFFERS = GonkBufferQueueDefs::NUM_BUFFER_SLOTS - 2 };
+
+ // The default API number used to indicate that no producer is connected
+ enum { NO_CONNECTED_API = 0 };
+
+ typedef Vector<GonkBufferItem> Fifo;
+ typedef mozilla::layers::TextureClient TextureClient;
+
+ // GonkBufferQueueCore manages a pool of gralloc memory slots to be used by
+ // producers and consumers. allocator is used to allocate all the needed
+ // gralloc buffers.
+ GonkBufferQueueCore(const sp<IGraphicBufferAlloc>& allocator = NULL);
+ virtual ~GonkBufferQueueCore();
+
+private:
+ // Dump our state in a string
+ void dump(String8& result, const char* prefix) const;
+
+ int getSlotFromTextureClientLocked(TextureClient* client) const;
+
+ // getMinUndequeuedBufferCountLocked returns the minimum number of buffers
+ // that must remain in a state other than DEQUEUED. The async parameter
+ // tells whether we're in asynchronous mode.
+ int getMinUndequeuedBufferCountLocked(bool async) const;
+
+ // getMinMaxBufferCountLocked returns the minimum number of buffers allowed
+ // given the current GonkBufferQueue state. The async parameter tells whether
+ // we're in asynchonous mode.
+ int getMinMaxBufferCountLocked(bool async) const;
+
+ // getMaxBufferCountLocked returns the maximum number of buffers that can be
+ // allocated at once. This value depends on the following member variables:
+ //
+ // mDequeueBufferCannotBlock
+ // mMaxAcquiredBufferCount
+ // mDefaultMaxBufferCount
+ // mOverrideMaxBufferCount
+ // async parameter
+ //
+ // Any time one of these member variables is changed while a producer is
+ // connected, mDequeueCondition must be broadcast.
+ int getMaxBufferCountLocked(bool async) const;
+
+ // setDefaultMaxBufferCountLocked sets the maximum number of buffer slots
+ // that will be used if the producer does not override the buffer slot
+ // count. The count must be between 2 and NUM_BUFFER_SLOTS, inclusive. The
+ // initial default is 2.
+ status_t setDefaultMaxBufferCountLocked(int count);
+
+ // freeBufferLocked frees the GraphicBuffer and sync resources for the
+ // given slot.
+ void freeBufferLocked(int slot);
+
+ // freeAllBuffersLocked frees the GraphicBuffer and sync resources for
+ // all slots.
+ void freeAllBuffersLocked();
+
+ // stillTracking returns true iff the buffer item is still being tracked
+ // in one of the slots.
+ bool stillTracking(const GonkBufferItem* item) const;
+
+ // waitWhileAllocatingLocked blocks until mIsAllocating is false.
+ void waitWhileAllocatingLocked() const;
+
+ // mAllocator is the connection to SurfaceFlinger that is used to allocate
+ // new GraphicBuffer objects.
+ sp<IGraphicBufferAlloc> mAllocator;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkBufferQueueCore objects. It must be locked whenever any
+ // member variable is accessed.
+ mutable Mutex mMutex;
+
+ // mIsAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume image buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the
+ // consumerDisconnect method. A GonkBufferQueue that is abandoned will return
+ // the NO_INIT error from all IGraphicBufferProducer methods capable of
+ // returning an error.
+ bool mIsAbandoned;
+
+ // mConsumerControlledByApp indicates whether the connected consumer is
+ // controlled by the application.
+ bool mConsumerControlledByApp;
+
+ // mConsumerName is a string used to identify the GonkBufferQueue in log
+ // messages. It is set by the IGraphicBufferConsumer::setConsumerName
+ // method.
+ String8 mConsumerName;
+
+ // mConsumerListener is used to notify the connected consumer of
+ // asynchronous events that it may wish to react to. It is initially
+ // set to NULL and is written by consumerConnect and consumerDisconnect.
+ sp<IConsumerListener> mConsumerListener;
+
+ // mConsumerUsageBits contains flags that the consumer wants for
+ // GraphicBuffers.
+ uint32_t mConsumerUsageBits;
+
+ // mConnectedApi indicates the producer API that is currently connected
+ // to this GonkBufferQueue. It defaults to NO_CONNECTED_API, and gets updated
+ // by the connect and disconnect methods.
+ int mConnectedApi;
+
+ // mConnectedProducerToken is used to set a binder death notification on
+ // the producer.
+ sp<IProducerListener> mConnectedProducerListener;
+
+ // mSlots is an array of buffer slots that must be mirrored on the producer
+ // side. This allows buffer ownership to be transferred between the producer
+ // and consumer without sending a GraphicBuffer over Binder. The entire
+ // array is initialized to NULL at construction time, and buffers are
+ // allocated for a slot when requestBuffer is called with that slot's index.
+ GonkBufferQueueDefs::SlotsType mSlots;
+
+ // mQueue is a FIFO of queued buffers used in synchronous mode.
+ Fifo mQueue;
+
+ // mOverrideMaxBufferCount is the limit on the number of buffers that will
+ // be allocated at one time. This value is set by the producer by calling
+ // setBufferCount. The default is 0, which means that the producer doesn't
+ // care about the number of buffers in the pool. In that case,
+ // mDefaultMaxBufferCount is used as the limit.
+ int mOverrideMaxBufferCount;
+
+ // mDequeueCondition is a condition variable used for dequeueBuffer in
+ // synchronous mode.
+ mutable Condition mDequeueCondition;
+
+ // mUseAsyncBuffer indicates whether an extra buffer is used in async mode
+ // to prevent dequeueBuffer from blocking.
+ bool mUseAsyncBuffer;
+
+ // mDequeueBufferCannotBlock indicates whether dequeueBuffer is allowed to
+ // block. This flag is set during connect when both the producer and
+ // consumer are controlled by the application.
+ bool mDequeueBufferCannotBlock;
+
+ // mDefaultBufferFormat can be set so it will override the buffer format
+ // when it isn't specified in dequeueBuffer.
+ uint32_t mDefaultBufferFormat;
+
+ // mDefaultWidth holds the default width of allocated buffers. It is used
+ // in dequeueBuffer if a width and height of 0 are specified.
+ int mDefaultWidth;
+
+ // mDefaultHeight holds the default height of allocated buffers. It is used
+ // in dequeueBuffer if a width and height of 0 are specified.
+ int mDefaultHeight;
+
+ // mDefaultMaxBufferCount is the default limit on the number of buffers that
+ // will be allocated at one time. This default limit is set by the consumer.
+ // The limit (as opposed to the default limit) may be overriden by the
+ // producer.
+ int mDefaultMaxBufferCount;
+
+ // mMaxAcquiredBufferCount is the number of buffers that the consumer may
+ // acquire at one time. It defaults to 1, and can be changed by the consumer
+ // via setMaxAcquiredBufferCount, but this may only be done while no
+ // producer is connected to the GonkBufferQueue. This value is used to derive
+ // the value returned for the MIN_UNDEQUEUED_BUFFERS query to the producer.
+ int mMaxAcquiredBufferCount;
+
+ // mBufferHasBeenQueued is true once a buffer has been queued. It is reset
+ // when something causes all buffers to be freed (e.g., changing the buffer
+ // count).
+ bool mBufferHasBeenQueued;
+
+ // mFrameCounter is the free running counter, incremented on every
+ // successful queueBuffer call and buffer allocation.
+ uint64_t mFrameCounter;
+
+ // mTransformHint is used to optimize for screen rotations.
+ uint32_t mTransformHint;
+
+ // mSidebandStream is a handle to the sideband buffer stream, if any
+ sp<NativeHandle> mSidebandStream;
+
+ // mIsAllocating indicates whether a producer is currently trying to allocate buffers (which
+ // releases mMutex while doing the allocation proper). Producers should not modify any of the
+ // FREE slots while this is true. mIsAllocatingCondition is signaled when this value changes to
+ // false.
+ bool mIsAllocating;
+
+ // mIsAllocatingCondition is a condition variable used by producers to wait until mIsAllocating
+ // becomes false.
+ mutable Condition mIsAllocatingCondition;
+}; // class GonkBufferQueueCore
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueDefs.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueDefs.h
new file mode 100644
index 000000000..60085706f
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueDefs.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_BUFFERQUEUECOREDEFS_H
+#define NATIVEWINDOW_BUFFERQUEUECOREDEFS_H
+
+#include "GonkBufferSlot.h"
+
+namespace android {
+ class GonkBufferQueueCore;
+
+ namespace GonkBufferQueueDefs {
+ // GonkBufferQueue will keep track of at most this value of buffers.
+ // Attempts at runtime to increase the number of buffers past this
+ // will fail.
+ enum { NUM_BUFFER_SLOTS = 64 };
+
+ typedef GonkBufferSlot SlotsType[NUM_BUFFER_SLOTS];
+ } // namespace GonkBufferQueueDefs
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.cpp
new file mode 100644
index 000000000..649d06bee
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GonkBufferQueue"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#define LOG_NDEBUG 0
+
+#include "GonkBufferQueue.h"
+#include "GonkBufferQueueConsumer.h"
+#include "GonkBufferQueueCore.h"
+#include "GonkBufferQueueProducer.h"
+
+namespace android {
+
+GonkBufferQueue::ProxyConsumerListener::ProxyConsumerListener(
+ const wp<ConsumerListener>& consumerListener):
+ mConsumerListener(consumerListener) {}
+
+GonkBufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {}
+
+#if ANDROID_VERSION == 21
+void GonkBufferQueue::ProxyConsumerListener::onFrameAvailable() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameAvailable();
+ }
+}
+#else
+void GonkBufferQueue::ProxyConsumerListener::onFrameAvailable(const ::android::BufferItem& item) {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameAvailable(item);
+ }
+}
+
+void GonkBufferQueue::ProxyConsumerListener::onFrameReplaced(const ::android::BufferItem& item) {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameReplaced(item);
+ }
+}
+#endif
+
+void GonkBufferQueue::ProxyConsumerListener::onBuffersReleased() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+}
+
+void GonkBufferQueue::ProxyConsumerListener::onSidebandStreamChanged() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onSidebandStreamChanged();
+ }
+}
+
+void GonkBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
+ sp<IGonkGraphicBufferConsumer>* outConsumer,
+ const sp<IGraphicBufferAlloc>& allocator) {
+ LOG_ALWAYS_FATAL_IF(outProducer == NULL,
+ "GonkBufferQueue: outProducer must not be NULL");
+ LOG_ALWAYS_FATAL_IF(outConsumer == NULL,
+ "GonkBufferQueue: outConsumer must not be NULL");
+
+ sp<GonkBufferQueueCore> core(new GonkBufferQueueCore(allocator));
+ LOG_ALWAYS_FATAL_IF(core == NULL,
+ "GonkBufferQueue: failed to create GonkBufferQueueCore");
+
+ sp<IGraphicBufferProducer> producer(new GonkBufferQueueProducer(core));
+ LOG_ALWAYS_FATAL_IF(producer == NULL,
+ "GonkBufferQueue: failed to create GonkBufferQueueProducer");
+
+ sp<IGonkGraphicBufferConsumer> consumer(new GonkBufferQueueConsumer(core));
+ LOG_ALWAYS_FATAL_IF(consumer == NULL,
+ "GonkBufferQueue: failed to create GonkBufferQueueConsumer");
+
+ *outProducer = producer;
+ *outConsumer = consumer;
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.h
new file mode 100644
index 000000000..b1b4e06b5
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUE_LL_H
+#define NATIVEWINDOW_GONKBUFFERQUEUE_LL_H
+
+#include "GonkBufferQueueDefs.h"
+#include "IGonkGraphicBufferConsumerLL.h"
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/IConsumerListener.h>
+
+// These are only required to keep other parts of the framework with incomplete
+// dependencies building successfully
+#include <gui/IGraphicBufferAlloc.h>
+
+namespace android {
+
+class GonkBufferQueue {
+public:
+ // GonkBufferQueue will keep track of at most this value of buffers.
+ // Attempts at runtime to increase the number of buffers past this will fail.
+ enum { NUM_BUFFER_SLOTS = GonkBufferQueueDefs::NUM_BUFFER_SLOTS };
+ // Used as a placeholder slot# when the value isn't pointing to an existing buffer.
+ enum { INVALID_BUFFER_SLOT = IGonkGraphicBufferConsumer::BufferItem::INVALID_BUFFER_SLOT };
+ // Alias to <IGonkGraphicBufferConsumer.h> -- please scope from there in future code!
+ enum {
+ NO_BUFFER_AVAILABLE = IGonkGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
+ PRESENT_LATER = IGonkGraphicBufferConsumer::PRESENT_LATER,
+ };
+
+ // When in async mode we reserve two slots in order to guarantee that the
+ // producer and consumer can run asynchronously.
+ enum { MAX_MAX_ACQUIRED_BUFFERS = NUM_BUFFER_SLOTS - 2 };
+
+ // for backward source compatibility
+ typedef ::android::ConsumerListener ConsumerListener;
+ typedef IGonkGraphicBufferConsumer::BufferItem BufferItem;
+
+ // ProxyConsumerListener is a ConsumerListener implementation that keeps a weak
+ // reference to the actual consumer object. It forwards all calls to that
+ // consumer object so long as it exists.
+ //
+ // This class exists to avoid having a circular reference between the
+ // GonkBufferQueue object and the consumer object. The reason this can't be a weak
+ // reference in the GonkBufferQueue class is because we're planning to expose the
+ // consumer side of a GonkBufferQueue as a binder interface, which doesn't support
+ // weak references.
+ class ProxyConsumerListener : public BnConsumerListener {
+ public:
+ ProxyConsumerListener(const wp<ConsumerListener>& consumerListener);
+ virtual ~ProxyConsumerListener();
+#if ANDROID_VERSION == 21
+ virtual void onFrameAvailable();
+#else
+ virtual void onFrameAvailable(const ::android::BufferItem& item);
+ virtual void onFrameReplaced(const ::android::BufferItem& item);
+#endif
+ virtual void onBuffersReleased();
+ virtual void onSidebandStreamChanged();
+ private:
+ // mConsumerListener is a weak reference to the IConsumerListener. This is
+ // the raison d'etre of ProxyConsumerListener.
+ wp<ConsumerListener> mConsumerListener;
+ };
+
+ // GonkBufferQueue manages a pool of gralloc memory slots to be used by
+ // producers and consumers. allocator is used to allocate all the
+ // needed gralloc buffers.
+ static void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
+ sp<IGonkGraphicBufferConsumer>* outConsumer,
+ const sp<IGraphicBufferAlloc>& allocator = NULL);
+
+private:
+ GonkBufferQueue(); // Create through createBufferQueue
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_GONKBUFFERQUEUE_LL_H
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.cpp
new file mode 100644
index 000000000..d3436756f
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.cpp
@@ -0,0 +1,886 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#define LOG_TAG "GonkBufferQueueProducer"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include "GonkBufferItem.h"
+#include "GonkBufferQueueCore.h"
+#include "GonkBufferQueueProducer.h"
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/IProducerListener.h>
+
+#include <cutils/compiler.h>
+#include <utils/Log.h>
+#include <utils/Trace.h>
+
+#include "mozilla/layers/GrallocTextureClient.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/TextureClient.h"
+
+namespace android {
+
+GonkBufferQueueProducer::GonkBufferQueueProducer(const sp<GonkBufferQueueCore>& core) :
+ mCore(core),
+ mSlots(core->mSlots),
+ mConsumerName(),
+ mSynchronousMode(true),
+ mStickyTransform(0) {}
+
+GonkBufferQueueProducer::~GonkBufferQueueProducer() {}
+
+status_t GonkBufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
+ ATRACE_CALL();
+ ALOGV("requestBuffer: slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("requestBuffer: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("requestBuffer: slot index %d out of range [0, %d)",
+ slot, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::DEQUEUED) {
+ ALOGE("requestBuffer: slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+
+ mSlots[slot].mRequestBufferCalled = true;
+ *buf = mSlots[slot].mGraphicBuffer;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::setBufferCount(int bufferCount) {
+ ATRACE_CALL();
+ ALOGV("setBufferCount: count = %d", bufferCount);
+
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("setBufferCount: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (bufferCount > GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("setBufferCount: bufferCount %d too large (max %d)",
+ bufferCount, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ // There must be no dequeued buffers when changing the buffer count.
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == GonkBufferSlot::DEQUEUED) {
+ ALOGE("setBufferCount: buffer owned by producer");
+ return BAD_VALUE;
+ }
+ }
+
+ if (bufferCount == 0) {
+ mCore->mOverrideMaxBufferCount = 0;
+ mCore->mDequeueCondition.broadcast();
+ return NO_ERROR;
+ }
+
+ const int minBufferSlots = mCore->getMinMaxBufferCountLocked(false);
+ if (bufferCount < minBufferSlots) {
+ ALOGE("setBufferCount: requested buffer count %d is less than "
+ "minimum %d", bufferCount, minBufferSlots);
+ return BAD_VALUE;
+ }
+
+ // Here we are guaranteed that the producer doesn't have any dequeued
+ // buffers and will release all of its buffer references. We don't
+ // clear the queue, however, so that currently queued buffers still
+ // get displayed.
+ mCore->freeAllBuffersLocked();
+ mCore->mOverrideMaxBufferCount = bufferCount;
+ mCore->mDequeueCondition.broadcast();
+ listener = mCore->mConsumerListener;
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::waitForFreeSlotThenRelock(const char* caller,
+ bool async, int* found, status_t* returnFlags) const {
+ bool tryAgain = true;
+ while (tryAgain) {
+ if (mCore->mIsAbandoned) {
+ ALOGE("%s: GonkBufferQueue has been abandoned", caller);
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = mCore->getMaxBufferCountLocked(async);
+ if (async && mCore->mOverrideMaxBufferCount) {
+ // FIXME: Some drivers are manually setting the buffer count
+ // (which they shouldn't), so we do this extra test here to
+ // handle that case. This is TEMPORARY until we get this fixed.
+ if (mCore->mOverrideMaxBufferCount < maxBufferCount) {
+ ALOGE("%s: async mode is invalid with buffer count override",
+ caller);
+ return BAD_VALUE;
+ }
+ }
+
+ // Free up any buffers that are in slots beyond the max buffer count
+ //for (int s = maxBufferCount; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ // assert(mSlots[s].mBufferState == GonkBufferSlot::FREE);
+ // if (mSlots[s].mGraphicBuffer != NULL) {
+ // mCore->freeBufferLocked(s);
+ // *returnFlags |= RELEASE_ALL_BUFFERS;
+ // }
+ //}
+
+ // Look for a free buffer to give to the client
+ *found = GonkBufferQueueCore::INVALID_BUFFER_SLOT;
+ int dequeuedCount = 0;
+ int acquiredCount = 0;
+ for (int s = 0; s < maxBufferCount; ++s) {
+ switch (mSlots[s].mBufferState) {
+ case GonkBufferSlot::DEQUEUED:
+ ++dequeuedCount;
+ break;
+ case GonkBufferSlot::ACQUIRED:
+ ++acquiredCount;
+ break;
+ case GonkBufferSlot::FREE:
+ // We return the oldest of the free buffers to avoid
+ // stalling the producer if possible, since the consumer
+ // may still have pending reads of in-flight buffers
+ if (*found == GonkBufferQueueCore::INVALID_BUFFER_SLOT ||
+ mSlots[s].mFrameNumber < mSlots[*found].mFrameNumber) {
+ *found = s;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Producers are not allowed to dequeue more than one buffer if they
+ // did not set a buffer count
+ if (!mCore->mOverrideMaxBufferCount && dequeuedCount) {
+ ALOGE("%s: can't dequeue multiple buffers without setting the "
+ "buffer count", caller);
+ return INVALID_OPERATION;
+ }
+
+ // See whether a buffer has been queued since the last
+ // setBufferCount so we know whether to perform the min undequeued
+ // buffers check below
+ if (mCore->mBufferHasBeenQueued) {
+ // Make sure the producer is not trying to dequeue more buffers
+ // than allowed
+ const int newUndequeuedCount =
+ maxBufferCount - (dequeuedCount + 1);
+ const int minUndequeuedCount =
+ mCore->getMinUndequeuedBufferCountLocked(async);
+ if (newUndequeuedCount < minUndequeuedCount) {
+ ALOGE("%s: min undequeued buffer count (%d) exceeded "
+ "(dequeued=%d undequeued=%d)",
+ caller, minUndequeuedCount,
+ dequeuedCount, newUndequeuedCount);
+ return INVALID_OPERATION;
+ }
+ }
+
+ // If we disconnect and reconnect quickly, we can be in a state where
+ // our slots are empty but we have many buffers in the queue. This can
+ // cause us to run out of memory if we outrun the consumer. Wait here if
+ // it looks like we have too many buffers queued up.
+ bool tooManyBuffers = mCore->mQueue.size()
+ > static_cast<size_t>(maxBufferCount);
+ if (tooManyBuffers) {
+ ALOGV("%s: queue size is %zu, waiting", caller,
+ mCore->mQueue.size());
+ }
+
+ // If no buffer is found, or if the queue has too many buffers
+ // outstanding, wait for a buffer to be acquired or released, or for the
+ // max buffer count to change.
+ tryAgain = (*found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) ||
+ tooManyBuffers;
+ if (tryAgain) {
+ // Return an error if we're in non-blocking mode (producer and
+ // consumer are controlled by the application).
+ // However, the consumer is allowed to briefly acquire an extra
+ // buffer (which could cause us to have to wait here), which is
+ // okay, since it is only used to implement an atomic acquire +
+ // release (e.g., in GLConsumer::updateTexImage())
+ if (mCore->mDequeueBufferCannotBlock &&
+ (acquiredCount <= mCore->mMaxAcquiredBufferCount)) {
+ return WOULD_BLOCK;
+ }
+ mCore->mDequeueCondition.wait(mCore->mMutex);
+ }
+ } // while (tryAgain)
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::dequeueBuffer(int *outSlot,
+ sp<android::Fence> *outFence, bool async,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage) {
+ ATRACE_CALL();
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+ mConsumerName = mCore->mConsumerName;
+ } // Autolock scope
+
+ ALOGV("dequeueBuffer: async=%s w=%u h=%u format=%#x, usage=%#x",
+ async ? "true" : "false", width, height, format, usage);
+
+ if ((width && !height) || (!width && height)) {
+ ALOGE("dequeueBuffer: invalid size: w=%u h=%u", width, height);
+ return BAD_VALUE;
+ }
+
+ status_t returnFlags = NO_ERROR;
+ // Reset slot
+ *outSlot = GonkBufferQueueCore::INVALID_BUFFER_SLOT;
+
+ bool attachedByConsumer = false;
+
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ if (format == 0) {
+ format = mCore->mDefaultBufferFormat;
+ }
+
+ // Enable the usage bits the consumer requested
+ usage |= mCore->mConsumerUsageBits;
+
+ int found;
+ status_t status = waitForFreeSlotThenRelock("dequeueBuffer", async,
+ &found, &returnFlags);
+ if (status != NO_ERROR) {
+ return status;
+ }
+
+ // This should not happen
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) {
+ ALOGE("dequeueBuffer: no available buffer slots");
+ return -EBUSY;
+ }
+
+ *outSlot = found;
+
+ attachedByConsumer = mSlots[found].mAttachedByConsumer;
+
+ const bool useDefaultSize = !width && !height;
+ if (useDefaultSize) {
+ width = mCore->mDefaultWidth;
+ height = mCore->mDefaultHeight;
+ }
+
+ mSlots[found].mBufferState = GonkBufferSlot::DEQUEUED;
+
+ const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
+ if ((buffer == NULL) ||
+ (static_cast<uint32_t>(buffer->width) != width) ||
+ (static_cast<uint32_t>(buffer->height) != height) ||
+ (static_cast<uint32_t>(buffer->format) != format) ||
+ ((static_cast<uint32_t>(buffer->usage) & usage) != usage))
+ {
+ mSlots[found].mAcquireCalled = false;
+ mSlots[found].mGraphicBuffer = NULL;
+ mSlots[found].mRequestBufferCalled = false;
+ mSlots[found].mFence = Fence::NO_FENCE;
+
+ if (mSlots[found].mTextureClient) {
+ mSlots[found].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[found].mTextureClient);
+ mSlots[found].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+
+ returnFlags |= BUFFER_NEEDS_REALLOCATION;
+ }
+
+ if (CC_UNLIKELY(mSlots[found].mFence == NULL)) {
+ ALOGE("dequeueBuffer: about to return a NULL fence - "
+ "slot=%d w=%d h=%d format=%u",
+ found, buffer->width, buffer->height, buffer->format);
+ }
+
+ *outFence = mSlots[found].mFence;
+ mSlots[found].mFence = Fence::NO_FENCE;
+ } // Autolock scope
+
+ if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
+ RefPtr<LayersIPCChannel> allocator = ImageBridgeChild::GetSingleton();
+ usage |= GraphicBuffer::USAGE_HW_TEXTURE;
+ GrallocTextureData* texData = GrallocTextureData::Create(IntSize(width,height), format,
+ gfx::BackendType::NONE,
+ usage, allocator);
+ if (!texData) {
+ ALOGE("dequeueBuffer: failed to alloc gralloc buffer");
+ return -ENOMEM;
+ }
+ RefPtr<TextureClient> textureClient = TextureClient::CreateWithData(
+ texData, TextureFlags::RECYCLE | TextureFlags::DEALLOCATE_CLIENT, allocator);
+
+ sp<GraphicBuffer> graphicBuffer = texData->GetGraphicBuffer();
+
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("dequeueBuffer: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ mSlots[*outSlot].mFrameNumber = UINT32_MAX;
+ mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
+ mSlots[*outSlot].mTextureClient = textureClient;
+ } // Autolock scope
+ }
+
+ if (attachedByConsumer) {
+ returnFlags |= BUFFER_NEEDS_REALLOCATION;
+ }
+
+ ALOGV("dequeueBuffer: returning slot=%d/%" PRIu64 " buf=%p flags=%#x",
+ *outSlot,
+ mSlots[*outSlot].mFrameNumber,
+ mSlots[*outSlot].mGraphicBuffer->handle, returnFlags);
+
+ return returnFlags;
+}
+
+status_t GonkBufferQueueProducer::detachBuffer(int slot) {
+ ATRACE_CALL();
+ ATRACE_BUFFER_INDEX(slot);
+ ALOGV("detachBuffer(P): slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("detachBuffer(P): GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("detachBuffer(P): slot index %d out of range [0, %d)",
+ slot, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::DEQUEUED) {
+ ALOGE("detachBuffer(P): slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ } else if (!mSlots[slot].mRequestBufferCalled) {
+ ALOGE("detachBuffer(P): buffer in slot %d has not been requested",
+ slot);
+ return BAD_VALUE;
+ }
+
+ mCore->freeBufferLocked(slot);
+ mCore->mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence) {
+ ATRACE_CALL();
+
+ if (outBuffer == NULL) {
+ ALOGE("detachNextBuffer: outBuffer must not be NULL");
+ return BAD_VALUE;
+ } else if (outFence == NULL) {
+ ALOGE("detachNextBuffer: outFence must not be NULL");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("detachNextBuffer: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ // Find the oldest valid slot
+ int found = GonkBufferQueueCore::INVALID_BUFFER_SLOT;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == GonkBufferSlot::FREE &&
+ mSlots[s].mGraphicBuffer != NULL) {
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT ||
+ mSlots[s].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = s;
+ }
+ }
+ }
+
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) {
+ return NO_MEMORY;
+ }
+
+ ALOGV("detachNextBuffer detached slot %d", found);
+
+ *outBuffer = mSlots[found].mGraphicBuffer;
+ *outFence = mSlots[found].mFence;
+ mCore->freeBufferLocked(found);
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::attachBuffer(int* outSlot,
+ const sp<android::GraphicBuffer>& buffer) {
+ ATRACE_CALL();
+
+ if (outSlot == NULL) {
+ ALOGE("attachBuffer(P): outSlot must not be NULL");
+ return BAD_VALUE;
+ } else if (buffer == NULL) {
+ ALOGE("attachBuffer(P): cannot attach NULL buffer");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ status_t returnFlags = NO_ERROR;
+ int found;
+ // TODO: Should we provide an async flag to attachBuffer? It seems
+ // unlikely that buffers which we are attaching to a GonkBufferQueue will
+ // be asynchronous (droppable), but it may not be impossible.
+ status_t status = waitForFreeSlotThenRelock("attachBuffer(P)", false,
+ &found, &returnFlags);
+ if (status != NO_ERROR) {
+ return status;
+ }
+
+ // This should not happen
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) {
+ ALOGE("attachBuffer(P): no available buffer slots");
+ return -EBUSY;
+ }
+
+ *outSlot = found;
+ ATRACE_BUFFER_INDEX(*outSlot);
+ ALOGV("attachBuffer(P): returning slot %d flags=%#x",
+ *outSlot, returnFlags);
+
+ mSlots[*outSlot].mGraphicBuffer = buffer;
+ mSlots[*outSlot].mBufferState = GonkBufferSlot::DEQUEUED;
+ mSlots[*outSlot].mFence = Fence::NO_FENCE;
+ mSlots[*outSlot].mRequestBufferCalled = true;
+
+ return returnFlags;
+}
+
+status_t GonkBufferQueueProducer::setSynchronousMode(bool enabled) {
+ ALOGV("setSynchronousMode: enabled=%d", enabled);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("setSynchronousMode: BufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mSynchronousMode != enabled) {
+ mSynchronousMode = enabled;
+ mCore->mDequeueCondition.broadcast();
+ }
+ return OK;
+}
+
+status_t GonkBufferQueueProducer::queueBuffer(int slot,
+ const QueueBufferInput &input, QueueBufferOutput *output) {
+ ATRACE_CALL();
+
+ int64_t timestamp;
+ bool isAutoTimestamp;
+ Rect crop;
+ int scalingMode;
+ uint32_t transform;
+ uint32_t stickyTransform;
+ bool async;
+ sp<Fence> fence;
+ input.deflate(&timestamp, &isAutoTimestamp, &crop, &scalingMode, &transform,
+ &async, &fence, &stickyTransform);
+
+ if (fence == NULL) {
+ ALOGE("queueBuffer: fence is NULL");
+ // Temporary workaround for b/17946343: soldier-on instead of returning an error. This
+ // prevents the client from dying, at the risk of visible corruption due to hwcomposer
+ // reading the buffer before the producer is done rendering it. Unless the buffer is the
+ // last frame of an animation, the corruption will be transient.
+ fence = Fence::NO_FENCE;
+ // return BAD_VALUE;
+ }
+
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
+ case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
+ break;
+ default:
+ ALOGE("queueBuffer: unknown scaling mode %d", scalingMode);
+ return BAD_VALUE;
+ }
+
+ GonkBufferItem item;
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("queueBuffer: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = mCore->getMaxBufferCountLocked(async);
+ if (async && mCore->mOverrideMaxBufferCount) {
+ // FIXME: Some drivers are manually setting the buffer count
+ // (which they shouldn't), so we do this extra test here to
+ // handle that case. This is TEMPORARY until we get this fixed.
+ if (mCore->mOverrideMaxBufferCount < maxBufferCount) {
+ ALOGE("queueBuffer: async mode is invalid with "
+ "buffer count override");
+ return BAD_VALUE;
+ }
+ }
+
+ if (slot < 0 || slot >= maxBufferCount) {
+ ALOGE("queueBuffer: slot index %d out of range [0, %d)",
+ slot, maxBufferCount);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::DEQUEUED) {
+ ALOGE("queueBuffer: slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ } else if (!mSlots[slot].mRequestBufferCalled) {
+ ALOGE("queueBuffer: slot %d was queued without requesting "
+ "a buffer", slot);
+ return BAD_VALUE;
+ }
+
+ ALOGV("queueBuffer: slot=%d/%" PRIu64 " time=%" PRIu64
+ " crop=[%d,%d,%d,%d] transform=%#x scale=%s",
+ slot, mCore->mFrameCounter + 1, timestamp,
+ crop.left, crop.top, crop.right, crop.bottom,
+ transform, GonkBufferItem::scalingModeName(scalingMode));
+
+ const sp<GraphicBuffer>& graphicBuffer(mSlots[slot].mGraphicBuffer);
+ Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
+ Rect croppedRect;
+ crop.intersect(bufferRect, &croppedRect);
+ if (croppedRect != crop) {
+ ALOGE("queueBuffer: crop rect is not contained within the "
+ "buffer in slot %d", slot);
+ return BAD_VALUE;
+ }
+
+ mSlots[slot].mFence = fence;
+ mSlots[slot].mBufferState = GonkBufferSlot::QUEUED;
+ ++mCore->mFrameCounter;
+ mSlots[slot].mFrameNumber = mCore->mFrameCounter;
+
+ item.mAcquireCalled = mSlots[slot].mAcquireCalled;
+ item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
+ item.mCrop = crop;
+ item.mTransform = transform & ~NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+ item.mTransformToDisplayInverse =
+ bool(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
+ item.mScalingMode = scalingMode;
+ item.mTimestamp = timestamp;
+ item.mIsAutoTimestamp = isAutoTimestamp;
+ item.mFrameNumber = mCore->mFrameCounter;
+ item.mSlot = slot;
+ item.mFence = fence;
+ item.mIsDroppable = mCore->mDequeueBufferCannotBlock || async;
+
+ mStickyTransform = stickyTransform;
+
+ if (mCore->mQueue.empty()) {
+ // When the queue is empty, we can ignore mDequeueBufferCannotBlock
+ // and simply queue this buffer
+ mCore->mQueue.push_back(item);
+ listener = mCore->mConsumerListener;
+ } else {
+ // When the queue is not empty, we need to look at the front buffer
+ // state to see if we need to replace it
+ GonkBufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
+ if (front->mIsDroppable || !mSynchronousMode) {
+ // If the front queued buffer is still being tracked, we first
+ // mark it as freed
+ if (mCore->stillTracking(front)) {
+ mSlots[front->mSlot].mBufferState = GonkBufferSlot::FREE;
+ // Reset the frame number of the freed buffer so that it is
+ // the first in line to be dequeued again
+ mSlots[front->mSlot].mFrameNumber = 0;
+ }
+ // Overwrite the droppable buffer with the incoming one
+ *front = item;
+ listener = mCore->mConsumerListener;
+ } else {
+ mCore->mQueue.push_back(item);
+ listener = mCore->mConsumerListener;
+ }
+ }
+
+ mCore->mBufferHasBeenQueued = true;
+ mCore->mDequeueCondition.broadcast();
+
+ output->inflate(mCore->mDefaultWidth, mCore->mDefaultHeight,
+ mCore->mTransformHint, mCore->mQueue.size());
+
+ item.mGraphicBuffer.clear();
+ item.mSlot = GonkBufferItem::INVALID_BUFFER_SLOT;
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+#if ANDROID_VERSION == 21
+ listener->onFrameAvailable();
+#else
+ listener->onFrameAvailable(reinterpret_cast<::android::BufferItem&>(item));
+#endif
+ }
+
+ return NO_ERROR;
+}
+
+void GonkBufferQueueProducer::cancelBuffer(int slot, const sp<Fence>& fence) {
+ ATRACE_CALL();
+ ALOGV("cancelBuffer: slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("cancelBuffer: GonkBufferQueue has been abandoned");
+ return;
+ }
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("cancelBuffer: slot index %d out of range [0, %d)",
+ slot, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::DEQUEUED) {
+ ALOGE("cancelBuffer: slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return;
+ } else if (fence == NULL) {
+ ALOGE("cancelBuffer: fence is NULL");
+ return;
+ }
+
+ mSlots[slot].mBufferState = GonkBufferSlot::FREE;
+ mSlots[slot].mFrameNumber = 0;
+ mSlots[slot].mFence = fence;
+ mCore->mDequeueCondition.broadcast();
+}
+
+int GonkBufferQueueProducer::query(int what, int *outValue) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (outValue == NULL) {
+ ALOGE("query: outValue was NULL");
+ return BAD_VALUE;
+ }
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("query: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ int value;
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ value = mCore->mDefaultWidth;
+ break;
+ case NATIVE_WINDOW_HEIGHT:
+ value = mCore->mDefaultHeight;
+ break;
+ case NATIVE_WINDOW_FORMAT:
+ value = mCore->mDefaultBufferFormat;
+ break;
+ case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+ value = mCore->getMinUndequeuedBufferCountLocked(false);
+ break;
+ case NATIVE_WINDOW_STICKY_TRANSFORM:
+ value = static_cast<int>(mStickyTransform);
+ break;
+ case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+ value = (mCore->mQueue.size() > 1);
+ break;
+ case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
+ value = mCore->mConsumerUsageBits;
+ break;
+ default:
+ return BAD_VALUE;
+ }
+
+ ALOGV("query: %d? %d", what, value);
+ *outValue = value;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp, QueueBufferOutput *output) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+ mConsumerName = mCore->mConsumerName;
+ ALOGV("connect(P): api=%d producerControlledByApp=%s", api,
+ producerControlledByApp ? "true" : "false");
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("connect(P): GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (mCore->mConsumerListener == NULL) {
+ ALOGE("connect(P): GonkBufferQueue has no consumer");
+ return NO_INIT;
+ }
+
+ if (output == NULL) {
+ ALOGE("connect(P): output was NULL");
+ return BAD_VALUE;
+ }
+
+ if (mCore->mConnectedApi != GonkBufferQueueCore::NO_CONNECTED_API) {
+ ALOGE("connect(P): already connected (cur=%d req=%d)", mCore->mConnectedApi,
+ api);
+ return BAD_VALUE;
+ }
+
+ int status = NO_ERROR;
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ mCore->mConnectedApi = api;
+ output->inflate(mCore->mDefaultWidth, mCore->mDefaultHeight,
+ mCore->mTransformHint, mCore->mQueue.size());
+
+ // Set up a death notification so that we can disconnect
+ // automatically if the remote producer dies
+ if (listener != NULL &&
+ listener->asBinder()->remoteBinder() != NULL) {
+ status = listener->asBinder()->linkToDeath(
+ static_cast<IBinder::DeathRecipient*>(this));
+ if (status != NO_ERROR) {
+ ALOGE("connect(P): linkToDeath failed: %s (%d)",
+ strerror(-status), status);
+ }
+ }
+ mCore->mConnectedProducerListener = listener;
+ break;
+ default:
+ ALOGE("connect(P): unknown API %d", api);
+ status = BAD_VALUE;
+ break;
+ }
+
+ mCore->mBufferHasBeenQueued = false;
+ mCore->mDequeueBufferCannotBlock =
+ mCore->mConsumerControlledByApp && producerControlledByApp;
+
+ return status;
+}
+
+status_t GonkBufferQueueProducer::disconnect(int api) {
+ ATRACE_CALL();
+ ALOGV("disconnect(P): api %d", api);
+
+ int status = NO_ERROR;
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ if (mCore->mIsAbandoned) {
+ // It's not really an error to disconnect after the surface has
+ // been abandoned; it should just be a no-op.
+ return NO_ERROR;
+ }
+
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ if (mCore->mConnectedApi == api) {
+ mCore->freeAllBuffersLocked();
+ mCore->mConnectedApi = GonkBufferQueueCore::NO_CONNECTED_API;
+ mCore->mSidebandStream.clear();
+ mCore->mDequeueCondition.broadcast();
+ listener = mCore->mConsumerListener;
+ } else {
+ ALOGE("disconnect(P): connected to another API "
+ "(cur=%d req=%d)", mCore->mConnectedApi, api);
+ status = BAD_VALUE;
+ }
+ break;
+ default:
+ ALOGE("disconnect(P): unknown API %d", api);
+ status = BAD_VALUE;
+ break;
+ }
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return status;
+}
+
+status_t GonkBufferQueueProducer::setSidebandStream(const sp<NativeHandle>& stream) {
+ return INVALID_OPERATION;
+}
+
+void GonkBufferQueueProducer::allocateBuffers(bool async, uint32_t width,
+ uint32_t height, uint32_t format, uint32_t usage) {
+ ALOGE("allocateBuffers: no op");
+}
+
+void GonkBufferQueueProducer::binderDied(const wp<android::IBinder>& /* who */) {
+ // If we're here, it means that a producer we were connected to died.
+ // We're guaranteed that we are still connected to it because we remove
+ // this callback upon disconnect. It's therefore safe to read mConnectedApi
+ // without synchronization here.
+ int api = mCore->mConnectedApi;
+ disconnect(api);
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.h
new file mode 100644
index 000000000..a1a22416a
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUEPRODUCER_LL_H
+#define NATIVEWINDOW_GONKBUFFERQUEUEPRODUCER_LL_H
+
+#include "GonkBufferQueueDefs.h"
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+
+class GonkBufferQueueProducer : public BnGraphicBufferProducer,
+ private IBinder::DeathRecipient {
+public:
+ friend class GonkBufferQueue; // Needed to access binderDied
+
+ GonkBufferQueueProducer(const sp<GonkBufferQueueCore>& core);
+ virtual ~GonkBufferQueueProducer();
+
+ // requestBuffer returns the GraphicBuffer for slot N.
+ //
+ // In normal operation, this is called the first time slot N is returned
+ // by dequeueBuffer. It must be called again if dequeueBuffer returns
+ // flags indicating that previously-returned buffers are no longer valid.
+ virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
+
+ // setBufferCount updates the number of available buffer slots. If this
+ // method succeeds, buffer slots will be both unallocated and owned by
+ // the GonkBufferQueue object (i.e. they are not owned by the producer or
+ // consumer).
+ //
+ // This will fail if the producer has dequeued any buffers, or if
+ // bufferCount is invalid. bufferCount must generally be a value
+ // between the minimum undequeued buffer count (exclusive) and NUM_BUFFER_SLOTS
+ // (inclusive). It may also be set to zero (the default) to indicate
+ // that the producer does not wish to set a value. The minimum value
+ // can be obtained by calling query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ // ...).
+ //
+ // This may only be called by the producer. The consumer will be told
+ // to discard buffers through the onBuffersReleased callback.
+ virtual status_t setBufferCount(int bufferCount);
+
+ // dequeueBuffer gets the next buffer slot index for the producer to use.
+ // If a buffer slot is available then that slot index is written to the
+ // location pointed to by the buf argument and a status of OK is returned.
+ // If no slot is available then a status of -EBUSY is returned and buf is
+ // unmodified.
+ //
+ // The outFence parameter will be updated to hold the fence associated with
+ // the buffer. The contents of the buffer must not be overwritten until the
+ // fence signals. If the fence is Fence::NO_FENCE, the buffer may be
+ // written immediately.
+ //
+ // The width and height parameters must be no greater than the minimum of
+ // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv).
+ // An error due to invalid dimensions might not be reported until
+ // updateTexImage() is called. If width and height are both zero, the
+ // default values specified by setDefaultBufferSize() are used instead.
+ //
+ // The pixel formats are enumerated in graphics.h, e.g.
+ // HAL_PIXEL_FORMAT_RGBA_8888. If the format is 0, the default format
+ // will be used.
+ //
+ // The usage argument specifies gralloc buffer usage flags. The values
+ // are enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER. These
+ // will be merged with the usage flags specified by setConsumerUsageBits.
+ //
+ // The return value may be a negative error value or a non-negative
+ // collection of flags. If the flags are set, the return values are
+ // valid, but additional actions must be performed.
+ //
+ // If IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION is set, the
+ // producer must discard cached GraphicBuffer references for the slot
+ // returned in buf.
+ // If IGraphicBufferProducer::RELEASE_ALL_BUFFERS is set, the producer
+ // must discard cached GraphicBuffer references for all slots.
+ //
+ // In both cases, the producer will need to call requestBuffer to get a
+ // GraphicBuffer handle for the returned slot.
+ virtual status_t dequeueBuffer(int *outSlot, sp<Fence>* outFence, bool async,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage);
+
+ // See IGraphicBufferProducer::detachBuffer
+ virtual status_t detachBuffer(int slot);
+
+ // See IGraphicBufferProducer::detachNextBuffer
+ virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence);
+
+ // See IGraphicBufferProducer::attachBuffer
+ virtual status_t attachBuffer(int* outSlot, const sp<GraphicBuffer>& buffer);
+
+ // queueBuffer returns a filled buffer to the GonkBufferQueue.
+ //
+ // Additional data is provided in the QueueBufferInput struct. Notably,
+ // a timestamp must be provided for the buffer. The timestamp is in
+ // nanoseconds, and must be monotonically increasing. Its other semantics
+ // (zero point, etc) are producer-specific and should be documented by the
+ // producer.
+ //
+ // The caller may provide a fence that signals when all rendering
+ // operations have completed. Alternatively, NO_FENCE may be used,
+ // indicating that the buffer is ready immediately.
+ //
+ // Some values are returned in the output struct: the current settings
+ // for default width and height, the current transform hint, and the
+ // number of queued buffers.
+ virtual status_t queueBuffer(int slot,
+ const QueueBufferInput& input, QueueBufferOutput* output);
+
+ // cancelBuffer returns a dequeued buffer to the GonkBufferQueue, but doesn't
+ // queue it for use by the consumer.
+ //
+ // The buffer will not be overwritten until the fence signals. The fence
+ // will usually be the one obtained from dequeueBuffer.
+ virtual void cancelBuffer(int slot, const sp<Fence>& fence);
+
+ // Query native window attributes. The "what" values are enumerated in
+ // window.h (e.g. NATIVE_WINDOW_FORMAT).
+ virtual int query(int what, int* outValue);
+
+ // connect attempts to connect a producer API to the GonkBufferQueue. This
+ // must be called before any other IGraphicBufferProducer methods are
+ // called except for getAllocator. A consumer must already be connected.
+ //
+ // This method will fail if connect was previously called on the
+ // GonkBufferQueue and no corresponding disconnect call was made (i.e. if
+ // it's still connected to a producer).
+ //
+ // APIs are enumerated in window.h (e.g. NATIVE_WINDOW_API_CPU).
+ virtual status_t connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp, QueueBufferOutput* output);
+
+ // disconnect attempts to disconnect a producer API from the GonkBufferQueue.
+ // Calling this method will cause any subsequent calls to other
+ // IGraphicBufferProducer methods to fail except for getAllocator and connect.
+ // Successfully calling connect after this will allow the other methods to
+ // succeed again.
+ //
+ // This method will fail if the the GonkBufferQueue is not currently
+ // connected to the specified producer API.
+ virtual status_t disconnect(int api);
+
+ // Attaches a sideband buffer stream to the IGraphicBufferProducer.
+ //
+ // A sideband stream is a device-specific mechanism for passing buffers
+ // from the producer to the consumer without using dequeueBuffer/
+ // queueBuffer. If a sideband stream is present, the consumer can choose
+ // whether to acquire buffers from the sideband stream or from the queued
+ // buffers.
+ //
+ // Passing NULL or a different stream handle will detach the previous
+ // handle if any.
+ virtual status_t setSidebandStream(const sp<NativeHandle>& stream);
+
+ // See IGraphicBufferProducer::allocateBuffers
+ virtual void allocateBuffers(bool async, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t usage);
+
+ // setSynchronousMode sets whether dequeueBuffer is synchronous or
+ // asynchronous. In synchronous mode, dequeueBuffer blocks until
+ // a buffer is available, the currently bound buffer can be dequeued and
+ // queued buffers will be acquired in order. In asynchronous mode,
+ // a queued buffer may be replaced by a subsequently queued buffer.
+ //
+ // The default mode is synchronous.
+ // This should be called only during initialization.
+ virtual status_t setSynchronousMode(bool enabled);
+
+private:
+ // This is required by the IBinder::DeathRecipient interface
+ virtual void binderDied(const wp<IBinder>& who);
+
+ // waitForFreeSlotThenRelock finds the oldest slot in the FREE state. It may
+ // block if there are no available slots and we are not in non-blocking
+ // mode (producer and consumer controlled by the application). If it blocks,
+ // it will release mCore->mMutex while blocked so that other operations on
+ // the GonkBufferQueue may succeed.
+ status_t waitForFreeSlotThenRelock(const char* caller, bool async,
+ int* found, status_t* returnFlags) const;
+
+ sp<GonkBufferQueueCore> mCore;
+
+ // This references mCore->mSlots. Lock mCore->mMutex while accessing.
+ GonkBufferQueueDefs::SlotsType& mSlots;
+
+ // This is a cached copy of the name stored in the GonkBufferQueueCore.
+ // It's updated during connect and dequeueBuffer (which should catch
+ // most updates).
+ String8 mConsumerName;
+
+ // mSynchronousMode whether we're in synchronous mode or not
+ bool mSynchronousMode;
+
+ uint32_t mStickyTransform;
+
+}; // class GonkBufferQueueProducer
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.cpp
new file mode 100644
index 000000000..9e4a424a9
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GonkBufferSlot.h"
+
+namespace android {
+
+const char* GonkBufferSlot::bufferStateName(BufferState state) {
+ switch (state) {
+ case GonkBufferSlot::DEQUEUED: return "DEQUEUED";
+ case GonkBufferSlot::QUEUED: return "QUEUED";
+ case GonkBufferSlot::FREE: return "FREE";
+ case GonkBufferSlot::ACQUIRED: return "ACQUIRED";
+ default: return "Unknown";
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.h
new file mode 100644
index 000000000..759bb7b23
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERSLOT_LL_H
+#define NATIVEWINDOW_GONKBUFFERSLOT_LL_H
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <utils/StrongPointer.h>
+
+#include "mozilla/layers/TextureClient.h"
+
+namespace android {
+
+struct GonkBufferSlot {
+ typedef mozilla::layers::TextureClient TextureClient;
+
+ GonkBufferSlot()
+ : mBufferState(GonkBufferSlot::FREE),
+ mRequestBufferCalled(false),
+ mFrameNumber(0),
+ mAcquireCalled(false),
+ mNeedsCleanupOnRelease(false),
+ mAttachedByConsumer(false) {
+ }
+
+ // mGraphicBuffer points to the buffer allocated for this slot or is NULL
+ // if no buffer has been allocated.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // BufferState represents the different states in which a buffer slot
+ // can be. All slots are initially FREE.
+ enum BufferState {
+ // FREE indicates that the buffer is available to be dequeued
+ // by the producer. The buffer may be in use by the consumer for
+ // a finite time, so the buffer must not be modified until the
+ // associated fence is signaled.
+ //
+ // The slot is "owned" by BufferQueue. It transitions to DEQUEUED
+ // when dequeueBuffer is called.
+ FREE = 0,
+
+ // DEQUEUED indicates that the buffer has been dequeued by the
+ // producer, but has not yet been queued or canceled. The
+ // producer may modify the buffer's contents as soon as the
+ // associated ready fence is signaled.
+ //
+ // The slot is "owned" by the producer. It can transition to
+ // QUEUED (via queueBuffer) or back to FREE (via cancelBuffer).
+ DEQUEUED = 1,
+
+ // QUEUED indicates that the buffer has been filled by the
+ // producer and queued for use by the consumer. The buffer
+ // contents may continue to be modified for a finite time, so
+ // the contents must not be accessed until the associated fence
+ // is signaled.
+ //
+ // The slot is "owned" by BufferQueue. It can transition to
+ // ACQUIRED (via acquireBuffer) or to FREE (if another buffer is
+ // queued in asynchronous mode).
+ QUEUED = 2,
+
+ // ACQUIRED indicates that the buffer has been acquired by the
+ // consumer. As with QUEUED, the contents must not be accessed
+ // by the consumer until the fence is signaled.
+ //
+ // The slot is "owned" by the consumer. It transitions to FREE
+ // when releaseBuffer is called.
+ ACQUIRED = 3
+ };
+
+ static const char* bufferStateName(BufferState state);
+
+ // mBufferState is the current state of this buffer slot.
+ BufferState mBufferState;
+
+ // mRequestBufferCalled is used for validating that the producer did
+ // call requestBuffer() when told to do so. Technically this is not
+ // needed but useful for debugging and catching producer bugs.
+ bool mRequestBufferCalled;
+
+ // mFrameNumber is the number of the queued frame for this slot. This
+ // is used to dequeue buffers in LRU order (useful because buffers
+ // may be released before their release fence is signaled).
+ uint64_t mFrameNumber;
+
+ // mFence is a fence which will signal when work initiated by the
+ // previous owner of the buffer is finished. When the buffer is FREE,
+ // the fence indicates when the consumer has finished reading
+ // from the buffer, or when the producer has finished writing if it
+ // called cancelBuffer after queueing some writes. When the buffer is
+ // QUEUED, it indicates when the producer has finished filling the
+ // buffer. When the buffer is DEQUEUED or ACQUIRED, the fence has been
+ // passed to the consumer or producer along with ownership of the
+ // buffer, and mFence is set to NO_FENCE.
+ sp<Fence> mFence;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates whether this buffer needs to be cleaned up by the
+ // consumer. This is set when a buffer in ACQUIRED state is freed.
+ // It causes releaseBuffer to return STALE_BUFFER_SLOT.
+ bool mNeedsCleanupOnRelease;
+
+ // Indicates whether the buffer was attached on the consumer side.
+ // If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when dequeued
+ // to prevent the producer from using a stale cached buffer.
+ bool mAttachedByConsumer;
+
+ // mTextureClient is a thin abstraction over remotely allocated GraphicBuffer.
+ RefPtr<TextureClient> mTextureClient;
+};
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseJB.cpp b/widget/gonk/nativewindow/GonkConsumerBaseJB.cpp
new file mode 100644
index 000000000..1ee37e4e2
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseJB.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GonkConsumerBase"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <hardware/hardware.h>
+
+#include <gui/IGraphicBufferAlloc.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include "GonkConsumerBaseJB.h"
+
+// Macros for including the GonkConsumerBase name in log messages
+#define CB_LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define CB_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define CB_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define CB_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define CB_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+GonkConsumerBase::GonkConsumerBase(const sp<GonkBufferQueue>& bufferQueue) :
+ mAbandoned(false),
+ mBufferQueue(bufferQueue) {
+ // Choose a name using the PID and a process-unique ID.
+ mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ // Note that we can't create an sp<...>(this) in a ctor that will not keep a
+ // reference once the ctor ends, as that would cause the refcount of 'this'
+ // dropping to 0 at the end of the ctor. Since all we need is a wp<...>
+ // that's what we create.
+ wp<GonkBufferQueue::ConsumerListener> listener;
+ sp<GonkBufferQueue::ConsumerListener> proxy;
+ listener = static_cast<GonkBufferQueue::ConsumerListener*>(this);
+ proxy = new GonkBufferQueue::ProxyConsumerListener(listener);
+
+ status_t err = mBufferQueue->consumerConnect(proxy);
+ if (err != NO_ERROR) {
+ CB_LOGE("GonkConsumerBase: error connecting to GonkBufferQueue: %s (%d)",
+ strerror(-err), err);
+ } else {
+ mBufferQueue->setConsumerName(mName);
+ }
+}
+
+GonkConsumerBase::~GonkConsumerBase() {
+ CB_LOGV("~GonkConsumerBase");
+ Mutex::Autolock lock(mMutex);
+
+ // Verify that abandon() has been called before we get here. This should
+ // be done by GonkConsumerBase::onLastStrongRef(), but it's possible for a
+ // derived class to override that method and not call
+ // GonkConsumerBase::onLastStrongRef().
+ LOG_ALWAYS_FATAL_IF(!mAbandoned, "[%s] ~GonkConsumerBase was called, but the "
+ "consumer is not abandoned!", mName.string());
+}
+
+void GonkConsumerBase::onLastStrongRef(const void* id) {
+ abandon();
+}
+
+void GonkConsumerBase::freeBufferLocked(int slotIndex) {
+ CB_LOGV("freeBufferLocked: slotIndex=%d", slotIndex);
+ mSlots[slotIndex].mGraphicBuffer = 0;
+ mSlots[slotIndex].mFence = Fence::NO_FENCE;
+}
+
+// Used for refactoring, should not be in final interface
+sp<GonkBufferQueue> GonkConsumerBase::getBufferQueue() const {
+ Mutex::Autolock lock(mMutex);
+ return mBufferQueue;
+}
+
+void GonkConsumerBase::onFrameAvailable() {
+ CB_LOGV("onFrameAvailable");
+
+ sp<FrameAvailableListener> listener;
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+#if ANDROID_VERSION == 17
+ listener = mFrameAvailableListener;
+#else
+ listener = mFrameAvailableListener.promote();
+#endif
+ }
+
+ if (listener != NULL) {
+ CB_LOGV("actually calling onFrameAvailable");
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkConsumerBase::onBuffersReleased() {
+ Mutex::Autolock lock(mMutex);
+
+ CB_LOGV("onBuffersReleased");
+
+ if (mAbandoned) {
+ // Nothing to do if we're already abandoned.
+ return;
+ }
+
+ uint32_t mask = 0;
+ mBufferQueue->getReleasedBuffers(&mask);
+ for (int i = 0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if (mask & (1 << i)) {
+ freeBufferLocked(i);
+ }
+ }
+}
+
+void GonkConsumerBase::abandon() {
+ CB_LOGV("abandon");
+ Mutex::Autolock lock(mMutex);
+
+ if (!mAbandoned) {
+ abandonLocked();
+ mAbandoned = true;
+ }
+}
+
+void GonkConsumerBase::abandonLocked() {
+ CB_LOGV("abandonLocked");
+ for (int i =0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ freeBufferLocked(i);
+ }
+ // disconnect from the GonkBufferQueue
+ mBufferQueue->consumerDisconnect();
+ mBufferQueue.clear();
+}
+
+void GonkConsumerBase::setFrameAvailableListener(
+#if ANDROID_VERSION == 17
+ const sp<FrameAvailableListener>& listener) {
+#else
+ const wp<FrameAvailableListener>& listener) {
+#endif
+ CB_LOGV("setFrameAvailableListener");
+ Mutex::Autolock lock(mMutex);
+ mFrameAvailableListener = listener;
+}
+
+void GonkConsumerBase::dump(String8& result) const {
+ char buffer[1024];
+ dump(result, "", buffer, 1024);
+}
+
+void GonkConsumerBase::dump(String8& result, const char* prefix,
+ char* buffer, size_t size) const {
+ Mutex::Autolock _l(mMutex);
+ dumpLocked(result, prefix, buffer, size);
+}
+
+void GonkConsumerBase::dumpLocked(String8& result, const char* prefix,
+ char* buffer, size_t SIZE) const {
+ snprintf(buffer, SIZE, "%smAbandoned=%d\n", prefix, int(mAbandoned));
+ result.append(buffer);
+
+ if (!mAbandoned) {
+ mBufferQueue->dumpToString(result, prefix, buffer, SIZE);
+ }
+}
+
+status_t GonkConsumerBase::acquireBufferLocked(GonkBufferQueue::BufferItem *item) {
+ status_t err = mBufferQueue->acquireBuffer(item);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ if (item->mGraphicBuffer != NULL) {
+ mSlots[item->mBuf].mGraphicBuffer = item->mGraphicBuffer;
+ }
+
+ mSlots[item->mBuf].mFence = item->mFence;
+
+ CB_LOGV("acquireBufferLocked: -> slot=%d", item->mBuf);
+
+ return OK;
+}
+
+status_t GonkConsumerBase::addReleaseFence(int slot, const sp<Fence>& fence) {
+ Mutex::Autolock lock(mMutex);
+ return addReleaseFenceLocked(slot, fence);
+}
+
+status_t GonkConsumerBase::addReleaseFenceLocked(int slot, const sp<Fence>& fence) {
+ CB_LOGV("addReleaseFenceLocked: slot=%d", slot);
+
+ if (!mSlots[slot].mFence.get()) {
+ mSlots[slot].mFence = fence;
+ } else {
+ sp<Fence> mergedFence = Fence::merge(
+ String8::format("%.28s:%d", mName.string(), slot),
+ mSlots[slot].mFence, fence);
+ if (!mergedFence.get()) {
+ CB_LOGE("failed to merge release fences");
+ // synchronization is broken, the best we can do is hope fences
+ // signal in order so the new fence will act like a union
+ mSlots[slot].mFence = fence;
+ return BAD_VALUE;
+ }
+ mSlots[slot].mFence = mergedFence;
+ }
+
+ return OK;
+}
+
+status_t GonkConsumerBase::releaseBufferLocked(int slot) {
+ CB_LOGV("releaseBufferLocked: slot=%d", slot);
+ status_t err = mBufferQueue->releaseBuffer(slot, mSlots[slot].mFence);
+ if (err == GonkBufferQueue::STALE_BUFFER_SLOT) {
+ freeBufferLocked(slot);
+ }
+
+ mSlots[slot].mFence = Fence::NO_FENCE;
+
+ return err;
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseJB.h b/widget/gonk/nativewindow/GonkConsumerBaseJB.h
new file mode 100644
index 000000000..8f523af37
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseJB.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKCONSUMERBASE_JB_H
+#define NATIVEWINDOW_GONKCONSUMERBASE_JB_H
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "GonkBufferQueueJB.h"
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class String8;
+
+// GonkConsumerBase is a base class for GonkBufferQueue consumer end-points. It
+// handles common tasks like management of the connection to the GonkBufferQueue
+// and the buffer pool.
+class GonkConsumerBase : public virtual RefBase,
+ protected GonkBufferQueue::ConsumerListener {
+public:
+ struct FrameAvailableListener : public virtual RefBase {
+ // onFrameAvailable() is called each time an additional frame becomes
+ // available for consumption. This means that frames that are queued
+ // while in asynchronous mode only trigger the callback if no previous
+ // frames are pending. Frames queued while in synchronous mode always
+ // trigger the callback.
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onFrameAvailable() = 0;
+ };
+
+ virtual ~GonkConsumerBase();
+
+ // abandon frees all the buffers and puts the GonkConsumerBase into the
+ // 'abandoned' state. Once put in this state the GonkConsumerBase can never
+ // leave it. When in the 'abandoned' state, all methods of the
+ // IGraphicBufferProducer interface will fail with the NO_INIT error.
+ //
+ // Note that while calling this method causes all the buffers to be freed
+ // from the perspective of the the GonkConsumerBase, if there are additional
+ // references on the buffers (e.g. if a buffer is referenced by a client
+ // or by OpenGL ES as a texture) then those buffer will remain allocated.
+ void abandon();
+
+ // set the name of the GonkConsumerBase that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // getBufferQueue returns the GonkBufferQueue object to which this
+ // GonkConsumerBase is connected.
+ sp<GonkBufferQueue> getBufferQueue() const;
+
+ // dump writes the current state to a string. Child classes should add
+ // their state to the dump by overriding the dumpLocked method, which is
+ // called by these methods after locking the mutex.
+ void dump(String8& result) const;
+ void dump(String8& result, const char* prefix, char* buffer, size_t SIZE) const;
+
+ // setFrameAvailableListener sets the listener object that will be notified
+ // when a new frame becomes available.
+#if ANDROID_VERSION == 17
+ void setFrameAvailableListener(const sp<FrameAvailableListener>& listener);
+#else
+ void setFrameAvailableListener(const wp<FrameAvailableListener>& listener);
+#endif
+
+private:
+ GonkConsumerBase(const GonkConsumerBase&);
+ void operator=(const GonkConsumerBase&);
+
+protected:
+
+ // GonkConsumerBase constructs a new GonkConsumerBase object to consume image
+ // buffers from the given GonkBufferQueue.
+ GonkConsumerBase(const sp<GonkBufferQueue> &bufferQueue);
+
+ // onLastStrongRef gets called by RefBase just before the dtor of the most
+ // derived class. It is used to clean up the buffers so that GonkConsumerBase
+ // can coordinate the clean-up by calling into virtual methods implemented
+ // by the derived classes. This would not be possible from the
+ // ConsuemrBase dtor because by the time that gets called the derived
+ // classes have already been destructed.
+ //
+ // This methods should not need to be overridden by derived classes, but
+ // if they are overridden the GonkConsumerBase implementation must be called
+ // from the derived class.
+ virtual void onLastStrongRef(const void* id);
+
+ // Implementation of the GonkBufferQueue::ConsumerListener interface. These
+ // calls are used to notify the GonkConsumerBase of asynchronous events in the
+ // GonkBufferQueue. These methods should not need to be overridden by derived
+ // classes, but if they are overridden the GonkConsumerBase implementation
+ // must be called from the derived class.
+ virtual void onFrameAvailable();
+ virtual void onBuffersReleased();
+
+ // freeBufferLocked frees up the given buffer slot. If the slot has been
+ // initialized this will release the reference to the GraphicBuffer in that
+ // slot. Otherwise it has no effect.
+ //
+ // Derived classes should override this method to clean up any state they
+ // keep per slot. If it is overridden, the derived class's implementation
+ // must call GonkConsumerBase::freeBufferLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void freeBufferLocked(int slotIndex);
+
+ // abandonLocked puts the GonkBufferQueue into the abandoned state, causing
+ // all future operations on it to fail. This method rather than the public
+ // abandon method should be overridden by child classes to add abandon-
+ // time behavior.
+ //
+ // Derived classes should override this method to clean up any object
+ // state they keep (as opposed to per-slot state). If it is overridden,
+ // the derived class's implementation must call GonkConsumerBase::abandonLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void abandonLocked();
+
+ // dumpLocked dumps the current state of the GonkConsumerBase object to the
+ // result string. Each line is prefixed with the string pointed to by the
+ // prefix argument. The buffer argument points to a buffer that may be
+ // used for intermediate formatting data, and the size of that buffer is
+ // indicated by the size argument.
+ //
+ // Derived classes should override this method to dump their internal
+ // state. If this method is overridden the derived class's implementation
+ // should call GonkConsumerBase::dumpLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void dumpLocked(String8& result, const char* prefix, char* buffer,
+ size_t size) const;
+
+ // acquireBufferLocked fetches the next buffer from the GonkBufferQueue and
+ // updates the buffer slot for the buffer returned.
+ //
+ // Derived classes should override this method to perform any
+ // initialization that must take place the first time a buffer is assigned
+ // to a slot. If it is overridden the derived class's implementation must
+ // call GonkConsumerBase::acquireBufferLocked.
+ virtual status_t acquireBufferLocked(GonkBufferQueue::BufferItem *item);
+
+ // releaseBufferLocked relinquishes control over a buffer, returning that
+ // control to the GonkBufferQueue.
+ //
+ // Derived classes should override this method to perform any cleanup that
+ // must take place when a buffer is released back to the GonkBufferQueue. If
+ // it is overridden the derived class's implementation must call
+ // GonkConsumerBase::releaseBufferLocked.
+ virtual status_t releaseBufferLocked(int buf);
+
+ // addReleaseFence* adds the sync points associated with a fence to the set
+ // of sync points that must be reached before the buffer in the given slot
+ // may be used after the slot has been released. This should be called by
+ // derived classes each time some asynchronous work is kicked off that
+ // references the buffer.
+ status_t addReleaseFence(int slot, const sp<Fence>& fence);
+ status_t addReleaseFenceLocked(int slot, const sp<Fence>& fence);
+
+ // Slot contains the information and object references that
+ // GonkConsumerBase maintains about a GonkBufferQueue buffer slot.
+ struct Slot {
+ // mGraphicBuffer is the Gralloc buffer store in the slot or NULL if
+ // no Gralloc buffer is in the slot.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence which will signal when the buffer associated with
+ // this buffer slot is no longer being used by the consumer and can be
+ // overwritten. The buffer can be dequeued before the fence signals;
+ // the producer is responsible for delaying writes until it signals.
+ sp<Fence> mFence;
+ };
+
+ // mSlots stores the buffers that have been allocated by the GonkBufferQueue
+ // for each buffer slot. It is initialized to null pointers, and gets
+ // filled in with the result of GonkBufferQueue::acquire when the
+ // client dequeues a buffer from a
+ // slot that has not yet been used. The buffer allocated to a slot will also
+ // be replaced if the requested buffer usage or geometry differs from that
+ // of the buffer allocated to a slot.
+ Slot mSlots[GonkBufferQueue::NUM_BUFFER_SLOTS];
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume images buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the abandon
+ // method. A GonkBufferQueue that has been abandoned will return the NO_INIT
+ // error from all IGonkConsumerBase methods capable of returning an error.
+ bool mAbandoned;
+
+ // mName is a string used to identify the GonkConsumerBase in log messages.
+ // It can be set by the setName method.
+ String8 mName;
+
+ // mFrameAvailableListener is the listener object that will be called when a
+ // new frame becomes available. If it is not NULL it will be called from
+ // queueBuffer.
+#if ANDROID_VERSION == 17
+ sp<FrameAvailableListener> mFrameAvailableListener;
+#else
+ wp<FrameAvailableListener> mFrameAvailableListener;
+#endif
+
+ // The GonkConsumerBase has-a GonkBufferQueue and is responsible for creating this object
+ // if none is supplied
+ sp<GonkBufferQueue> mBufferQueue;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkConsumerBase objects. It must be locked whenever the
+ // member variables are accessed or when any of the *Locked methods are
+ // called.
+ //
+ // This mutex is intended to be locked by derived classes.
+ mutable Mutex mMutex;
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_GONKCONSUMERBASE_H
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseKK.cpp b/widget/gonk/nativewindow/GonkConsumerBaseKK.cpp
new file mode 100644
index 000000000..3fc9fc16c
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseKK.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GonkConsumerBase"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <hardware/hardware.h>
+
+#include <gui/IGraphicBufferAlloc.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include "GonkConsumerBaseKK.h"
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+GonkConsumerBase::GonkConsumerBase(const sp<GonkBufferQueue>& bufferQueue, bool controlledByApp) :
+ mAbandoned(false),
+ mConsumer(bufferQueue) {
+ // Choose a name using the PID and a process-unique ID.
+ mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ // Note that we can't create an sp<...>(this) in a ctor that will not keep a
+ // reference once the ctor ends, as that would cause the refcount of 'this'
+ // dropping to 0 at the end of the ctor. Since all we need is a wp<...>
+ // that's what we create.
+ wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
+ sp<IConsumerListener> proxy = new GonkBufferQueue::ProxyConsumerListener(listener);
+
+ status_t err = mConsumer->consumerConnect(proxy, controlledByApp);
+ if (err != NO_ERROR) {
+ ALOGE("GonkConsumerBase: error connecting to GonkBufferQueue: %s (%d)",
+ strerror(-err), err);
+ } else {
+ mConsumer->setConsumerName(mName);
+ }
+}
+
+GonkConsumerBase::~GonkConsumerBase() {
+ ALOGV("~GonkConsumerBase");
+ Mutex::Autolock lock(mMutex);
+
+ // Verify that abandon() has been called before we get here. This should
+ // be done by GonkConsumerBase::onLastStrongRef(), but it's possible for a
+ // derived class to override that method and not call
+ // GonkConsumerBase::onLastStrongRef().
+ LOG_ALWAYS_FATAL_IF(!mAbandoned, "[%s] ~GonkConsumerBase was called, but the "
+ "consumer is not abandoned!", mName.string());
+}
+
+void GonkConsumerBase::onLastStrongRef(const void* id) {
+ abandon();
+}
+
+void GonkConsumerBase::freeBufferLocked(int slotIndex) {
+ ALOGV("freeBufferLocked: slotIndex=%d", slotIndex);
+ mSlots[slotIndex].mGraphicBuffer = 0;
+ mSlots[slotIndex].mFence = Fence::NO_FENCE;
+ mSlots[slotIndex].mFrameNumber = 0;
+}
+
+// Used for refactoring, should not be in final interface
+sp<GonkBufferQueue> GonkConsumerBase::getBufferQueue() const {
+ Mutex::Autolock lock(mMutex);
+ return mConsumer;
+}
+
+void GonkConsumerBase::onFrameAvailable() {
+ ALOGV("onFrameAvailable");
+
+ sp<FrameAvailableListener> listener;
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+ listener = mFrameAvailableListener.promote();
+ }
+
+ if (listener != NULL) {
+ ALOGV("actually calling onFrameAvailable");
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkConsumerBase::onBuffersReleased() {
+ Mutex::Autolock lock(mMutex);
+
+ ALOGV("onBuffersReleased");
+
+ if (mAbandoned) {
+ // Nothing to do if we're already abandoned.
+ return;
+ }
+
+ uint32_t mask = 0;
+ mConsumer->getReleasedBuffers(&mask);
+ for (int i = 0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if (mask & (1 << i)) {
+ freeBufferLocked(i);
+ }
+ }
+}
+
+void GonkConsumerBase::abandon() {
+ ALOGV("abandon");
+ Mutex::Autolock lock(mMutex);
+
+ if (!mAbandoned) {
+ abandonLocked();
+ mAbandoned = true;
+ }
+}
+
+void GonkConsumerBase::abandonLocked() {
+ ALOGV("abandonLocked");
+ for (int i =0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ freeBufferLocked(i);
+ }
+ // disconnect from the BufferQueue
+ mConsumer->consumerDisconnect();
+ mConsumer.clear();
+}
+
+void GonkConsumerBase::setFrameAvailableListener(
+ const wp<FrameAvailableListener>& listener) {
+ ALOGV("setFrameAvailableListener");
+ Mutex::Autolock lock(mMutex);
+ mFrameAvailableListener = listener;
+}
+
+void GonkConsumerBase::dump(String8& result) const {
+ dump(result, "");
+}
+
+void GonkConsumerBase::dump(String8& result, const char* prefix) const {
+ Mutex::Autolock _l(mMutex);
+ dumpLocked(result, prefix);
+}
+
+void GonkConsumerBase::dumpLocked(String8& result, const char* prefix) const {
+ result.appendFormat("%smAbandoned=%d\n", prefix, int(mAbandoned));
+
+ if (!mAbandoned) {
+ mConsumer->dumpToString(result, prefix);
+ }
+}
+
+status_t GonkConsumerBase::acquireBufferLocked(IGonkGraphicBufferConsumer::BufferItem *item,
+ nsecs_t presentWhen) {
+ status_t err = mConsumer->acquireBuffer(item, presentWhen);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ if (item->mGraphicBuffer != NULL) {
+ mSlots[item->mBuf].mGraphicBuffer = item->mGraphicBuffer;
+ }
+
+ mSlots[item->mBuf].mFrameNumber = item->mFrameNumber;
+ mSlots[item->mBuf].mFence = item->mFence;
+
+ ALOGV("acquireBufferLocked: -> slot=%d", item->mBuf);
+
+ return OK;
+}
+
+status_t GonkConsumerBase::addReleaseFence(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
+ Mutex::Autolock lock(mMutex);
+ return addReleaseFenceLocked(slot, graphicBuffer, fence);
+}
+
+status_t GonkConsumerBase::addReleaseFenceLocked(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
+ ALOGV("addReleaseFenceLocked: slot=%d", slot);
+
+ // If consumer no longer tracks this graphicBuffer, we can safely
+ // drop this fence, as it will never be received by the producer.
+ if (!stillTracking(slot, graphicBuffer)) {
+ return OK;
+ }
+
+ if (!mSlots[slot].mFence.get()) {
+ mSlots[slot].mFence = fence;
+ } else {
+ sp<Fence> mergedFence = Fence::merge(
+ String8::format("%.28s:%d", mName.string(), slot),
+ mSlots[slot].mFence, fence);
+ if (!mergedFence.get()) {
+ ALOGE("failed to merge release fences");
+ // synchronization is broken, the best we can do is hope fences
+ // signal in order so the new fence will act like a union
+ mSlots[slot].mFence = fence;
+ return BAD_VALUE;
+ }
+ mSlots[slot].mFence = mergedFence;
+ }
+
+ return OK;
+}
+
+status_t GonkConsumerBase::releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer) {
+ // If consumer no longer tracks this graphicBuffer (we received a new
+ // buffer on the same slot), the buffer producer is definitely no longer
+ // tracking it.
+ if (!stillTracking(slot, graphicBuffer)) {
+ return OK;
+ }
+
+ ALOGV("releaseBufferLocked: slot=%d/%llu",
+ slot, mSlots[slot].mFrameNumber);
+ status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber, mSlots[slot].mFence);
+ if (err == GonkBufferQueue::STALE_BUFFER_SLOT) {
+ freeBufferLocked(slot);
+ }
+
+ mSlots[slot].mFence = Fence::NO_FENCE;
+
+ return err;
+}
+
+bool GonkConsumerBase::stillTracking(int slot,
+ const sp<GraphicBuffer> graphicBuffer) {
+ if (slot < 0 || slot >= GonkBufferQueue::NUM_BUFFER_SLOTS) {
+ return false;
+ }
+ return (mSlots[slot].mGraphicBuffer != NULL &&
+ mSlots[slot].mGraphicBuffer->handle == graphicBuffer->handle);
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseKK.h b/widget/gonk/nativewindow/GonkConsumerBaseKK.h
new file mode 100644
index 000000000..e198ad843
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseKK.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKCONSUMERBASE_KK_H
+#define NATIVEWINDOW_GONKCONSUMERBASE_KK_H
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <gui/IConsumerListener.h>
+
+#include "GonkBufferQueueKK.h"
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class String8;
+
+// GonkConsumerBase is a base class for GonkBufferQueue consumer end-points. It
+// handles common tasks like management of the connection to the GonkBufferQueue
+// and the buffer pool.
+class GonkConsumerBase : public virtual RefBase,
+ protected ConsumerListener {
+public:
+ struct FrameAvailableListener : public virtual RefBase {
+ // onFrameAvailable() is called each time an additional frame becomes
+ // available for consumption. This means that frames that are queued
+ // while in asynchronous mode only trigger the callback if no previous
+ // frames are pending. Frames queued while in synchronous mode always
+ // trigger the callback.
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onFrameAvailable() = 0;
+ };
+
+ virtual ~GonkConsumerBase();
+
+ // abandon frees all the buffers and puts the GonkConsumerBase into the
+ // 'abandoned' state. Once put in this state the GonkConsumerBase can never
+ // leave it. When in the 'abandoned' state, all methods of the
+ // IGraphicBufferProducer interface will fail with the NO_INIT error.
+ //
+ // Note that while calling this method causes all the buffers to be freed
+ // from the perspective of the the GonkConsumerBase, if there are additional
+ // references on the buffers (e.g. if a buffer is referenced by a client
+ // or by OpenGL ES as a texture) then those buffer will remain allocated.
+ void abandon();
+
+ // set the name of the GonkConsumerBase that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // getBufferQueue returns the GonkBufferQueue object to which this
+ // GonkConsumerBase is connected.
+ sp<GonkBufferQueue> getBufferQueue() const;
+
+ // dump writes the current state to a string. Child classes should add
+ // their state to the dump by overriding the dumpLocked method, which is
+ // called by these methods after locking the mutex.
+ void dump(String8& result) const;
+ void dump(String8& result, const char* prefix) const;
+
+ // setFrameAvailableListener sets the listener object that will be notified
+ // when a new frame becomes available.
+ void setFrameAvailableListener(const wp<FrameAvailableListener>& listener);
+
+private:
+ GonkConsumerBase(const GonkConsumerBase&);
+ void operator=(const GonkConsumerBase&);
+
+protected:
+
+ // GonkConsumerBase constructs a new GonkConsumerBase object to consume image
+ // buffers from the given GonkBufferQueue.
+ GonkConsumerBase(const sp<GonkBufferQueue>& bufferQueue, bool controlledByApp = false);
+
+ // onLastStrongRef gets called by RefBase just before the dtor of the most
+ // derived class. It is used to clean up the buffers so that GonkConsumerBase
+ // can coordinate the clean-up by calling into virtual methods implemented
+ // by the derived classes. This would not be possible from the
+ // ConsuemrBase dtor because by the time that gets called the derived
+ // classes have already been destructed.
+ //
+ // This methods should not need to be overridden by derived classes, but
+ // if they are overridden the GonkConsumerBase implementation must be called
+ // from the derived class.
+ virtual void onLastStrongRef(const void* id);
+
+ // Implementation of the GonkBufferQueue::ConsumerListener interface. These
+ // calls are used to notify the GonkConsumerBase of asynchronous events in the
+ // GonkBufferQueue. These methods should not need to be overridden by derived
+ // classes, but if they are overridden the GonkConsumerBase implementation
+ // must be called from the derived class.
+ virtual void onFrameAvailable();
+ virtual void onBuffersReleased();
+
+ // freeBufferLocked frees up the given buffer slot. If the slot has been
+ // initialized this will release the reference to the GraphicBuffer in that
+ // slot. Otherwise it has no effect.
+ //
+ // Derived classes should override this method to clean up any state they
+ // keep per slot. If it is overridden, the derived class's implementation
+ // must call GonkConsumerBase::freeBufferLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void freeBufferLocked(int slotIndex);
+
+ // abandonLocked puts the GonkBufferQueue into the abandoned state, causing
+ // all future operations on it to fail. This method rather than the public
+ // abandon method should be overridden by child classes to add abandon-
+ // time behavior.
+ //
+ // Derived classes should override this method to clean up any object
+ // state they keep (as opposed to per-slot state). If it is overridden,
+ // the derived class's implementation must call GonkConsumerBase::abandonLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void abandonLocked();
+
+ // dumpLocked dumps the current state of the GonkConsumerBase object to the
+ // result string. Each line is prefixed with the string pointed to by the
+ // prefix argument. The buffer argument points to a buffer that may be
+ // used for intermediate formatting data, and the size of that buffer is
+ // indicated by the size argument.
+ //
+ // Derived classes should override this method to dump their internal
+ // state. If this method is overridden the derived class's implementation
+ // should call GonkConsumerBase::dumpLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void dumpLocked(String8& result, const char* prefix) const;
+
+ // acquireBufferLocked fetches the next buffer from the GonkBufferQueue and
+ // updates the buffer slot for the buffer returned.
+ //
+ // Derived classes should override this method to perform any
+ // initialization that must take place the first time a buffer is assigned
+ // to a slot. If it is overridden the derived class's implementation must
+ // call GonkConsumerBase::acquireBufferLocked.
+ virtual status_t acquireBufferLocked(IGonkGraphicBufferConsumer::BufferItem *item,
+ nsecs_t presentWhen);
+
+ // releaseBufferLocked relinquishes control over a buffer, returning that
+ // control to the GonkBufferQueue.
+ //
+ // Derived classes should override this method to perform any cleanup that
+ // must take place when a buffer is released back to the GonkBufferQueue. If
+ // it is overridden the derived class's implementation must call
+ // GonkConsumerBase::releaseBufferLocked.
+ virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer);
+
+ // returns true iff the slot still has the graphicBuffer in it.
+ bool stillTracking(int slot, const sp<GraphicBuffer> graphicBuffer);
+
+ // addReleaseFence* adds the sync points associated with a fence to the set
+ // of sync points that must be reached before the buffer in the given slot
+ // may be used after the slot has been released. This should be called by
+ // derived classes each time some asynchronous work is kicked off that
+ // references the buffer.
+ status_t addReleaseFence(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence);
+ status_t addReleaseFenceLocked(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence);
+
+ // Slot contains the information and object references that
+ // GonkConsumerBase maintains about a GonkBufferQueue buffer slot.
+ struct Slot {
+ // mGraphicBuffer is the Gralloc buffer store in the slot or NULL if
+ // no Gralloc buffer is in the slot.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence which will signal when the buffer associated with
+ // this buffer slot is no longer being used by the consumer and can be
+ // overwritten. The buffer can be dequeued before the fence signals;
+ // the producer is responsible for delaying writes until it signals.
+ sp<Fence> mFence;
+
+ // the frame number of the last acquired frame for this slot
+ uint64_t mFrameNumber;
+ };
+
+ // mSlots stores the buffers that have been allocated by the GonkBufferQueue
+ // for each buffer slot. It is initialized to null pointers, and gets
+ // filled in with the result of GonkBufferQueue::acquire when the
+ // client dequeues a buffer from a
+ // slot that has not yet been used. The buffer allocated to a slot will also
+ // be replaced if the requested buffer usage or geometry differs from that
+ // of the buffer allocated to a slot.
+ Slot mSlots[GonkBufferQueue::NUM_BUFFER_SLOTS];
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume images buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the abandon
+ // method. A GonkBufferQueue that has been abandoned will return the NO_INIT
+ // error from all IGonkConsumerBase methods capable of returning an error.
+ bool mAbandoned;
+
+ // mName is a string used to identify the GonkConsumerBase in log messages.
+ // It can be set by the setName method.
+ String8 mName;
+
+ // mFrameAvailableListener is the listener object that will be called when a
+ // new frame becomes available. If it is not NULL it will be called from
+ // queueBuffer.
+ wp<FrameAvailableListener> mFrameAvailableListener;
+
+ // The GonkConsumerBase has-a GonkBufferQueue and is responsible for creating this object
+ // if none is supplied
+ sp<GonkBufferQueue> mConsumer;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkConsumerBase objects. It must be locked whenever the
+ // member variables are accessed or when any of the *Locked methods are
+ // called.
+ //
+ // This mutex is intended to be locked by derived classes.
+ mutable Mutex mMutex;
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_GONKCONSUMERBASE_H
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseLL.cpp b/widget/gonk/nativewindow/GonkConsumerBaseLL.cpp
new file mode 100644
index 000000000..5b1166b57
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseLL.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#define LOG_TAG "GonkConsumerBase"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <hardware/hardware.h>
+
+#include <gui/IGraphicBufferAlloc.h>
+
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include "GonkConsumerBaseLL.h"
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+GonkConsumerBase::GonkConsumerBase(const sp<IGonkGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :
+ mAbandoned(false),
+ mConsumer(bufferQueue) {
+ // Choose a name using the PID and a process-unique ID.
+ mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ // Note that we can't create an sp<...>(this) in a ctor that will not keep a
+ // reference once the ctor ends, as that would cause the refcount of 'this'
+ // dropping to 0 at the end of the ctor. Since all we need is a wp<...>
+ // that's what we create.
+ wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
+ sp<IConsumerListener> proxy = new GonkBufferQueue::ProxyConsumerListener(listener);
+
+ status_t err = mConsumer->consumerConnect(proxy, controlledByApp);
+ if (err != NO_ERROR) {
+ ALOGE("GonkConsumerBase: error connecting to GonkBufferQueue: %s (%d)",
+ strerror(-err), err);
+ } else {
+ mConsumer->setConsumerName(mName);
+ }
+}
+
+GonkConsumerBase::~GonkConsumerBase() {
+ ALOGV("~GonkConsumerBase");
+ Mutex::Autolock lock(mMutex);
+
+ // Verify that abandon() has been called before we get here. This should
+ // be done by GonkConsumerBase::onLastStrongRef(), but it's possible for a
+ // derived class to override that method and not call
+ // GonkConsumerBase::onLastStrongRef().
+ LOG_ALWAYS_FATAL_IF(!mAbandoned, "[%s] ~GonkConsumerBase was called, but the "
+ "consumer is not abandoned!", mName.string());
+}
+
+void GonkConsumerBase::onLastStrongRef(const void* id __attribute__((unused))) {
+ abandon();
+}
+
+void GonkConsumerBase::freeBufferLocked(int slotIndex) {
+ ALOGV("freeBufferLocked: slotIndex=%d", slotIndex);
+ mSlots[slotIndex].mGraphicBuffer = 0;
+ mSlots[slotIndex].mFence = Fence::NO_FENCE;
+ mSlots[slotIndex].mFrameNumber = 0;
+}
+
+#if ANDROID_VERSION == 21
+void GonkConsumerBase::onFrameAvailable() {
+#else
+void GonkConsumerBase::onFrameAvailable(const ::android::BufferItem& item) {
+#endif
+ ALOGV("onFrameAvailable");
+
+ sp<FrameAvailableListener> listener;
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+ listener = mFrameAvailableListener.promote();
+ }
+
+ if (listener != NULL) {
+ ALOGV("actually calling onFrameAvailable");
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkConsumerBase::onBuffersReleased() {
+ Mutex::Autolock lock(mMutex);
+
+ ALOGV("onBuffersReleased");
+
+ if (mAbandoned) {
+ // Nothing to do if we're already abandoned.
+ return;
+ }
+
+ uint64_t mask = 0;
+ mConsumer->getReleasedBuffers(&mask);
+ for (int i = 0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if (mask & (1ULL << i)) {
+ freeBufferLocked(i);
+ }
+ }
+}
+
+void GonkConsumerBase::onSidebandStreamChanged() {
+}
+
+void GonkConsumerBase::abandon() {
+ ALOGV("abandon");
+ Mutex::Autolock lock(mMutex);
+
+ if (!mAbandoned) {
+ abandonLocked();
+ mAbandoned = true;
+ }
+}
+
+void GonkConsumerBase::abandonLocked() {
+ ALOGV("abandonLocked");
+ for (int i =0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ freeBufferLocked(i);
+ }
+ // disconnect from the BufferQueue
+ mConsumer->consumerDisconnect();
+ mConsumer.clear();
+}
+
+void GonkConsumerBase::setFrameAvailableListener(
+ const wp<FrameAvailableListener>& listener) {
+ ALOGV("setFrameAvailableListener");
+ Mutex::Autolock lock(mMutex);
+ mFrameAvailableListener = listener;
+}
+
+void GonkConsumerBase::dump(String8& result) const {
+ dump(result, "");
+}
+
+void GonkConsumerBase::dump(String8& result, const char* prefix) const {
+ Mutex::Autolock _l(mMutex);
+ dumpLocked(result, prefix);
+}
+
+void GonkConsumerBase::dumpLocked(String8& result, const char* prefix) const {
+ result.appendFormat("%smAbandoned=%d\n", prefix, int(mAbandoned));
+
+ if (!mAbandoned) {
+ mConsumer->dumpToString(result, prefix);
+ }
+}
+
+status_t GonkConsumerBase::acquireBufferLocked(GonkBufferQueue::BufferItem *item,
+ nsecs_t presentWhen) {
+ status_t err = mConsumer->acquireBuffer(item, presentWhen);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ if (item->mGraphicBuffer != NULL) {
+ mSlots[item->mBuf].mGraphicBuffer = item->mGraphicBuffer;
+ }
+
+ mSlots[item->mBuf].mFrameNumber = item->mFrameNumber;
+ mSlots[item->mBuf].mFence = item->mFence;
+
+ ALOGV("acquireBufferLocked: -> slot=%d/%" PRIu64,
+ item->mBuf, item->mFrameNumber);
+
+ return OK;
+}
+
+status_t GonkConsumerBase::addReleaseFence(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
+ Mutex::Autolock lock(mMutex);
+ return addReleaseFenceLocked(slot, graphicBuffer, fence);
+}
+
+status_t GonkConsumerBase::addReleaseFenceLocked(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
+ ALOGV("addReleaseFenceLocked: slot=%d", slot);
+
+ // If consumer no longer tracks this graphicBuffer, we can safely
+ // drop this fence, as it will never be received by the producer.
+ if (!stillTracking(slot, graphicBuffer)) {
+ return OK;
+ }
+
+ if (!mSlots[slot].mFence.get()) {
+ mSlots[slot].mFence = fence;
+ } else {
+ sp<Fence> mergedFence = Fence::merge(
+ String8::format("%.28s:%d", mName.string(), slot),
+ mSlots[slot].mFence, fence);
+ if (!mergedFence.get()) {
+ ALOGE("failed to merge release fences");
+ // synchronization is broken, the best we can do is hope fences
+ // signal in order so the new fence will act like a union
+ mSlots[slot].mFence = fence;
+ return BAD_VALUE;
+ }
+ mSlots[slot].mFence = mergedFence;
+ }
+
+ return OK;
+}
+
+status_t GonkConsumerBase::releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer) {
+ // If consumer no longer tracks this graphicBuffer (we received a new
+ // buffer on the same slot), the buffer producer is definitely no longer
+ // tracking it.
+ if (!stillTracking(slot, graphicBuffer)) {
+ return OK;
+ }
+
+ ALOGV("releaseBufferLocked: slot=%d/%" PRIu64,
+ slot, mSlots[slot].mFrameNumber);
+ status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber, mSlots[slot].mFence);
+ if (err == IGonkGraphicBufferConsumer::STALE_BUFFER_SLOT) {
+ freeBufferLocked(slot);
+ }
+
+ mSlots[slot].mFence = Fence::NO_FENCE;
+
+ return err;
+}
+
+bool GonkConsumerBase::stillTracking(int slot,
+ const sp<GraphicBuffer> graphicBuffer) {
+ if (slot < 0 || slot >= GonkBufferQueue::NUM_BUFFER_SLOTS) {
+ return false;
+ }
+ return (mSlots[slot].mGraphicBuffer != NULL &&
+ mSlots[slot].mGraphicBuffer->handle == graphicBuffer->handle);
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseLL.h b/widget/gonk/nativewindow/GonkConsumerBaseLL.h
new file mode 100644
index 000000000..0b2c2d166
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseLL.h
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKCONSUMERBASE_LL_H
+#define NATIVEWINDOW_GONKCONSUMERBASE_LL_H
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <gui/IConsumerListener.h>
+
+#include "GonkBufferQueueLL.h"
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class String8;
+
+// GonkConsumerBase is a base class for GonkBufferQueue consumer end-points. It
+// handles common tasks like management of the connection to the GonkBufferQueue
+// and the buffer pool.
+class GonkConsumerBase : public virtual RefBase,
+ protected ConsumerListener {
+public:
+ struct FrameAvailableListener : public virtual RefBase {
+ // onFrameAvailable() is called each time an additional frame becomes
+ // available for consumption. This means that frames that are queued
+ // while in asynchronous mode only trigger the callback if no previous
+ // frames are pending. Frames queued while in synchronous mode always
+ // trigger the callback.
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onFrameAvailable() = 0;
+ };
+
+ virtual ~GonkConsumerBase();
+
+ // abandon frees all the buffers and puts the GonkConsumerBase into the
+ // 'abandoned' state. Once put in this state the GonkConsumerBase can never
+ // leave it. When in the 'abandoned' state, all methods of the
+ // IGraphicBufferProducer interface will fail with the NO_INIT error.
+ //
+ // Note that while calling this method causes all the buffers to be freed
+ // from the perspective of the the GonkConsumerBase, if there are additional
+ // references on the buffers (e.g. if a buffer is referenced by a client
+ // or by OpenGL ES as a texture) then those buffer will remain allocated.
+ void abandon();
+
+ // set the name of the GonkConsumerBase that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // dump writes the current state to a string. Child classes should add
+ // their state to the dump by overriding the dumpLocked method, which is
+ // called by these methods after locking the mutex.
+ void dump(String8& result) const;
+ void dump(String8& result, const char* prefix) const;
+
+ // setFrameAvailableListener sets the listener object that will be notified
+ // when a new frame becomes available.
+ void setFrameAvailableListener(const wp<FrameAvailableListener>& listener);
+
+private:
+ GonkConsumerBase(const GonkConsumerBase&);
+ void operator=(const GonkConsumerBase&);
+
+protected:
+ // GonkConsumerBase constructs a new GonkConsumerBase object to consume image
+ // buffers from the given IGonkGraphicBufferConsumer.
+ // The controlledByApp flag indicates that this consumer is under the application's
+ // control.
+ GonkConsumerBase(const sp<IGonkGraphicBufferConsumer>& consumer, bool controlledByApp = false);
+
+ // onLastStrongRef gets called by RefBase just before the dtor of the most
+ // derived class. It is used to clean up the buffers so that GonkConsumerBase
+ // can coordinate the clean-up by calling into virtual methods implemented
+ // by the derived classes. This would not be possible from the
+ // ConsuemrBase dtor because by the time that gets called the derived
+ // classes have already been destructed.
+ //
+ // This methods should not need to be overridden by derived classes, but
+ // if they are overridden the GonkConsumerBase implementation must be called
+ // from the derived class.
+ virtual void onLastStrongRef(const void* id);
+
+ // Implementation of the IConsumerListener interface. These
+ // calls are used to notify the GonkConsumerBase of asynchronous events in the
+ // GonkBufferQueue. The onFrameAvailable and onBuffersReleased methods should
+ // not need to be overridden by derived classes, but if they are overridden
+ // the GonkConsumerBase implementation must be called from the derived class.
+ // The GonkConsumerBase version of onSidebandStreamChanged does nothing and can
+ // be overriden by derived classes if they want the notification.
+#if ANDROID_VERSION == 21
+ virtual void onFrameAvailable();
+#else
+ virtual void onFrameAvailable(const ::android::BufferItem& item);
+ virtual void onFrameReplaced(const ::android::BufferItem& item) {};
+#endif
+ virtual void onBuffersReleased();
+ virtual void onSidebandStreamChanged();
+
+ // freeBufferLocked frees up the given buffer slot. If the slot has been
+ // initialized this will release the reference to the GraphicBuffer in that
+ // slot. Otherwise it has no effect.
+ //
+ // Derived classes should override this method to clean up any state they
+ // keep per slot. If it is overridden, the derived class's implementation
+ // must call GonkConsumerBase::freeBufferLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void freeBufferLocked(int slotIndex);
+
+ // abandonLocked puts the GonkBufferQueue into the abandoned state, causing
+ // all future operations on it to fail. This method rather than the public
+ // abandon method should be overridden by child classes to add abandon-
+ // time behavior.
+ //
+ // Derived classes should override this method to clean up any object
+ // state they keep (as opposed to per-slot state). If it is overridden,
+ // the derived class's implementation must call GonkConsumerBase::abandonLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void abandonLocked();
+
+ // dumpLocked dumps the current state of the GonkConsumerBase object to the
+ // result string. Each line is prefixed with the string pointed to by the
+ // prefix argument. The buffer argument points to a buffer that may be
+ // used for intermediate formatting data, and the size of that buffer is
+ // indicated by the size argument.
+ //
+ // Derived classes should override this method to dump their internal
+ // state. If this method is overridden the derived class's implementation
+ // should call GonkConsumerBase::dumpLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void dumpLocked(String8& result, const char* prefix) const;
+
+ // acquireBufferLocked fetches the next buffer from the GonkBufferQueue and
+ // updates the buffer slot for the buffer returned.
+ //
+ // Derived classes should override this method to perform any
+ // initialization that must take place the first time a buffer is assigned
+ // to a slot. If it is overridden the derived class's implementation must
+ // call GonkConsumerBase::acquireBufferLocked.
+ virtual status_t acquireBufferLocked(IGonkGraphicBufferConsumer::BufferItem *item,
+ nsecs_t presentWhen);
+
+ // releaseBufferLocked relinquishes control over a buffer, returning that
+ // control to the GonkBufferQueue.
+ //
+ // Derived classes should override this method to perform any cleanup that
+ // must take place when a buffer is released back to the GonkBufferQueue. If
+ // it is overridden the derived class's implementation must call
+ // GonkConsumerBase::releaseBufferLocked.
+ virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer);
+
+ // returns true iff the slot still has the graphicBuffer in it.
+ bool stillTracking(int slot, const sp<GraphicBuffer> graphicBuffer);
+
+ // addReleaseFence* adds the sync points associated with a fence to the set
+ // of sync points that must be reached before the buffer in the given slot
+ // may be used after the slot has been released. This should be called by
+ // derived classes each time some asynchronous work is kicked off that
+ // references the buffer.
+ status_t addReleaseFence(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence);
+ status_t addReleaseFenceLocked(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence);
+
+ // Slot contains the information and object references that
+ // GonkConsumerBase maintains about a GonkBufferQueue buffer slot.
+ struct Slot {
+ // mGraphicBuffer is the Gralloc buffer store in the slot or NULL if
+ // no Gralloc buffer is in the slot.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence which will signal when the buffer associated with
+ // this buffer slot is no longer being used by the consumer and can be
+ // overwritten. The buffer can be dequeued before the fence signals;
+ // the producer is responsible for delaying writes until it signals.
+ sp<Fence> mFence;
+
+ // the frame number of the last acquired frame for this slot
+ uint64_t mFrameNumber;
+ };
+
+ // mSlots stores the buffers that have been allocated by the GonkBufferQueue
+ // for each buffer slot. It is initialized to null pointers, and gets
+ // filled in with the result of GonkBufferQueue::acquire when the
+ // client dequeues a buffer from a
+ // slot that has not yet been used. The buffer allocated to a slot will also
+ // be replaced if the requested buffer usage or geometry differs from that
+ // of the buffer allocated to a slot.
+ Slot mSlots[GonkBufferQueue::NUM_BUFFER_SLOTS];
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume images buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the abandon
+ // method. A GonkBufferQueue that has been abandoned will return the NO_INIT
+ // error from all IConsumerBase methods capable of returning an error.
+ bool mAbandoned;
+
+ // mName is a string used to identify the GonkConsumerBase in log messages.
+ // It can be set by the setName method.
+ String8 mName;
+
+ // mFrameAvailableListener is the listener object that will be called when a
+ // new frame becomes available. If it is not NULL it will be called from
+ // queueBuffer.
+ wp<FrameAvailableListener> mFrameAvailableListener;
+
+ // The GonkConsumerBase has-a GonkBufferQueue and is responsible for creating this object
+ // if none is supplied
+ sp<IGonkGraphicBufferConsumer> mConsumer;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkConsumerBase objects. It must be locked whenever the
+ // member variables are accessed or when any of the *Locked methods are
+ // called.
+ //
+ // This mutex is intended to be locked by derived classes.
+ mutable Mutex mMutex;
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_GONKCONSUMERBASE_LL_H
diff --git a/widget/gonk/nativewindow/GonkNativeWindow.h b/widget/gonk/nativewindow/GonkNativeWindow.h
new file mode 100644
index 000000000..61b6780b8
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindow.h
@@ -0,0 +1,22 @@
+/* Copyright 2013 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
+# include "GonkNativeWindowLL.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19
+# include "GonkNativeWindowKK.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+# include "GonkNativeWindowJB.h"
+#endif
diff --git a/widget/gonk/nativewindow/GonkNativeWindowJB.cpp b/widget/gonk/nativewindow/GonkNativeWindowJB.cpp
new file mode 100644
index 000000000..e38642009
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowJB.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "GonkNativeWindow"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Log.h>
+
+#include "GonkNativeWindowJB.h"
+#include "GrallocImages.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/RefPtr.h"
+
+#define BI_LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define BI_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define BI_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define BI_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define BI_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+namespace android {
+
+GonkNativeWindow::GonkNativeWindow(int bufferCount) :
+ GonkConsumerBase(new GonkBufferQueue(true) ),
+ mNewFrameCallback(nullptr)
+{
+ mBufferQueue->setMaxAcquiredBufferCount(bufferCount);
+}
+
+GonkNativeWindow::~GonkNativeWindow() {
+}
+
+void GonkNativeWindow::setName(const String8& name) {
+ Mutex::Autolock _l(mMutex);
+ mName = name;
+ mBufferQueue->setConsumerName(name);
+}
+#if ANDROID_VERSION >= 18
+status_t GonkNativeWindow::acquireBuffer(BufferItem *item, bool waitForFence) {
+ status_t err;
+
+ if (!item) return BAD_VALUE;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = acquireBufferLocked(item);
+ if (err != OK) {
+ if (err != NO_BUFFER_AVAILABLE) {
+ BI_LOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
+ }
+ return err;
+ }
+
+ if (waitForFence) {
+ err = item->mFence->waitForever("GonkNativeWindow::acquireBuffer");
+ if (err != OK) {
+ BI_LOGE("Failed to wait for fence of acquired buffer: %s (%d)",
+ strerror(-err), err);
+ return err;
+ }
+ }
+
+ item->mGraphicBuffer = mSlots[item->mBuf].mGraphicBuffer;
+
+ return OK;
+}
+
+status_t GonkNativeWindow::releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence) {
+ status_t err;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = addReleaseFenceLocked(item.mBuf, releaseFence);
+
+ err = releaseBufferLocked(item.mBuf);
+ if (err != OK) {
+ BI_LOGE("Failed to release buffer: %s (%d)",
+ strerror(-err), err);
+ }
+ return err;
+}
+#endif
+
+status_t GonkNativeWindow::setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Mutex::Autolock _l(mMutex);
+ return mBufferQueue->setDefaultBufferSize(w, h);
+}
+
+status_t GonkNativeWindow::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock _l(mMutex);
+ return mBufferQueue->setDefaultBufferFormat(defaultFormat);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getCurrentBuffer() {
+ Mutex::Autolock _l(mMutex);
+ GonkBufferQueue::BufferItem item;
+
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ status_t err = acquireBufferLocked(&item);
+ if (err != NO_ERROR) {
+ return NULL;
+ }
+
+ RefPtr<TextureClient> textureClient =
+ mBufferQueue->getTextureClientFromBuffer(item.mGraphicBuffer.get());
+ if (!textureClient) {
+ return NULL;
+ }
+ textureClient->SetRecycleCallback(GonkNativeWindow::RecycleCallback, this);
+ return textureClient.forget();
+}
+
+/* static */ void
+GonkNativeWindow::RecycleCallback(TextureClient* client, void* closure) {
+ GonkNativeWindow* nativeWindow =
+ static_cast<GonkNativeWindow*>(closure);
+
+ MOZ_ASSERT(client && !client->IsDead());
+ client->ClearRecycleCallback();
+ nativeWindow->returnBuffer(client);
+}
+
+void GonkNativeWindow::returnBuffer(TextureClient* client) {
+ BI_LOGD("GonkNativeWindow::returnBuffer");
+ Mutex::Autolock lock(mMutex);
+
+ int index = mBufferQueue->getSlotFromTextureClientLocked(client);
+ if (index < 0) {
+ }
+
+ FenceHandle handle = client->GetAndResetReleaseFenceHandle();
+ RefPtr<FenceHandle::FdObj> fdObj = handle.GetAndResetFdObj();
+ sp<Fence> fence = new Fence(fdObj->GetAndResetFd());
+
+ addReleaseFenceLocked(index, fence);
+
+ releaseBufferLocked(index);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getTextureClientFromBuffer(ANativeWindowBuffer* buffer) {
+ Mutex::Autolock lock(mMutex);
+ return mBufferQueue->getTextureClientFromBuffer(buffer);
+}
+
+void GonkNativeWindow::setNewFrameCallback(
+ GonkNativeWindowNewFrameCallback* callback) {
+ BI_LOGD("setNewFrameCallback");
+ Mutex::Autolock lock(mMutex);
+ mNewFrameCallback = callback;
+}
+
+void GonkNativeWindow::onFrameAvailable() {
+ GonkConsumerBase::onFrameAvailable();
+
+ if (mNewFrameCallback) {
+ mNewFrameCallback->OnNewFrame();
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkNativeWindowJB.h b/widget/gonk/nativewindow/GonkNativeWindowJB.h
new file mode 100644
index 000000000..e63a7527d
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowJB.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKNATIVEWINDOW_JB_H
+#define NATIVEWINDOW_GONKNATIVEWINDOW_JB_H
+
+#include <ui/GraphicBuffer.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "GonkConsumerBaseJB.h"
+#include "GrallocImages.h"
+#include "mozilla/layers/LayersSurfaces.h"
+
+namespace mozilla {
+namespace layers {
+ class PGrallocBufferChild;
+}
+}
+
+namespace android {
+
+// The user of GonkNativeWindow who wants to receive notification of
+// new frames should implement this interface.
+class GonkNativeWindowNewFrameCallback {
+public:
+ virtual void OnNewFrame() = 0;
+};
+
+/**
+ * GonkNativeWindow is a GonkBufferQueue consumer endpoint that allows clients
+ * access to the whole BufferItem entry from GonkBufferQueue. Multiple buffers may
+ * be acquired at once, to be used concurrently by the client. This consumer can
+ * operate either in synchronous or asynchronous mode.
+ */
+class GonkNativeWindow: public GonkConsumerBase
+{
+ typedef mozilla::layers::TextureClient TextureClient;
+ public:
+ typedef GonkConsumerBase::FrameAvailableListener FrameAvailableListener;
+
+ typedef GonkBufferQueue::BufferItem BufferItem;
+
+ enum { INVALID_BUFFER_SLOT = GonkBufferQueue::INVALID_BUFFER_SLOT };
+ enum { NO_BUFFER_AVAILABLE = GonkBufferQueue::NO_BUFFER_AVAILABLE };
+
+ // Create a new buffer item consumer. The consumerUsage parameter determines
+ // the consumer usage flags passed to the graphics allocator. The
+ // bufferCount parameter specifies how many buffers can be locked for user
+ // access at the same time.
+ GonkNativeWindow(int bufferCount = GonkBufferQueue::MIN_UNDEQUEUED_BUFFERS);
+
+ virtual ~GonkNativeWindow();
+
+ // set the name of the GonkNativeWindow that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // Gets the next graphics buffer from the producer, filling out the
+ // passed-in BufferItem structure. Returns NO_BUFFER_AVAILABLE if the queue
+ // of buffers is empty, and INVALID_OPERATION if the maximum number of
+ // buffers is already acquired.
+ //
+ // Only a fixed number of buffers can be acquired at a time, determined by
+ // the construction-time bufferCount parameter. If INVALID_OPERATION is
+ // returned by acquireBuffer, then old buffers must be returned to the
+ // queue by calling releaseBuffer before more buffers can be acquired.
+ //
+ // If waitForFence is true, and the acquired BufferItem has a valid fence object,
+ // acquireBuffer will wait on the fence with no timeout before returning.
+#if ANDROID_VERSION >= 18
+ status_t acquireBuffer(BufferItem *item, bool waitForFence = true);
+#endif
+ // Returns an acquired buffer to the queue, allowing it to be reused. Since
+ // only a fixed number of buffers may be acquired at a time, old buffers
+ // must be released by calling releaseBuffer to ensure new buffers can be
+ // acquired by acquireBuffer. Once a BufferItem is released, the caller must
+ // not access any members of the BufferItem, and should immediately remove
+ // all of its references to the BufferItem itself.
+#if ANDROID_VERSION >= 18
+ status_t releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence = Fence::NO_FENCE);
+#endif
+
+ sp<IGraphicBufferProducer> getProducerInterface() const { return getBufferQueue(); }
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // requestBuffers when a with and height of zero is requested.
+ status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultBufferFormat allows the BufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer
+ status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // Get next frame from the queue, caller owns the returned buffer.
+ already_AddRefed<TextureClient> getCurrentBuffer();
+
+ // Return the buffer to the queue and mark it as FREE. After that
+ // the buffer is useable again for the decoder.
+ void returnBuffer(TextureClient* client);
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ void setNewFrameCallback(GonkNativeWindowNewFrameCallback* callback);
+
+ static void RecycleCallback(TextureClient* client, void* closure);
+
+protected:
+ virtual void onFrameAvailable();
+
+private:
+ GonkNativeWindowNewFrameCallback* mNewFrameCallback;
+};
+
+} // namespace android
+
+#endif // NATIVEWINDOW_GONKNATIVEWINDOW_JB_H
diff --git a/widget/gonk/nativewindow/GonkNativeWindowKK.cpp b/widget/gonk/nativewindow/GonkNativeWindowKK.cpp
new file mode 100644
index 000000000..cf34d6539
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowKK.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "GonkNativeWindow"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Log.h>
+
+#include "GonkNativeWindowKK.h"
+#include "GrallocImages.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+namespace android {
+
+GonkNativeWindow::GonkNativeWindow(int bufferCount) :
+ GonkConsumerBase(new GonkBufferQueue(true), false),
+ mNewFrameCallback(nullptr)
+{
+ mConsumer->setMaxAcquiredBufferCount(bufferCount);
+}
+
+GonkNativeWindow::GonkNativeWindow(const sp<GonkBufferQueue>& bq,
+ uint32_t consumerUsage, int bufferCount, bool controlledByApp) :
+ GonkConsumerBase(bq, controlledByApp)
+{
+ mConsumer->setConsumerUsageBits(consumerUsage);
+ mConsumer->setMaxAcquiredBufferCount(bufferCount);
+}
+
+GonkNativeWindow::~GonkNativeWindow() {
+}
+
+void GonkNativeWindow::setName(const String8& name) {
+ Mutex::Autolock _l(mMutex);
+ mName = name;
+ mConsumer->setConsumerName(name);
+}
+
+status_t GonkNativeWindow::acquireBuffer(BufferItem *item,
+ nsecs_t presentWhen, bool waitForFence) {
+ status_t err;
+
+ if (!item) return BAD_VALUE;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = acquireBufferLocked(item, presentWhen);
+ if (err != OK) {
+ if (err != NO_BUFFER_AVAILABLE) {
+ ALOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
+ }
+ return err;
+ }
+
+ if (waitForFence) {
+ err = item->mFence->waitForever("GonkNativeWindow::acquireBuffer");
+ if (err != OK) {
+ ALOGE("Failed to wait for fence of acquired buffer: %s (%d)",
+ strerror(-err), err);
+ return err;
+ }
+ }
+
+ item->mGraphicBuffer = mSlots[item->mBuf].mGraphicBuffer;
+
+ return OK;
+}
+
+status_t GonkNativeWindow::releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence) {
+ status_t err;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = addReleaseFenceLocked(item.mBuf, item.mGraphicBuffer, releaseFence);
+
+ err = releaseBufferLocked(item.mBuf, item.mGraphicBuffer);
+ if (err != OK) {
+ ALOGE("Failed to release buffer: %s (%d)",
+ strerror(-err), err);
+ }
+ return err;
+}
+
+status_t GonkNativeWindow::setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Mutex::Autolock _l(mMutex);
+ return mConsumer->setDefaultBufferSize(w, h);
+}
+
+status_t GonkNativeWindow::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock _l(mMutex);
+ return mConsumer->setDefaultBufferFormat(defaultFormat);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getCurrentBuffer() {
+ Mutex::Autolock _l(mMutex);
+ BufferItem item;
+
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ status_t err = acquireBufferLocked(&item, 0); //???
+ if (err != NO_ERROR) {
+ return NULL;
+ }
+
+ RefPtr<TextureClient> textureClient =
+ mConsumer->getTextureClientFromBuffer(item.mGraphicBuffer.get());
+ if (!textureClient) {
+ return NULL;
+ }
+ textureClient->SetRecycleCallback(GonkNativeWindow::RecycleCallback, this);
+ return textureClient.forget();
+}
+
+/* static */ void
+GonkNativeWindow::RecycleCallback(TextureClient* client, void* closure) {
+ GonkNativeWindow* nativeWindow =
+ static_cast<GonkNativeWindow*>(closure);
+
+ MOZ_ASSERT(client && !client->IsDead());
+ client->ClearRecycleCallback();
+ nativeWindow->returnBuffer(client);
+}
+
+void GonkNativeWindow::returnBuffer(TextureClient* client) {
+ ALOGD("GonkNativeWindow::returnBuffer");
+ Mutex::Autolock lock(mMutex);
+
+ int index = mConsumer->getSlotFromTextureClientLocked(client);
+ if (index < 0) {
+ }
+
+ FenceHandle handle = client->GetAndResetReleaseFenceHandle();
+ RefPtr<FenceHandle::FdObj> fdObj = handle.GetAndResetFdObj();
+ sp<Fence> fence = new Fence(fdObj->GetAndResetFd());
+
+ addReleaseFenceLocked(index,
+ mSlots[index].mGraphicBuffer,
+ fence);
+
+ releaseBufferLocked(index, mSlots[index].mGraphicBuffer);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getTextureClientFromBuffer(ANativeWindowBuffer* buffer) {
+ Mutex::Autolock lock(mMutex);
+ return mConsumer->getTextureClientFromBuffer(buffer);
+}
+
+void GonkNativeWindow::setNewFrameCallback(
+ GonkNativeWindowNewFrameCallback* callback) {
+ ALOGD("setNewFrameCallback");
+ Mutex::Autolock lock(mMutex);
+ mNewFrameCallback = callback;
+}
+
+void GonkNativeWindow::onFrameAvailable() {
+ GonkConsumerBase::onFrameAvailable();
+
+ if (mNewFrameCallback) {
+ mNewFrameCallback->OnNewFrame();
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkNativeWindowKK.h b/widget/gonk/nativewindow/GonkNativeWindowKK.h
new file mode 100644
index 000000000..e36788b41
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowKK.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKNATIVEWINDOW_KK_H
+#define NATIVEWINDOW_GONKNATIVEWINDOW_KK_H
+
+#include <ui/GraphicBuffer.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "GonkConsumerBaseKK.h"
+#include "GrallocImages.h"
+#include "IGonkGraphicBufferConsumer.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/LayersSurfaces.h"
+
+namespace mozilla {
+namespace layers {
+ class PGrallocBufferChild;
+}
+}
+
+namespace android {
+
+// The user of GonkNativeWindow who wants to receive notification of
+// new frames should implement this interface.
+class GonkNativeWindowNewFrameCallback {
+public:
+ virtual void OnNewFrame() = 0;
+};
+
+/**
+ * GonkNativeWindow is a GonkBufferQueue consumer endpoint that allows clients
+ * access to the whole BufferItem entry from GonkBufferQueue. Multiple buffers may
+ * be acquired at once, to be used concurrently by the client. This consumer can
+ * operate either in synchronous or asynchronous mode.
+ */
+class GonkNativeWindow: public GonkConsumerBase
+{
+ typedef mozilla::layers::TextureClient TextureClient;
+ public:
+ typedef GonkConsumerBase::FrameAvailableListener FrameAvailableListener;
+ typedef IGonkGraphicBufferConsumer::BufferItem BufferItem;
+
+ enum { INVALID_BUFFER_SLOT = GonkBufferQueue::INVALID_BUFFER_SLOT };
+ enum { NO_BUFFER_AVAILABLE = GonkBufferQueue::NO_BUFFER_AVAILABLE };
+
+ // Create a new buffer item consumer. The consumerUsage parameter determines
+ // the consumer usage flags passed to the graphics allocator. The
+ // bufferCount parameter specifies how many buffers can be locked for user
+ // access at the same time.
+ // controlledByApp tells whether this consumer is controlled by the
+ // application.
+ GonkNativeWindow(int bufferCount = GonkBufferQueue::MIN_UNDEQUEUED_BUFFERS);
+ GonkNativeWindow(const sp<GonkBufferQueue>& bq, uint32_t consumerUsage,
+ int bufferCount = GonkBufferQueue::MIN_UNDEQUEUED_BUFFERS,
+ bool controlledByApp = false);
+
+ virtual ~GonkNativeWindow();
+
+ // set the name of the GonkNativeWindow that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // Gets the next graphics buffer from the producer, filling out the
+ // passed-in BufferItem structure. Returns NO_BUFFER_AVAILABLE if the queue
+ // of buffers is empty, and INVALID_OPERATION if the maximum number of
+ // buffers is already acquired.
+ //
+ // Only a fixed number of buffers can be acquired at a time, determined by
+ // the construction-time bufferCount parameter. If INVALID_OPERATION is
+ // returned by acquireBuffer, then old buffers must be returned to the
+ // queue by calling releaseBuffer before more buffers can be acquired.
+ //
+ // If waitForFence is true, and the acquired BufferItem has a valid fence object,
+ // acquireBuffer will wait on the fence with no timeout before returning.
+ status_t acquireBuffer(BufferItem *item, nsecs_t presentWhen,
+ bool waitForFence = true);
+
+ // Returns an acquired buffer to the queue, allowing it to be reused. Since
+ // only a fixed number of buffers may be acquired at a time, old buffers
+ // must be released by calling releaseBuffer to ensure new buffers can be
+ // acquired by acquireBuffer. Once a BufferItem is released, the caller must
+ // not access any members of the BufferItem, and should immediately remove
+ // all of its references to the BufferItem itself.
+ status_t releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence = Fence::NO_FENCE);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // requestBuffers when a with and height of zero is requested.
+ status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultBufferFormat allows the BufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer
+ status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // Get next frame from the queue, caller owns the returned buffer.
+ already_AddRefed<TextureClient> getCurrentBuffer();
+
+ // Return the buffer to the queue and mark it as FREE. After that
+ // the buffer is useable again for the decoder.
+ void returnBuffer(TextureClient* client);
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ void setNewFrameCallback(GonkNativeWindowNewFrameCallback* callback);
+
+ static void RecycleCallback(TextureClient* client, void* closure);
+
+protected:
+ virtual void onFrameAvailable();
+
+private:
+ GonkNativeWindowNewFrameCallback* mNewFrameCallback;
+};
+
+} // namespace android
+
+#endif // NATIVEWINDOW_GONKNATIVEWINDOW_JB_H
diff --git a/widget/gonk/nativewindow/GonkNativeWindowLL.cpp b/widget/gonk/nativewindow/GonkNativeWindowLL.cpp
new file mode 100644
index 000000000..48644a22f
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowLL.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "GonkNativeWindow"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Log.h>
+
+#include "GonkNativeWindowLL.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+namespace android {
+
+GonkNativeWindow::GonkNativeWindow(
+ const sp<IGonkGraphicBufferConsumer>& consumer, int bufferCount) :
+ GonkConsumerBase(consumer, false),
+ mNewFrameCallback(nullptr)
+{
+ if (bufferCount != DEFAULT_MAX_BUFFERS) {
+ status_t err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
+ LOG_ALWAYS_FATAL_IF(err != OK,
+ "Failed to set max acquired buffer count to %d", bufferCount);
+ }
+}
+
+GonkNativeWindow::GonkNativeWindow(
+ const sp<IGonkGraphicBufferConsumer>& consumer, uint32_t consumerUsage,
+ int bufferCount, bool controlledByApp) :
+ GonkConsumerBase(consumer, controlledByApp)
+{
+ status_t err = mConsumer->setConsumerUsageBits(consumerUsage);
+ LOG_ALWAYS_FATAL_IF(err != OK,
+ "Failed to set consumer usage bits to %#x", consumerUsage);
+ if (bufferCount != DEFAULT_MAX_BUFFERS) {
+ err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
+ LOG_ALWAYS_FATAL_IF(err != OK,
+ "Failed to set max acquired buffer count to %d", bufferCount);
+ }
+}
+
+GonkNativeWindow::~GonkNativeWindow() {
+}
+
+void GonkNativeWindow::setName(const String8& name) {
+ Mutex::Autolock _l(mMutex);
+ mName = name;
+ mConsumer->setConsumerName(name);
+}
+
+status_t GonkNativeWindow::acquireBuffer(BufferItem *item,
+ nsecs_t presentWhen, bool waitForFence) {
+ status_t err;
+
+ if (!item) return BAD_VALUE;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = acquireBufferLocked(item, presentWhen);
+ if (err != OK) {
+ if (err != NO_BUFFER_AVAILABLE) {
+ ALOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
+ }
+ return err;
+ }
+
+ if (waitForFence) {
+ err = item->mFence->waitForever("GonkNativeWindow::acquireBuffer");
+ if (err != OK) {
+ ALOGE("Failed to wait for fence of acquired buffer: %s (%d)",
+ strerror(-err), err);
+ return err;
+ }
+ }
+
+ item->mGraphicBuffer = mSlots[item->mBuf].mGraphicBuffer;
+
+ return OK;
+}
+
+status_t GonkNativeWindow::releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence) {
+ status_t err;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = addReleaseFenceLocked(item.mBuf, item.mGraphicBuffer, releaseFence);
+
+ err = releaseBufferLocked(item.mBuf, item.mGraphicBuffer);
+ if (err != OK) {
+ ALOGE("Failed to release buffer: %s (%d)",
+ strerror(-err), err);
+ }
+ return err;
+}
+
+status_t GonkNativeWindow::setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Mutex::Autolock _l(mMutex);
+ return mConsumer->setDefaultBufferSize(w, h);
+}
+
+status_t GonkNativeWindow::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock _l(mMutex);
+ return mConsumer->setDefaultBufferFormat(defaultFormat);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getCurrentBuffer() {
+ Mutex::Autolock _l(mMutex);
+ BufferItem item;
+
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ status_t err = acquireBufferLocked(&item, 0); //???
+ if (err != NO_ERROR) {
+ return NULL;
+ }
+
+ RefPtr<TextureClient> textureClient =
+ mConsumer->getTextureClientFromBuffer(item.mGraphicBuffer.get());
+ if (!textureClient) {
+ return NULL;
+ }
+ textureClient->SetRecycleCallback(GonkNativeWindow::RecycleCallback, this);
+ return textureClient.forget();
+}
+
+/* static */ void
+GonkNativeWindow::RecycleCallback(TextureClient* client, void* closure) {
+ GonkNativeWindow* nativeWindow =
+ static_cast<GonkNativeWindow*>(closure);
+
+ MOZ_ASSERT(client && !client->IsDead());
+ client->ClearRecycleCallback();
+ nativeWindow->returnBuffer(client);
+}
+
+void GonkNativeWindow::returnBuffer(TextureClient* client) {
+ ALOGD("GonkNativeWindow::returnBuffer");
+ Mutex::Autolock lock(mMutex);
+
+ int index = mConsumer->getSlotFromTextureClientLocked(client);
+ if (index < 0) {
+ return;
+ }
+
+ FenceHandle handle = client->GetAndResetReleaseFenceHandle();
+ RefPtr<FenceHandle::FdObj> fdObj = handle.GetAndResetFdObj();
+ sp<Fence> fence = new Fence(fdObj->GetAndResetFd());
+
+ status_t err;
+ err = addReleaseFenceLocked(index,
+ mSlots[index].mGraphicBuffer,
+ fence);
+
+ err = releaseBufferLocked(index, mSlots[index].mGraphicBuffer);
+
+ if (err != OK) {
+ ALOGE("Failed to return buffer: %s (%d)", strerror(-err), err);
+ }
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getTextureClientFromBuffer(ANativeWindowBuffer* buffer) {
+ Mutex::Autolock lock(mMutex);
+ return mConsumer->getTextureClientFromBuffer(buffer);
+}
+
+void GonkNativeWindow::setNewFrameCallback(
+ GonkNativeWindowNewFrameCallback* callback) {
+ ALOGD("setNewFrameCallback");
+ Mutex::Autolock lock(mMutex);
+ mNewFrameCallback = callback;
+}
+
+#if ANDROID_VERSION == 21
+void GonkNativeWindow::onFrameAvailable() {
+ GonkConsumerBase::onFrameAvailable();
+#else
+void GonkNativeWindow::onFrameAvailable(const ::android::BufferItem &item) {
+ GonkConsumerBase::onFrameAvailable(item);
+#endif
+
+ if (mNewFrameCallback) {
+ mNewFrameCallback->OnNewFrame();
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkNativeWindowLL.h b/widget/gonk/nativewindow/GonkNativeWindowLL.h
new file mode 100644
index 000000000..64cd6482d
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowLL.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_GONKNATIVEWINDOW_LL_H
+#define NATIVEWINDOW_GONKNATIVEWINDOW_LL_H
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "GonkConsumerBaseLL.h"
+#include "IGonkGraphicBufferConsumerLL.h"
+
+namespace android {
+
+// The user of GonkNativeWindow who wants to receive notification of
+// new frames should implement this interface.
+class GonkNativeWindowNewFrameCallback {
+public:
+ virtual void OnNewFrame() = 0;
+};
+
+/**
+ * GonkNativeWindow is a GonkBufferQueue consumer endpoint that allows clients
+ * access to the whole BufferItem entry from GonkBufferQueue. Multiple buffers may
+ * be acquired at once, to be used concurrently by the client. This consumer can
+ * operate either in synchronous or asynchronous mode.
+ */
+class GonkNativeWindow: public GonkConsumerBase
+{
+ typedef mozilla::layers::TextureClient TextureClient;
+ public:
+ typedef GonkConsumerBase::FrameAvailableListener FrameAvailableListener;
+ typedef GonkBufferQueue::BufferItem BufferItem;
+
+ enum { DEFAULT_MAX_BUFFERS = -1 };
+ enum { INVALID_BUFFER_SLOT = GonkBufferQueue::INVALID_BUFFER_SLOT };
+ enum { NO_BUFFER_AVAILABLE = GonkBufferQueue::NO_BUFFER_AVAILABLE };
+
+ // Create a new buffer item consumer. The consumerUsage parameter determines
+ // the consumer usage flags passed to the graphics allocator. The
+ // bufferCount parameter specifies how many buffers can be locked for user
+ // access at the same time.
+ // controlledByApp tells whether this consumer is controlled by the
+ // application.
+ GonkNativeWindow(const sp<IGonkGraphicBufferConsumer>& consumer,
+ int bufferCount = DEFAULT_MAX_BUFFERS);
+ GonkNativeWindow(const sp<IGonkGraphicBufferConsumer>& consumer,
+ uint32_t consumerUsage, int bufferCount = DEFAULT_MAX_BUFFERS,
+ bool controlledByApp = false);
+
+ virtual ~GonkNativeWindow();
+
+ // set the name of the GonkNativeWindow that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // Gets the next graphics buffer from the producer, filling out the
+ // passed-in BufferItem structure. Returns NO_BUFFER_AVAILABLE if the queue
+ // of buffers is empty, and INVALID_OPERATION if the maximum number of
+ // buffers is already acquired.
+ //
+ // Only a fixed number of buffers can be acquired at a time, determined by
+ // the construction-time bufferCount parameter. If INVALID_OPERATION is
+ // returned by acquireBuffer, then old buffers must be returned to the
+ // queue by calling releaseBuffer before more buffers can be acquired.
+ //
+ // If waitForFence is true, and the acquired BufferItem has a valid fence object,
+ // acquireBuffer will wait on the fence with no timeout before returning.
+ status_t acquireBuffer(BufferItem *item, nsecs_t presentWhen,
+ bool waitForFence = true);
+
+ // Returns an acquired buffer to the queue, allowing it to be reused. Since
+ // only a fixed number of buffers may be acquired at a time, old buffers
+ // must be released by calling releaseBuffer to ensure new buffers can be
+ // acquired by acquireBuffer. Once a BufferItem is released, the caller must
+ // not access any members of the BufferItem, and should immediately remove
+ // all of its references to the BufferItem itself.
+ status_t releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence = Fence::NO_FENCE);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // requestBuffers when a with and height of zero is requested.
+ status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultBufferFormat allows the GonkBufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer
+ status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // Get next frame from the queue, caller owns the returned buffer.
+ already_AddRefed<TextureClient> getCurrentBuffer();
+
+ // Return the buffer to the queue and mark it as FREE. After that
+ // the buffer is useable again for the decoder.
+ void returnBuffer(TextureClient* client);
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ void setNewFrameCallback(GonkNativeWindowNewFrameCallback* callback);
+
+ static void RecycleCallback(TextureClient* client, void* closure);
+
+protected:
+#if ANDROID_VERSION == 21
+ virtual void onFrameAvailable();
+#else
+ virtual void onFrameAvailable(const ::android::BufferItem &item);
+#endif
+
+private:
+ GonkNativeWindowNewFrameCallback* mNewFrameCallback;
+};
+
+} // namespace android
+
+#endif // NATIVEWINDOW_GONKNATIVEWINDOW_LL_H
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumer.h b/widget/gonk/nativewindow/IGonkGraphicBufferConsumer.h
new file mode 100644
index 000000000..14541c9b4
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumer.h
@@ -0,0 +1,20 @@
+/* Copyright 2013 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
+# include "IGonkGraphicBufferConsumerLL.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19
+# include "IGonkGraphicBufferConsumerKK.h"
+#endif
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.cpp b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.cpp
new file mode 100644
index 000000000..c4c9f6578
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.cpp
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+
+#include <binder/Parcel.h>
+#include <binder/IInterface.h>
+
+#include <gui/IConsumerListener.h>
+#include "IGonkGraphicBufferConsumerKK.h"
+
+#include <ui/GraphicBuffer.h>
+#include <ui/Fence.h>
+
+#include <system/window.h>
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+IGonkGraphicBufferConsumer::BufferItem::BufferItem() :
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mIsAutoTimestamp(false),
+ mFrameNumber(0),
+ mBuf(INVALID_BUFFER_SLOT),
+ mIsDroppable(false),
+ mAcquireCalled(false),
+ mTransformToDisplayInverse(false) {
+ mCrop.makeInvalid();
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getPodSize() const {
+ size_t c = sizeof(mCrop) +
+ sizeof(mTransform) +
+ sizeof(mScalingMode) +
+ sizeof(mTimestamp) +
+ sizeof(mIsAutoTimestamp) +
+ sizeof(mFrameNumber) +
+ sizeof(mBuf) +
+ sizeof(mIsDroppable) +
+ sizeof(mAcquireCalled) +
+ sizeof(mTransformToDisplayInverse);
+ return c;
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getFlattenedSize() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ if (mFence != 0) {
+ c += mFence->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ return sizeof(int32_t) + c + getPodSize();
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getFdCount() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFdCount();
+ }
+ if (mFence != 0) {
+ c += mFence->getFdCount();
+ }
+ return c;
+}
+
+status_t IGonkGraphicBufferConsumer::BufferItem::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const {
+
+ // make sure we have enough space
+ if (count < BufferItem::getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ // content flags are stored first
+ uint32_t& flags = *static_cast<uint32_t*>(buffer);
+
+ // advance the pointer
+ FlattenableUtils::advance(buffer, size, sizeof(uint32_t));
+
+ flags = 0;
+ if (mGraphicBuffer != 0) {
+ status_t err = mGraphicBuffer->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 1;
+ }
+ if (mFence != 0) {
+ status_t err = mFence->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 2;
+ }
+
+ // check we have enough space (in case flattening the fence/graphicbuffer lied to us)
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, mCrop);
+ FlattenableUtils::write(buffer, size, mTransform);
+ FlattenableUtils::write(buffer, size, mScalingMode);
+ FlattenableUtils::write(buffer, size, mTimestamp);
+ FlattenableUtils::write(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::write(buffer, size, mFrameNumber);
+ FlattenableUtils::write(buffer, size, mBuf);
+ FlattenableUtils::write(buffer, size, mIsDroppable);
+ FlattenableUtils::write(buffer, size, mAcquireCalled);
+ FlattenableUtils::write(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+status_t IGonkGraphicBufferConsumer::BufferItem::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+
+ if (size < sizeof(uint32_t))
+ return NO_MEMORY;
+
+ uint32_t flags = 0;
+ FlattenableUtils::read(buffer, size, flags);
+
+ if (flags & 1) {
+ mGraphicBuffer = new GraphicBuffer();
+ status_t err = mGraphicBuffer->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ if (flags & 2) {
+ mFence = new Fence();
+ status_t err = mFence->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ // check we have enough space
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, mCrop);
+ FlattenableUtils::read(buffer, size, mTransform);
+ FlattenableUtils::read(buffer, size, mScalingMode);
+ FlattenableUtils::read(buffer, size, mTimestamp);
+ FlattenableUtils::read(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::read(buffer, size, mFrameNumber);
+ FlattenableUtils::read(buffer, size, mBuf);
+ FlattenableUtils::read(buffer, size, mIsDroppable);
+ FlattenableUtils::read(buffer, size, mAcquireCalled);
+ FlattenableUtils::read(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+// ---------------------------------------------------------------------------
+
+enum {
+ ACQUIRE_BUFFER = IBinder::FIRST_CALL_TRANSACTION,
+ RELEASE_BUFFER,
+ CONSUMER_CONNECT,
+ CONSUMER_DISCONNECT,
+ GET_RELEASED_BUFFERS,
+ SET_DEFAULT_BUFFER_SIZE,
+ SET_DEFAULT_MAX_BUFFER_COUNT,
+ DISABLE_ASYNC_BUFFER,
+ SET_MAX_ACQUIRED_BUFFER_COUNT,
+ SET_CONSUMER_NAME,
+ SET_DEFAULT_BUFFER_FORMAT,
+ SET_CONSUMER_USAGE_BITS,
+ SET_TRANSFORM_HINT,
+ DUMP,
+};
+
+class BpGonkGraphicBufferConsumer : public BpInterface<IGonkGraphicBufferConsumer>
+{
+public:
+ BpGonkGraphicBufferConsumer(const sp<IBinder>& impl)
+ : BpInterface<IGonkGraphicBufferConsumer>(impl)
+ {
+ }
+
+ virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt64(presentWhen);
+ status_t result = remote()->transact(ACQUIRE_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.read(*buffer);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
+ const sp<Fence>& releaseFence) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(buf);
+ data.writeInt64(frameNumber);
+ data.write(*releaseFence);
+ status_t result = remote()->transact(RELEASE_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeStrongBinder(consumer->asBinder());
+ data.writeInt32(controlledByApp);
+ status_t result = remote()->transact(CONSUMER_CONNECT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t consumerDisconnect() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(CONSUMER_DISCONNECT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t getReleasedBuffers(uint32_t* slotMask) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(GET_RELEASED_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ *slotMask = reply.readInt32();
+ return reply.readInt32();
+ }
+
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(w);
+ data.writeInt32(h);
+ status_t result = remote()->transact(SET_DEFAULT_BUFFER_SIZE, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setDefaultMaxBufferCount(int bufferCount) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(bufferCount);
+ status_t result = remote()->transact(SET_DEFAULT_MAX_BUFFER_COUNT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t disableAsyncBuffer() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(DISABLE_ASYNC_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(maxAcquiredBuffers);
+ status_t result = remote()->transact(SET_MAX_ACQUIRED_BUFFER_COUNT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual void setConsumerName(const String8& name) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeString8(name);
+ remote()->transact(SET_CONSUMER_NAME, data, &reply);
+ }
+
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(defaultFormat);
+ status_t result = remote()->transact(SET_DEFAULT_BUFFER_FORMAT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setConsumerUsageBits(uint32_t usage) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(usage);
+ status_t result = remote()->transact(SET_CONSUMER_USAGE_BITS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setTransformHint(uint32_t hint) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(hint);
+ status_t result = remote()->transact(SET_TRANSFORM_HINT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual void dumpToString(String8& result, const char* prefix) const {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeString8(result);
+ data.writeString8(String8(prefix ? prefix : ""));
+ remote()->transact(DUMP, data, &reply);
+ reply.readString8();
+ }
+};
+
+IMPLEMENT_META_INTERFACE(GonkGraphicBufferConsumer, "android.gui.IGonkGraphicBufferConsumer");
+// ----------------------------------------------------------------------
+
+status_t BnGonkGraphicBufferConsumer::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ switch(code) {
+ case ACQUIRE_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ BufferItem item;
+ int64_t presentWhen = data.readInt64();
+ status_t result = acquireBuffer(&item, presentWhen);
+ status_t err = reply->write(item);
+ if (err) return err;
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case RELEASE_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ int buf = data.readInt32();
+ uint64_t frameNumber = data.readInt64();
+ sp<Fence> releaseFence = new Fence();
+ status_t err = data.read(*releaseFence);
+ if (err) return err;
+ status_t result = releaseBuffer(buf, frameNumber, releaseFence);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+
+ case CONSUMER_CONNECT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ sp<IConsumerListener> consumer = IConsumerListener::asInterface( data.readStrongBinder() );
+ bool controlledByApp = data.readInt32();
+ status_t result = consumerConnect(consumer, controlledByApp);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+
+ case CONSUMER_DISCONNECT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ status_t result = consumerDisconnect();
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case GET_RELEASED_BUFFERS: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t slotMask;
+ status_t result = getReleasedBuffers(&slotMask);
+ reply->writeInt32(slotMask);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_BUFFER_SIZE: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t w = data.readInt32();
+ uint32_t h = data.readInt32();
+ status_t result = setDefaultBufferSize(w, h);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_MAX_BUFFER_COUNT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t bufferCount = data.readInt32();
+ status_t result = setDefaultMaxBufferCount(bufferCount);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case DISABLE_ASYNC_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ status_t result = disableAsyncBuffer();
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_MAX_ACQUIRED_BUFFER_COUNT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t maxAcquiredBuffers = data.readInt32();
+ status_t result = setMaxAcquiredBufferCount(maxAcquiredBuffers);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_CONSUMER_NAME: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ setConsumerName( data.readString8() );
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_BUFFER_FORMAT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t defaultFormat = data.readInt32();
+ status_t result = setDefaultBufferFormat(defaultFormat);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_CONSUMER_USAGE_BITS: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t usage = data.readInt32();
+ status_t result = setConsumerUsageBits(usage);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_TRANSFORM_HINT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t hint = data.readInt32();
+ status_t result = setTransformHint(hint);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+
+ case DUMP: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ String8 result = data.readString8();
+ String8 prefix = data.readString8();
+ static_cast<IGonkGraphicBufferConsumer*>(this)->dumpToString(result, prefix);
+ reply->writeString8(result);
+ return NO_ERROR;
+ }
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.h b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.h
new file mode 100644
index 000000000..ce51e1ef2
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_IGONKGRAPHICBUFFERCONSUMER_KK_H
+#define NATIVEWINDOW_IGONKGRAPHICBUFFERCONSUMER_KK_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+#include <binder/IInterface.h>
+#include <ui/Rect.h>
+
+#include "mozilla/Types.h"
+#include "mozilla/layers/LayersSurfaces.h"
+
+namespace mozilla {
+
+namespace layers {
+class TextureClient;
+}
+}
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class MOZ_EXPORT IConsumerListener;
+class MOZ_EXPORT GraphicBuffer;
+class MOZ_EXPORT Fence;
+
+class IGonkGraphicBufferConsumer : public IInterface {
+ typedef mozilla::layers::TextureClient TextureClient;
+public:
+
+ // public facing structure for BufferSlot
+ class BufferItem : public Flattenable<BufferItem> {
+ friend class Flattenable<BufferItem>;
+ size_t getPodSize() const;
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ public:
+ enum { INVALID_BUFFER_SLOT = -1 };
+ BufferItem();
+
+ // mGraphicBuffer points to the buffer allocated for this slot, or is NULL
+ // if the buffer in this slot has been acquired in the past (see
+ // BufferSlot.mAcquireCalled).
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence that will signal when the buffer is idle.
+ sp<Fence> mFence;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued.
+ int64_t mTimestamp;
+
+ // mIsAutoTimestamp indicates whether mTimestamp was generated
+ // automatically when the buffer was queued.
+ bool mIsAutoTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot.
+ uint64_t mFrameNumber;
+
+ // mBuf is the slot index of this buffer
+ int mBuf;
+
+ // mIsDroppable whether this buffer was queued with the
+ // property that it can be replaced by a new buffer for the purpose of
+ // making sure dequeueBuffer() won't block.
+ // i.e.: was the BufferQueue in "mDequeueBufferCannotBlock" when this buffer
+ // was queued.
+ bool mIsDroppable;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates this buffer must be transformed by the inverse transform of the screen
+ // it is displayed onto. This is applied after mTransform.
+ bool mTransformToDisplayInverse;
+ };
+
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the BufferQueue. If no buffer is pending then it returns -EINVAL. If a
+ // buffer is successfully acquired, the information about the buffer is
+ // returned in BufferItem. If the buffer returned had previously been
+ // acquired then the BufferItem::mGraphicBuffer field of buffer is set to
+ // NULL and it is assumed that the consumer still holds a reference to the
+ // buffer.
+ //
+ // If presentWhen is nonzero, it indicates the time when the buffer will
+ // be displayed on screen. If the buffer's timestamp is farther in the
+ // future, the buffer won't be acquired, and PRESENT_LATER will be
+ // returned. The presentation time is in nanoseconds, and the time base
+ // is CLOCK_MONOTONIC.
+ virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen) = 0;
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // BufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use. frameNumber is used to indentify the exact buffer returned.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ //
+ // Note that the dependencies on EGL will be removed once we switch to using
+ // the Android HW Sync HAL.
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) = 0;
+
+ // consumerConnect connects a consumer to the BufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // BufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the BufferQueue by the producer to fail.
+ // controlledByApp indicates whether the consumer is controlled by
+ // the application.
+ //
+ // consumer may not be NULL.
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) = 0;
+
+ // consumerDisconnect disconnects a consumer from the BufferQueue. All
+ // buffers will be freed and the BufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the BufferQueue by the producer to
+ // fail.
+ virtual status_t consumerDisconnect() = 0;
+
+ // getReleasedBuffers sets the value pointed to by slotMask to a bit mask
+ // indicating which buffer slots have been released by the BufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ virtual status_t getReleasedBuffers(uint32_t* slotMask) = 0;
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) = 0;
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ virtual status_t setDefaultMaxBufferCount(int bufferCount) = 0;
+
+ // disableAsyncBuffer disables the extra buffer used in async mode
+ // (when both producer and consumer have set their "isControlledByApp"
+ // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
+ //
+ // This can only be called before consumerConnect().
+ virtual status_t disableAsyncBuffer() = 0;
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the BufferQueue.
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) = 0;
+
+ // setConsumerName sets the name used in logging
+ virtual void setConsumerName(const String8& name) = 0;
+
+ // setDefaultBufferFormat allows the BufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat) = 0;
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ virtual status_t setConsumerUsageBits(uint32_t usage) = 0;
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ virtual status_t setTransformHint(uint32_t hint) = 0;
+
+ // dump state into a string
+ virtual void dumpToString(String8& result, const char* prefix) const = 0;
+
+public:
+ DECLARE_META_INTERFACE(GonkGraphicBufferConsumer);
+};
+
+// ----------------------------------------------------------------------------
+
+class BnGonkGraphicBufferConsumer : public BnInterface<IGonkGraphicBufferConsumer>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_IGRAPHICBUFFERCONSUMER_H
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.cpp b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.cpp
new file mode 100644
index 000000000..729ed6736
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.cpp
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/NativeHandle.h>
+
+#include <binder/Parcel.h>
+#include <binder/IInterface.h>
+
+#include <gui/IConsumerListener.h>
+#include "IGonkGraphicBufferConsumerLL.h"
+
+#include <ui/GraphicBuffer.h>
+#include <ui/Fence.h>
+
+#include <system/window.h>
+
+#include "mozilla/layers/TextureClient.h"
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+IGonkGraphicBufferConsumer::BufferItem::BufferItem() :
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mIsAutoTimestamp(false),
+ mFrameNumber(0),
+ mBuf(INVALID_BUFFER_SLOT),
+ mIsDroppable(false),
+ mAcquireCalled(false),
+ mTransformToDisplayInverse(false) {
+ mCrop.makeInvalid();
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getPodSize() const {
+ size_t c = sizeof(mCrop) +
+ sizeof(mTransform) +
+ sizeof(mScalingMode) +
+ sizeof(mTimestamp) +
+ sizeof(mIsAutoTimestamp) +
+ sizeof(mFrameNumber) +
+ sizeof(mBuf) +
+ sizeof(mIsDroppable) +
+ sizeof(mAcquireCalled) +
+ sizeof(mTransformToDisplayInverse);
+ return c;
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getFlattenedSize() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFlattenedSize();
+ c = FlattenableUtils::align<4>(c);
+ }
+ if (mFence != 0) {
+ c += mFence->getFlattenedSize();
+ c = FlattenableUtils::align<4>(c);
+ }
+ return sizeof(int32_t) + c + getPodSize();
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getFdCount() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFdCount();
+ }
+ if (mFence != 0) {
+ c += mFence->getFdCount();
+ }
+ return c;
+}
+
+static void writeBoolAsInt(void*& buffer, size_t& size, bool b) {
+ FlattenableUtils::write(buffer, size, static_cast<int32_t>(b));
+}
+
+static bool readBoolFromInt(void const*& buffer, size_t& size) {
+ int32_t i;
+ FlattenableUtils::read(buffer, size, i);
+ return static_cast<bool>(i);
+}
+
+status_t IGonkGraphicBufferConsumer::BufferItem::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const {
+
+ // make sure we have enough space
+ if (size < BufferItem::getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ // content flags are stored first
+ uint32_t& flags = *static_cast<uint32_t*>(buffer);
+
+ // advance the pointer
+ FlattenableUtils::advance(buffer, size, sizeof(uint32_t));
+
+ flags = 0;
+ if (mGraphicBuffer != 0) {
+ status_t err = mGraphicBuffer->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 1;
+ }
+ if (mFence != 0) {
+ status_t err = mFence->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 2;
+ }
+
+ // check we have enough space (in case flattening the fence/graphicbuffer lied to us)
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, mCrop);
+ FlattenableUtils::write(buffer, size, mTransform);
+ FlattenableUtils::write(buffer, size, mScalingMode);
+ FlattenableUtils::write(buffer, size, mTimestamp);
+ writeBoolAsInt(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::write(buffer, size, mFrameNumber);
+ FlattenableUtils::write(buffer, size, mBuf);
+ writeBoolAsInt(buffer, size, mIsDroppable);
+ writeBoolAsInt(buffer, size, mAcquireCalled);
+ writeBoolAsInt(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+status_t IGonkGraphicBufferConsumer::BufferItem::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+
+ if (size < sizeof(uint32_t))
+ return NO_MEMORY;
+
+ uint32_t flags = 0;
+ FlattenableUtils::read(buffer, size, flags);
+
+ if (flags & 1) {
+ mGraphicBuffer = new GraphicBuffer();
+ status_t err = mGraphicBuffer->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ if (flags & 2) {
+ mFence = new Fence();
+ status_t err = mFence->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ // check we have enough space
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, mCrop);
+ FlattenableUtils::read(buffer, size, mTransform);
+ FlattenableUtils::read(buffer, size, mScalingMode);
+ FlattenableUtils::read(buffer, size, mTimestamp);
+ mIsAutoTimestamp = readBoolFromInt(buffer, size);
+ FlattenableUtils::read(buffer, size, mFrameNumber);
+ FlattenableUtils::read(buffer, size, mBuf);
+ mIsDroppable = readBoolFromInt(buffer, size);
+ mAcquireCalled = readBoolFromInt(buffer, size);
+ mTransformToDisplayInverse = readBoolFromInt(buffer, size);
+
+ return NO_ERROR;
+}
+
+// ---------------------------------------------------------------------------
+
+enum {
+ ACQUIRE_BUFFER = IBinder::FIRST_CALL_TRANSACTION,
+ DETACH_BUFFER,
+ ATTACH_BUFFER,
+ RELEASE_BUFFER,
+ CONSUMER_CONNECT,
+ CONSUMER_DISCONNECT,
+ GET_RELEASED_BUFFERS,
+ SET_DEFAULT_BUFFER_SIZE,
+ SET_DEFAULT_MAX_BUFFER_COUNT,
+ DISABLE_ASYNC_BUFFER,
+ SET_MAX_ACQUIRED_BUFFER_COUNT,
+ SET_CONSUMER_NAME,
+ SET_DEFAULT_BUFFER_FORMAT,
+ SET_CONSUMER_USAGE_BITS,
+ SET_TRANSFORM_HINT,
+ GET_SIDEBAND_STREAM,
+ DUMP,
+};
+
+
+class BpGonkGraphicBufferConsumer : public BpInterface<IGonkGraphicBufferConsumer>
+{
+public:
+ BpGonkGraphicBufferConsumer(const sp<IBinder>& impl)
+ : BpInterface<IGonkGraphicBufferConsumer>(impl)
+ {
+ }
+
+ virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt64(presentWhen);
+ status_t result = remote()->transact(ACQUIRE_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.read(*buffer);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t detachBuffer(int slot) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(slot);
+ status_t result = remote()->transact(DETACH_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.readInt32();
+ return result;
+ }
+
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.write(*buffer.get());
+ status_t result = remote()->transact(ATTACH_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ *slot = reply.readInt32();
+ result = reply.readInt32();
+ return result;
+ }
+
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(buf);
+ data.writeInt64(frameNumber);
+ data.write(*releaseFence);
+ status_t result = remote()->transact(RELEASE_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeStrongBinder(consumer->asBinder());
+ data.writeInt32(controlledByApp);
+ status_t result = remote()->transact(CONSUMER_CONNECT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t consumerDisconnect() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(CONSUMER_DISCONNECT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t getReleasedBuffers(uint64_t* slotMask) {
+ Parcel data, reply;
+ if (slotMask == NULL) {
+ ALOGE("getReleasedBuffers: slotMask must not be NULL");
+ return BAD_VALUE;
+ }
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(GET_RELEASED_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ *slotMask = reply.readInt64();
+ return reply.readInt32();
+ }
+
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(w);
+ data.writeInt32(h);
+ status_t result = remote()->transact(SET_DEFAULT_BUFFER_SIZE, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setDefaultMaxBufferCount(int bufferCount) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(bufferCount);
+ status_t result = remote()->transact(SET_DEFAULT_MAX_BUFFER_COUNT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t disableAsyncBuffer() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(DISABLE_ASYNC_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(maxAcquiredBuffers);
+ status_t result = remote()->transact(SET_MAX_ACQUIRED_BUFFER_COUNT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual void setConsumerName(const String8& name) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeString8(name);
+ remote()->transact(SET_CONSUMER_NAME, data, &reply);
+ }
+
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(defaultFormat);
+ status_t result = remote()->transact(SET_DEFAULT_BUFFER_FORMAT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setConsumerUsageBits(uint32_t usage) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(usage);
+ status_t result = remote()->transact(SET_CONSUMER_USAGE_BITS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setTransformHint(uint32_t hint) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(hint);
+ status_t result = remote()->transact(SET_TRANSFORM_HINT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual sp<NativeHandle> getSidebandStream() const {
+ Parcel data, reply;
+ status_t err;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ if ((err = remote()->transact(GET_SIDEBAND_STREAM, data, &reply)) != NO_ERROR) {
+ return NULL;
+ }
+ sp<NativeHandle> stream;
+ if (reply.readInt32()) {
+ stream = NativeHandle::create(reply.readNativeHandle(), true);
+ }
+ return stream;
+ }
+
+ virtual void dumpToString(String8& result, const char* prefix) const {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeString8(result);
+ data.writeString8(String8(prefix ? prefix : ""));
+ remote()->transact(DUMP, data, &reply);
+ reply.readString8();
+ }
+
+ // Added by mozilla
+ virtual already_AddRefed<mozilla::layers::TextureClient>
+ getTextureClientFromBuffer(ANativeWindowBuffer* buffer)
+ {
+ return nullptr;
+ }
+
+ virtual int
+ getSlotFromTextureClientLocked(mozilla::layers::TextureClient* client) const
+ {
+ return BAD_VALUE;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(GonkGraphicBufferConsumer, "android.gui.IGonkGraphicBufferConsumer");
+
+// ----------------------------------------------------------------------
+
+status_t BnGonkGraphicBufferConsumer::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ switch(code) {
+ case ACQUIRE_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ BufferItem item;
+ int64_t presentWhen = data.readInt64();
+ status_t result = acquireBuffer(&item, presentWhen);
+ status_t err = reply->write(item);
+ if (err) return err;
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case DETACH_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ int slot = data.readInt32();
+ int result = detachBuffer(slot);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case ATTACH_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ sp<GraphicBuffer> buffer = new GraphicBuffer();
+ data.read(*buffer.get());
+ int slot;
+ int result = attachBuffer(&slot, buffer);
+ reply->writeInt32(slot);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case RELEASE_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ int buf = data.readInt32();
+ uint64_t frameNumber = data.readInt64();
+ sp<Fence> releaseFence = new Fence();
+ status_t err = data.read(*releaseFence);
+ if (err) return err;
+ status_t result = releaseBuffer(buf, frameNumber, releaseFence);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case CONSUMER_CONNECT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ sp<IConsumerListener> consumer = IConsumerListener::asInterface( data.readStrongBinder() );
+ bool controlledByApp = data.readInt32();
+ status_t result = consumerConnect(consumer, controlledByApp);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case CONSUMER_DISCONNECT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ status_t result = consumerDisconnect();
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case GET_RELEASED_BUFFERS: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint64_t slotMask;
+ status_t result = getReleasedBuffers(&slotMask);
+ reply->writeInt64(slotMask);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_BUFFER_SIZE: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t w = data.readInt32();
+ uint32_t h = data.readInt32();
+ status_t result = setDefaultBufferSize(w, h);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_MAX_BUFFER_COUNT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t bufferCount = data.readInt32();
+ status_t result = setDefaultMaxBufferCount(bufferCount);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case DISABLE_ASYNC_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ status_t result = disableAsyncBuffer();
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_MAX_ACQUIRED_BUFFER_COUNT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t maxAcquiredBuffers = data.readInt32();
+ status_t result = setMaxAcquiredBufferCount(maxAcquiredBuffers);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_CONSUMER_NAME: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ setConsumerName( data.readString8() );
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_BUFFER_FORMAT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t defaultFormat = data.readInt32();
+ status_t result = setDefaultBufferFormat(defaultFormat);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_CONSUMER_USAGE_BITS: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t usage = data.readInt32();
+ status_t result = setConsumerUsageBits(usage);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_TRANSFORM_HINT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t hint = data.readInt32();
+ status_t result = setTransformHint(hint);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case DUMP: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ String8 result = data.readString8();
+ String8 prefix = data.readString8();
+ static_cast<IGonkGraphicBufferConsumer*>(this)->dumpToString(result, prefix);
+ reply->writeString8(result);
+ return NO_ERROR;
+ }
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.h b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.h
new file mode 100644
index 000000000..8a93a0849
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.h
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NATIVEWINDOW_IGONKGRAPHICBUFFERCONSUMER_LL_H
+#define NATIVEWINDOW_IGONKGRAPHICBUFFERCONSUMER_LL_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+#include <binder/IInterface.h>
+#include <ui/Rect.h>
+
+#include "mozilla/RefPtr.h"
+
+class ANativeWindowBuffer;
+
+namespace mozilla {
+namespace layers {
+class TextureClient;
+}
+}
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class Fence;
+class GraphicBuffer;
+class IConsumerListener;
+class NativeHandle;
+
+class IGonkGraphicBufferConsumer : public IInterface {
+public:
+
+ // public facing structure for BufferSlot
+ class BufferItem : public Flattenable<BufferItem> {
+ friend class Flattenable<BufferItem>;
+ size_t getPodSize() const;
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ public:
+ // The default value of mBuf, used to indicate this doesn't correspond to a slot.
+ enum { INVALID_BUFFER_SLOT = -1 };
+ BufferItem();
+
+ // mGraphicBuffer points to the buffer allocated for this slot, or is NULL
+ // if the buffer in this slot has been acquired in the past (see
+ // BufferSlot.mAcquireCalled).
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence that will signal when the buffer is idle.
+ sp<Fence> mFence;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ // refer to NATIVE_WINDOW_TRANSFORM_* in <window.h>
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ // refer to NATIVE_WINDOW_SCALING_* in <window.h>
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued. This value
+ // is guaranteed to be monotonically increasing for each newly
+ // acquired buffer.
+ int64_t mTimestamp;
+
+ // mIsAutoTimestamp indicates whether mTimestamp was generated
+ // automatically when the buffer was queued.
+ bool mIsAutoTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot.
+ uint64_t mFrameNumber;
+
+ // mBuf is the slot index of this buffer (default INVALID_BUFFER_SLOT).
+ int mBuf;
+
+ // mIsDroppable whether this buffer was queued with the
+ // property that it can be replaced by a new buffer for the purpose of
+ // making sure dequeueBuffer() won't block.
+ // i.e.: was the BufferQueue in "mDequeueBufferCannotBlock" when this buffer
+ // was queued.
+ bool mIsDroppable;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates this buffer must be transformed by the inverse transform of the screen
+ // it is displayed onto. This is applied after mTransform.
+ bool mTransformToDisplayInverse;
+ };
+
+ enum {
+ // Returned by releaseBuffer, after which the consumer must
+ // free any references to the just-released buffer that it might have.
+ STALE_BUFFER_SLOT = 1,
+ // Returned by dequeueBuffer if there are no pending buffers available.
+ NO_BUFFER_AVAILABLE,
+ // Returned by dequeueBuffer if it's too early for the buffer to be acquired.
+ PRESENT_LATER,
+ };
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the BufferQueue. If no buffer is pending then it returns
+ // NO_BUFFER_AVAILABLE. If a buffer is successfully acquired, the
+ // information about the buffer is returned in BufferItem.
+ //
+ // If the buffer returned had previously been
+ // acquired then the BufferItem::mGraphicBuffer field of buffer is set to
+ // NULL and it is assumed that the consumer still holds a reference to the
+ // buffer.
+ //
+ // If presentWhen is non-zero, it indicates the time when the buffer will
+ // be displayed on screen. If the buffer's timestamp is farther in the
+ // future, the buffer won't be acquired, and PRESENT_LATER will be
+ // returned. The presentation time is in nanoseconds, and the time base
+ // is CLOCK_MONOTONIC.
+ //
+ // Return of NO_ERROR means the operation completed as normal.
+ //
+ // Return of a positive value means the operation could not be completed
+ // at this time, but the user should try again later:
+ // * NO_BUFFER_AVAILABLE - no buffer is pending (nothing queued by producer)
+ // * PRESENT_LATER - the buffer's timestamp is farther in the future
+ //
+ // Return of a negative value means an error has occurred:
+ // * INVALID_OPERATION - too many buffers have been acquired
+ virtual status_t acquireBuffer(BufferItem* buffer, nsecs_t presentWhen) = 0;
+
+ // detachBuffer attempts to remove all ownership of the buffer in the given
+ // slot from the buffer queue. If this call succeeds, the slot will be
+ // freed, and there will be no way to obtain the buffer from this interface.
+ // The freed slot will remain unallocated until either it is selected to
+ // hold a freshly allocated buffer in dequeueBuffer or a buffer is attached
+ // to the slot. The buffer must have already been acquired.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - the given slot number is invalid, either because it is
+ // out of the range [0, NUM_BUFFER_SLOTS) or because the slot
+ // it refers to is not currently acquired.
+ virtual status_t detachBuffer(int slot) = 0;
+
+ // attachBuffer attempts to transfer ownership of a buffer to the buffer
+ // queue. If this call succeeds, it will be as if this buffer was acquired
+ // from the returned slot number. As such, this call will fail if attaching
+ // this buffer would cause too many buffers to be simultaneously acquired.
+ //
+ // If the buffer is successfully attached, its frameNumber is initialized
+ // to 0. This must be passed into the releaseBuffer call or else the buffer
+ // will be deallocated as stale.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - outSlot or buffer were NULL
+ // * INVALID_OPERATION - cannot attach the buffer because it would cause too
+ // many buffers to be acquired.
+ // * NO_MEMORY - no free slots available
+ virtual status_t attachBuffer(int *outSlot,
+ const sp<GraphicBuffer>& buffer) = 0;
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // BufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use. frameNumber is used to indentify the exact buffer returned.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ //
+ // Note that the dependencies on EGL will be removed once we switch to using
+ // the Android HW Sync HAL.
+ //
+ // Return of NO_ERROR means the operation completed as normal.
+ //
+ // Return of a positive value means the operation could not be completed
+ // at this time, but the user should try again later:
+ // * STALE_BUFFER_SLOT - see above (second paragraph)
+ //
+ // Return of a negative value means an error has occurred:
+ // * BAD_VALUE - one of the following could've happened:
+ // * the buffer slot was invalid
+ // * the fence was NULL
+ // * the buffer slot specified is not in the acquired state
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) = 0;
+
+ // consumerConnect connects a consumer to the BufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // BufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the BufferQueue by the producer to fail.
+ // controlledByApp indicates whether the consumer is controlled by
+ // the application.
+ //
+ // consumer may not be NULL.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned
+ // * BAD_VALUE - a NULL consumer was provided
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) = 0;
+
+ // consumerDisconnect disconnects a consumer from the BufferQueue. All
+ // buffers will be freed and the BufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the BufferQueue by the producer to
+ // fail.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - no consumer is currently connected
+ virtual status_t consumerDisconnect() = 0;
+
+ // getReleasedBuffers sets the value pointed to by slotMask to a bit set.
+ // Each bit index with a 1 corresponds to a released buffer slot with that
+ // index value. In particular, a released buffer is one that has
+ // been released by the BufferQueue but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ virtual status_t getReleasedBuffers(uint64_t* slotMask) = 0;
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - either w or h was zero
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) = 0;
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - bufferCount was out of range (see above).
+ virtual status_t setDefaultMaxBufferCount(int bufferCount) = 0;
+
+ // disableAsyncBuffer disables the extra buffer used in async mode
+ // (when both producer and consumer have set their "isControlledByApp"
+ // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
+ //
+ // This can only be called before consumerConnect().
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * INVALID_OPERATION - attempting to call this after consumerConnect.
+ virtual status_t disableAsyncBuffer() = 0;
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the BufferQueue.
+ //
+ // maxAcquiredBuffers must be (inclusive) between 1 and MAX_MAX_ACQUIRED_BUFFERS.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - maxAcquiredBuffers was out of range (see above).
+ // * INVALID_OPERATION - attempting to call this after a producer connected.
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) = 0;
+
+ // setConsumerName sets the name used in logging
+ virtual void setConsumerName(const String8& name) = 0;
+
+ // setDefaultBufferFormat allows the BufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ //
+ // Return of a value other than NO_ERROR means an unknown error has occurred.
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat) = 0;
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ //
+ // Return of a value other than NO_ERROR means an unknown error has occurred.
+ virtual status_t setConsumerUsageBits(uint32_t usage) = 0;
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ //
+ // Return of a value other than NO_ERROR means an unknown error has occurred.
+ virtual status_t setTransformHint(uint32_t hint) = 0;
+
+ // Retrieve the sideband buffer stream, if any.
+ virtual sp<NativeHandle> getSidebandStream() const = 0;
+
+ // dump state into a string
+ virtual void dumpToString(String8& result, const char* prefix) const = 0;
+
+ // Added by mozilla
+ virtual already_AddRefed<mozilla::layers::TextureClient>
+ getTextureClientFromBuffer(ANativeWindowBuffer* buffer) = 0;
+
+ virtual int getSlotFromTextureClientLocked(mozilla::layers::TextureClient* client) const = 0;
+
+public:
+ DECLARE_META_INTERFACE(GonkGraphicBufferConsumer);
+};
+
+// ----------------------------------------------------------------------------
+
+class BnGonkGraphicBufferConsumer : public BnInterface<IGonkGraphicBufferConsumer>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_IGONKGRAPHICBUFFERCONSUMER_H
diff --git a/widget/gonk/nativewindow/moz.build b/widget/gonk/nativewindow/moz.build
new file mode 100644
index 000000000..fbcee601c
--- /dev/null
+++ b/widget/gonk/nativewindow/moz.build
@@ -0,0 +1,104 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# Copyright 2013 Mozilla Foundation and Mozilla contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+EXPORTS += [
+ 'GonkBufferQueue.h',
+ 'GonkNativeWindow.h',
+]
+
+if CONFIG['ANDROID_VERSION'] >= '19':
+ EXPORTS += [
+ 'IGonkGraphicBufferConsumer.h',
+ ]
+
+if CONFIG['ANDROID_VERSION'] >= '21':
+ EXPORTS += [
+ 'GonkBufferQueueLL/GonkBufferQueueDefs.h',
+ 'GonkBufferQueueLL/GonkBufferQueueLL.h',
+ 'GonkBufferQueueLL/GonkBufferQueueProducer.h',
+ 'GonkBufferQueueLL/GonkBufferSlot.h',
+ 'GonkConsumerBaseLL.h',
+ 'GonkNativeWindowLL.h',
+ 'IGonkGraphicBufferConsumerLL.h',
+ ]
+elif CONFIG['ANDROID_VERSION'] >= '19':
+ EXPORTS += [
+ 'GonkBufferQueueKK.h',
+ 'GonkConsumerBaseKK.h',
+ 'GonkNativeWindowKK.h',
+ 'IGonkGraphicBufferConsumerKK.h',
+ ]
+elif CONFIG['ANDROID_VERSION'] in ('17', '18'):
+ EXPORTS += [
+ 'GonkBufferQueueJB.h',
+ 'GonkConsumerBaseJB.h',
+ 'GonkNativeWindowJB.h',
+ ]
+
+if CONFIG['MOZ_WEBRTC']:
+ if CONFIG['ANDROID_VERSION'] >= '21':
+ SOURCES += [
+ 'GonkBufferQueueLL/GonkBufferItem.cpp',
+ 'GonkBufferQueueLL/GonkBufferQueueConsumer.cpp',
+ 'GonkBufferQueueLL/GonkBufferQueueCore.cpp',
+ 'GonkBufferQueueLL/GonkBufferQueueLL.cpp',
+ 'GonkBufferQueueLL/GonkBufferQueueProducer.cpp',
+ 'GonkBufferQueueLL/GonkBufferSlot.cpp',
+ 'GonkConsumerBaseLL.cpp',
+ 'GonkNativeWindowLL.cpp',
+ 'IGonkGraphicBufferConsumerLL.cpp',
+ ]
+ elif CONFIG['ANDROID_VERSION'] >= '19':
+ SOURCES += [
+ 'GonkBufferQueueKK.cpp',
+ 'GonkConsumerBaseKK.cpp',
+ 'GonkNativeWindowKK.cpp',
+ 'IGonkGraphicBufferConsumerKK.cpp',
+ ]
+ elif CONFIG['ANDROID_VERSION'] in ('17', '18'):
+ SOURCES += [
+ 'GonkBufferQueueJB.cpp',
+ 'GonkConsumerBaseJB.cpp',
+ 'GonkNativeWindowJB.cpp',
+ ]
+
+if CONFIG['ANDROID_VERSION'] >= '18':
+ SOURCES += [
+ 'FakeSurfaceComposer.cpp',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['ANDROID_VERSION'] >= '18':
+ LOCAL_INCLUDES += [
+ '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
+ 'frameworks/native/opengl/include',
+ ]
+ ]
+
+DEFINES['HAVE_ANDROID_OS'] = True
+
+# Suppress some GCC warnings being treated as errors:
+# - about attributes on forward declarations for types that are already
+# defined, which complains about an important MOZ_EXPORT for android::AString
+if CONFIG['GNU_CC']:
+ CXXFLAGS += ['-Wno-error=attributes', '-Wno-overloaded-virtual']
+
+FINAL_LIBRARY = 'xul'
+
+DISABLE_STL_WRAPPING = True
+
+NO_VISIBILITY_FLAGS = True
diff --git a/widget/gonk/nsAppShell.cpp b/widget/gonk/nsAppShell.cpp
new file mode 100644
index 000000000..24e791b4b
--- /dev/null
+++ b/widget/gonk/nsAppShell.cpp
@@ -0,0 +1,1087 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=4 tw=80 et: */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <hardware_legacy/power.h>
+#include <signal.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/BitSet.h>
+
+#include "base/basictypes.h"
+#include "GonkPermission.h"
+#include "libdisplay/BootAnimation.h"
+#include "nscore.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Hal.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Services.h"
+#include "mozilla/TextEvents.h"
+#if ANDROID_VERSION >= 18
+#include "nativewindow/FakeSurfaceComposer.h"
+#endif
+#include "nsAppShell.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Touch.h"
+#include "nsGkAtoms.h"
+#include "nsIObserverService.h"
+#include "nsIScreen.h"
+#include "nsScreenManagerGonk.h"
+#include "nsThreadUtils.h"
+#include "nsWindow.h"
+#include "OrientationObserver.h"
+#include "GonkMemoryPressureMonitoring.h"
+
+#include "android/log.h"
+#include "libui/EventHub.h"
+#include "libui/InputReader.h"
+#include "libui/InputDispatcher.h"
+
+#include "mozilla/Preferences.h"
+#include "GeckoProfiler.h"
+
+// Defines kKeyMapping and GetKeyNameIndex()
+#include "GonkKeyMapping.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "GeckoTouchDispatcher.h"
+
+#undef LOG
+#define LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+#ifdef VERBOSE_LOG_ENABLED
+# define VERBOSE_LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+#else
+# define VERBOSE_LOG(args...) \
+ (void)0
+#endif
+
+using namespace android;
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::services;
+using namespace mozilla::widget;
+
+bool gDrawRequest = false;
+static nsAppShell *gAppShell = nullptr;
+static int epollfd = 0;
+static int signalfds[2] = {0};
+static bool sDevInputAudioJack;
+static int32_t sHeadphoneState;
+static int32_t sMicrophoneState;
+
+// Amount of time in MS before an input is considered expired.
+static const uint64_t kInputExpirationThresholdMs = 1000;
+static const char kKey_WAKE_LOCK_ID[] = "GeckoKeyEvent";
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAppShell, nsBaseAppShell, nsIObserver)
+
+static uint64_t
+nanosecsToMillisecs(nsecs_t nsecs)
+{
+ return nsecs / 1000000;
+}
+
+namespace mozilla {
+
+bool ProcessNextEvent()
+{
+ return gAppShell->ProcessNextNativeEvent(true);
+}
+
+void NotifyEvent()
+{
+ gAppShell->NotifyNativeEvent();
+}
+
+} // namespace mozilla
+
+static void
+pipeHandler(int fd, FdHandler *data)
+{
+ ssize_t len;
+ do {
+ char tmp[32];
+ len = read(fd, tmp, sizeof(tmp));
+ } while (len > 0);
+}
+
+struct Touch {
+ int32_t id;
+ PointerCoords coords;
+};
+
+struct UserInputData {
+ uint64_t timeMs;
+ enum {
+ MOTION_DATA,
+ KEY_DATA
+ } type;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t deviceId;
+ union {
+ struct {
+ int32_t keyCode;
+ int32_t scanCode;
+ } key;
+ struct {
+ int32_t touchCount;
+ ::Touch touches[MAX_POINTERS];
+ } motion;
+ };
+};
+
+static mozilla::Modifiers
+getDOMModifiers(int32_t metaState)
+{
+ mozilla::Modifiers result = 0;
+ if (metaState & (AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
+ result |= MODIFIER_ALT;
+ }
+ if (metaState & (AMETA_SHIFT_ON |
+ AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
+ result |= MODIFIER_SHIFT;
+ }
+ if (metaState & AMETA_FUNCTION_ON) {
+ result |= MODIFIER_FN;
+ }
+ if (metaState & (AMETA_CTRL_ON |
+ AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
+ result |= MODIFIER_CONTROL;
+ }
+ if (metaState & (AMETA_META_ON |
+ AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
+ result |= MODIFIER_META;
+ }
+ if (metaState & AMETA_CAPS_LOCK_ON) {
+ result |= MODIFIER_CAPSLOCK;
+ }
+ if (metaState & AMETA_NUM_LOCK_ON) {
+ result |= MODIFIER_NUMLOCK;
+ }
+ if (metaState & AMETA_SCROLL_LOCK_ON) {
+ result |= MODIFIER_SCROLLLOCK;
+ }
+ return result;
+}
+
+class MOZ_STACK_CLASS KeyEventDispatcher
+{
+public:
+ KeyEventDispatcher(const UserInputData& aData,
+ KeyCharacterMap* aKeyCharMap);
+ void Dispatch();
+
+private:
+ const UserInputData& mData;
+ sp<KeyCharacterMap> mKeyCharMap;
+
+ char16_t mChar;
+ char16_t mUnmodifiedChar;
+
+ uint32_t mDOMKeyCode;
+ uint32_t mDOMKeyLocation;
+ KeyNameIndex mDOMKeyNameIndex;
+ CodeNameIndex mDOMCodeNameIndex;
+ char16_t mDOMPrintableKeyValue;
+
+ bool IsKeyPress() const
+ {
+ return mData.action == AKEY_EVENT_ACTION_DOWN;
+ }
+ bool IsRepeat() const
+ {
+ return IsKeyPress() && (mData.flags & AKEY_EVENT_FLAG_LONG_PRESS);
+ }
+
+ char16_t PrintableKeyValue() const;
+
+ int32_t UnmodifiedMetaState() const
+ {
+ return mData.metaState &
+ ~(AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON |
+ AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON |
+ AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
+ }
+
+ static bool IsControlChar(char16_t aChar)
+ {
+ return (aChar < ' ' || aChar == 0x7F);
+ }
+
+ void DispatchKeyDownEvent();
+ void DispatchKeyUpEvent();
+ nsEventStatus DispatchKeyEventInternal(EventMessage aEventMessage);
+};
+
+KeyEventDispatcher::KeyEventDispatcher(const UserInputData& aData,
+ KeyCharacterMap* aKeyCharMap)
+ : mData(aData)
+ , mKeyCharMap(aKeyCharMap)
+ , mChar(0)
+ , mUnmodifiedChar(0)
+ , mDOMPrintableKeyValue(0)
+{
+ // XXX Printable key's keyCode value should be computed with actual
+ // input character.
+ mDOMKeyCode = (mData.key.keyCode < (ssize_t)ArrayLength(kKeyMapping)) ?
+ kKeyMapping[mData.key.keyCode] : 0;
+ mDOMKeyNameIndex = GetKeyNameIndex(mData.key.keyCode);
+ mDOMCodeNameIndex = GetCodeNameIndex(mData.key.scanCode);
+ mDOMKeyLocation =
+ WidgetKeyboardEvent::ComputeLocationFromCodeValue(mDOMCodeNameIndex);
+
+ if (!mKeyCharMap.get()) {
+ return;
+ }
+
+ mChar = mKeyCharMap->getCharacter(mData.key.keyCode, mData.metaState);
+ if (IsControlChar(mChar)) {
+ mChar = 0;
+ }
+ int32_t unmodifiedMetaState = UnmodifiedMetaState();
+ if (mData.metaState == unmodifiedMetaState) {
+ mUnmodifiedChar = mChar;
+ } else {
+ mUnmodifiedChar = mKeyCharMap->getCharacter(mData.key.keyCode,
+ unmodifiedMetaState);
+ if (IsControlChar(mUnmodifiedChar)) {
+ mUnmodifiedChar = 0;
+ }
+ }
+
+ mDOMPrintableKeyValue = PrintableKeyValue();
+}
+
+char16_t
+KeyEventDispatcher::PrintableKeyValue() const
+{
+ if (mDOMKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
+ return 0;
+ }
+ return mChar ? mChar : mUnmodifiedChar;
+}
+
+nsEventStatus
+KeyEventDispatcher::DispatchKeyEventInternal(EventMessage aEventMessage)
+{
+ WidgetKeyboardEvent event(true, aEventMessage, nullptr);
+ if (aEventMessage == eKeyPress) {
+ // XXX If the charCode is not a printable character, the charCode
+ // should be computed without Ctrl/Alt/Meta modifiers.
+ event.mCharCode = static_cast<uint32_t>(mChar);
+ }
+ if (!event.mCharCode) {
+ event.mKeyCode = mDOMKeyCode;
+ }
+ event.mIsChar = !!event.mCharCode;
+ event.mIsRepeat = IsRepeat();
+ event.mKeyNameIndex = mDOMKeyNameIndex;
+ if (mDOMPrintableKeyValue) {
+ event.mKeyValue = mDOMPrintableKeyValue;
+ }
+ event.mCodeNameIndex = mDOMCodeNameIndex;
+ event.mModifiers = getDOMModifiers(mData.metaState);
+ event.mLocation = mDOMKeyLocation;
+ event.mTime = mData.timeMs;
+ return nsWindow::DispatchKeyInput(event);
+}
+
+void
+KeyEventDispatcher::Dispatch()
+{
+ // XXX Even if unknown key is pressed, DOM key event should be
+ // dispatched since Gecko for the other platforms are implemented
+ // as so.
+ if (!mDOMKeyCode && mDOMKeyNameIndex == KEY_NAME_INDEX_Unidentified) {
+ VERBOSE_LOG("Got unknown key event code. "
+ "type 0x%04x code 0x%04x value %d",
+ mData.action, mData.key.keyCode, IsKeyPress());
+ return;
+ }
+
+ if (IsKeyPress()) {
+ DispatchKeyDownEvent();
+ } else {
+ DispatchKeyUpEvent();
+ }
+}
+
+void
+KeyEventDispatcher::DispatchKeyDownEvent()
+{
+ nsEventStatus status = DispatchKeyEventInternal(eKeyDown);
+ if (status != nsEventStatus_eConsumeNoDefault) {
+ DispatchKeyEventInternal(eKeyPress);
+ }
+}
+
+void
+KeyEventDispatcher::DispatchKeyUpEvent()
+{
+ DispatchKeyEventInternal(eKeyUp);
+}
+
+class SwitchEventRunnable : public mozilla::Runnable {
+public:
+ SwitchEventRunnable(hal::SwitchEvent& aEvent) : mEvent(aEvent)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ hal::NotifySwitchStateFromInputDevice(mEvent.device(),
+ mEvent.status());
+ return NS_OK;
+ }
+private:
+ hal::SwitchEvent mEvent;
+};
+
+static void
+updateHeadphoneSwitch()
+{
+ hal::SwitchEvent event;
+
+ switch (sHeadphoneState) {
+ case AKEY_STATE_UP:
+ event.status() = hal::SWITCH_STATE_OFF;
+ break;
+ case AKEY_STATE_DOWN:
+ event.status() = sMicrophoneState == AKEY_STATE_DOWN ?
+ hal::SWITCH_STATE_HEADSET : hal::SWITCH_STATE_HEADPHONE;
+ break;
+ default:
+ return;
+ }
+
+ event.device() = hal::SWITCH_HEADPHONES;
+ NS_DispatchToMainThread(new SwitchEventRunnable(event));
+}
+
+class GeckoPointerController : public PointerControllerInterface {
+ float mX;
+ float mY;
+ int32_t mButtonState;
+ InputReaderConfiguration* mConfig;
+public:
+ GeckoPointerController(InputReaderConfiguration* config)
+ : mX(0)
+ , mY(0)
+ , mButtonState(0)
+ , mConfig(config)
+ {}
+
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const;
+ virtual void move(float deltaX, float deltaY);
+ virtual void setButtonState(int32_t buttonState);
+ virtual int32_t getButtonState() const;
+ virtual void setPosition(float x, float y);
+ virtual void getPosition(float* outX, float* outY) const;
+ virtual void fade(Transition transition) {}
+ virtual void unfade(Transition transition) {}
+ virtual void setPresentation(Presentation presentation) {}
+ virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits) {}
+ virtual void clearSpots() {}
+};
+
+bool
+GeckoPointerController::getBounds(float* outMinX,
+ float* outMinY,
+ float* outMaxX,
+ float* outMaxY) const
+{
+ DisplayViewport viewport;
+
+ mConfig->getDisplayInfo(false, &viewport);
+
+ *outMinX = *outMinY = 0;
+ *outMaxX = viewport.logicalRight;
+ *outMaxY = viewport.logicalBottom;
+ return true;
+}
+
+void
+GeckoPointerController::move(float deltaX, float deltaY)
+{
+ float minX, minY, maxX, maxY;
+ getBounds(&minX, &minY, &maxX, &maxY);
+
+ mX = clamped(mX + deltaX, minX, maxX);
+ mY = clamped(mY + deltaY, minY, maxY);
+}
+
+void
+GeckoPointerController::setButtonState(int32_t buttonState)
+{
+ mButtonState = buttonState;
+}
+
+int32_t
+GeckoPointerController::getButtonState() const
+{
+ return mButtonState;
+}
+
+void
+GeckoPointerController::setPosition(float x, float y)
+{
+ mX = x;
+ mY = y;
+}
+
+void
+GeckoPointerController::getPosition(float* outX, float* outY) const
+{
+ *outX = mX;
+ *outY = mY;
+}
+
+class GeckoInputReaderPolicy : public InputReaderPolicyInterface {
+ InputReaderConfiguration mConfig;
+public:
+ GeckoInputReaderPolicy() {}
+
+ virtual void getReaderConfiguration(InputReaderConfiguration* outConfig);
+ virtual sp<PointerControllerInterface> obtainPointerController(int32_t
+deviceId)
+ {
+ return new GeckoPointerController(&mConfig);
+ };
+ virtual void notifyInputDevicesChanged(const android::Vector<InputDeviceInfo>& inputDevices) {};
+ virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(const String8& inputDeviceDescriptor)
+ {
+ return nullptr;
+ };
+ virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier)
+ {
+ return String8::empty();
+ };
+
+ void setDisplayInfo();
+
+protected:
+ virtual ~GeckoInputReaderPolicy() {}
+};
+
+class GeckoInputDispatcher : public InputDispatcherInterface {
+public:
+ GeckoInputDispatcher(sp<EventHub> &aEventHub)
+ : mQueueLock("GeckoInputDispatcher::mQueueMutex")
+ , mEventHub(aEventHub)
+ , mKeyDownCount(0)
+ , mKeyEventsFiltered(false)
+ , mPowerWakelock(false)
+ {
+ mTouchDispatcher = GeckoTouchDispatcher::GetInstance();
+ }
+
+ virtual void dump(String8& dump);
+
+ virtual void monitor() {}
+
+ // Called on the main thread
+ virtual void dispatchOnce();
+
+ // notify* methods are called on the InputReaderThread
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
+ virtual void notifyKey(const NotifyKeyArgs* args);
+ virtual void notifyMotion(const NotifyMotionArgs* args);
+ virtual void notifySwitch(const NotifySwitchArgs* args);
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
+
+ virtual int32_t injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags);
+
+ virtual void setInputWindows(const android::Vector<sp<InputWindowHandle> >& inputWindowHandles);
+ virtual void setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle);
+
+ virtual void setInputDispatchMode(bool enabled, bool frozen);
+ virtual void setInputFilterEnabled(bool enabled) {}
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) { return true; }
+
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
+
+
+
+protected:
+ virtual ~GeckoInputDispatcher() { }
+
+private:
+ // mQueueLock should generally be locked while using mEventQueue.
+ // UserInputData is pushed on on the InputReaderThread and
+ // popped and dispatched on the main thread.
+ mozilla::Mutex mQueueLock;
+ std::queue<UserInputData> mEventQueue;
+ sp<EventHub> mEventHub;
+ RefPtr<GeckoTouchDispatcher> mTouchDispatcher;
+
+ int mKeyDownCount;
+ bool mKeyEventsFiltered;
+ bool mPowerWakelock;
+};
+
+// GeckoInputReaderPolicy
+void
+GeckoInputReaderPolicy::setDisplayInfo()
+{
+ static_assert(static_cast<int>(nsIScreen::ROTATION_0_DEG) ==
+ static_cast<int>(DISPLAY_ORIENTATION_0),
+ "Orientation enums not matched!");
+ static_assert(static_cast<int>(nsIScreen::ROTATION_90_DEG) ==
+ static_cast<int>(DISPLAY_ORIENTATION_90),
+ "Orientation enums not matched!");
+ static_assert(static_cast<int>(nsIScreen::ROTATION_180_DEG) ==
+ static_cast<int>(DISPLAY_ORIENTATION_180),
+ "Orientation enums not matched!");
+ static_assert(static_cast<int>(nsIScreen::ROTATION_270_DEG) ==
+ static_cast<int>(DISPLAY_ORIENTATION_270),
+ "Orientation enums not matched!");
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+
+ uint32_t rotation = nsIScreen::ROTATION_0_DEG;
+ DebugOnly<nsresult> rv = screen->GetRotation(&rotation);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ LayoutDeviceIntRect screenBounds = screen->GetNaturalBounds();
+
+ DisplayViewport viewport;
+ viewport.displayId = 0;
+ viewport.orientation = rotation;
+ viewport.physicalRight = viewport.deviceWidth = screenBounds.width;
+ viewport.physicalBottom = viewport.deviceHeight = screenBounds.height;
+ if (viewport.orientation == DISPLAY_ORIENTATION_90 ||
+ viewport.orientation == DISPLAY_ORIENTATION_270) {
+ viewport.logicalRight = screenBounds.height;
+ viewport.logicalBottom = screenBounds.width;
+ } else {
+ viewport.logicalRight = screenBounds.width;
+ viewport.logicalBottom = screenBounds.height;
+ }
+ mConfig.setDisplayInfo(false, viewport);
+}
+
+void GeckoInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig)
+{
+ *outConfig = mConfig;
+}
+
+
+// GeckoInputDispatcher
+void
+GeckoInputDispatcher::dump(String8& dump)
+{
+}
+
+static bool
+isExpired(const UserInputData& data)
+{
+ uint64_t timeNowMs =
+ nanosecsToMillisecs(systemTime(SYSTEM_TIME_MONOTONIC));
+ return (timeNowMs - data.timeMs) > kInputExpirationThresholdMs;
+}
+
+void
+GeckoInputDispatcher::dispatchOnce()
+{
+ UserInputData data;
+ {
+ MutexAutoLock lock(mQueueLock);
+ if (mEventQueue.empty())
+ return;
+ data = mEventQueue.front();
+ mEventQueue.pop();
+ if (!mEventQueue.empty())
+ gAppShell->NotifyNativeEvent();
+ }
+
+ switch (data.type) {
+ case UserInputData::MOTION_DATA: {
+ MOZ_ASSERT_UNREACHABLE("Should not dispatch touch events here anymore");
+ break;
+ }
+ case UserInputData::KEY_DATA: {
+ if (!mKeyDownCount) {
+ // No pending events, the filter state can be updated.
+ mKeyEventsFiltered = isExpired(data);
+ }
+
+ mKeyDownCount += (data.action == AKEY_EVENT_ACTION_DOWN) ? 1 : -1;
+ if (mKeyEventsFiltered) {
+ return;
+ }
+
+ sp<KeyCharacterMap> kcm = mEventHub->getKeyCharacterMap(data.deviceId);
+ KeyEventDispatcher dispatcher(data, kcm.get());
+ dispatcher.Dispatch();
+ break;
+ }
+ }
+ MutexAutoLock lock(mQueueLock);
+ if (mPowerWakelock && mEventQueue.empty()) {
+ release_wake_lock(kKey_WAKE_LOCK_ID);
+ mPowerWakelock = false;
+ }
+}
+
+void
+GeckoInputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs*)
+{
+ gAppShell->CheckPowerKey();
+}
+
+void
+GeckoInputDispatcher::notifyKey(const NotifyKeyArgs* args)
+{
+ UserInputData data;
+ data.timeMs = nanosecsToMillisecs(args->eventTime);
+ data.type = UserInputData::KEY_DATA;
+ data.action = args->action;
+ data.flags = args->flags;
+ data.metaState = args->metaState;
+ data.deviceId = args->deviceId;
+ data.key.keyCode = args->keyCode;
+ data.key.scanCode = args->scanCode;
+ {
+ MutexAutoLock lock(mQueueLock);
+ mEventQueue.push(data);
+ if (!mPowerWakelock) {
+ mPowerWakelock =
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, kKey_WAKE_LOCK_ID);
+ }
+ }
+ gAppShell->NotifyNativeEvent();
+}
+
+static void
+addMultiTouch(MultiTouchInput& aMultiTouch,
+ const NotifyMotionArgs* args, int aIndex)
+{
+ int32_t id = args->pointerProperties[aIndex].id;
+ PointerCoords coords = args->pointerCoords[aIndex];
+ float force = coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+
+ float orientation = coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+ float rotationAngle = orientation * 180 / M_PI;
+ if (rotationAngle == 90) {
+ rotationAngle = -90;
+ }
+
+ float radiusX, radiusY;
+ if (rotationAngle < 0) {
+ radiusX = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR) / 2;
+ radiusY = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR) / 2;
+ rotationAngle += 90;
+ } else {
+ radiusX = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR) / 2;
+ radiusY = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR) / 2;
+ }
+
+ ScreenIntPoint point = ScreenIntPoint::Round(coords.getX(),
+ coords.getY());
+
+ SingleTouchData touchData(id, point, ScreenSize(radiusX, radiusY),
+ rotationAngle, force);
+
+ aMultiTouch.mTouches.AppendElement(touchData);
+}
+
+void
+GeckoInputDispatcher::notifyMotion(const NotifyMotionArgs* args)
+{
+ uint32_t time = nanosecsToMillisecs(args->eventTime);
+ int32_t action = args->action & AMOTION_EVENT_ACTION_MASK;
+ int touchCount = args->pointerCount;
+ MOZ_ASSERT(touchCount <= MAX_POINTERS);
+ TimeStamp timestamp = mozilla::TimeStamp::FromSystemTime(args->eventTime);
+ Modifiers modifiers = getDOMModifiers(args->metaState);
+
+ MultiTouchInput::MultiTouchType touchType = MultiTouchInput::MULTITOUCH_CANCEL;
+ switch (action) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ touchType = MultiTouchInput::MULTITOUCH_START;
+ break;
+ case AMOTION_EVENT_ACTION_MOVE:
+ touchType = MultiTouchInput::MULTITOUCH_MOVE;
+ break;
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ touchType = MultiTouchInput::MULTITOUCH_END;
+ break;
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ touchType = MultiTouchInput::MULTITOUCH_CANCEL;
+ break;
+ case AMOTION_EVENT_ACTION_HOVER_EXIT:
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ NS_WARNING("Ignoring hover touch events");
+ return;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Could not assign a touch type");
+ break;
+ }
+
+ MultiTouchInput touchData(touchType, time, timestamp, modifiers);
+
+ // For touch ends, we have to filter out which finger is actually
+ // the touch end since the touch array has all fingers, not just the touch
+ // that we want to end
+ if (touchType == MultiTouchInput::MULTITOUCH_END) {
+ int touchIndex = args->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK;
+ touchIndex >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ addMultiTouch(touchData, args, touchIndex);
+ } else {
+ for (int32_t i = 0; i < touchCount; ++i) {
+ addMultiTouch(touchData, args, i);
+ }
+ }
+
+ mTouchDispatcher->NotifyTouch(touchData, timestamp);
+}
+
+void GeckoInputDispatcher::notifySwitch(const NotifySwitchArgs* args)
+{
+ if (!sDevInputAudioJack)
+ return;
+
+ bool needSwitchUpdate = false;
+
+ if (args->switchMask & (1 << SW_HEADPHONE_INSERT)) {
+ sHeadphoneState = (args->switchValues & (1 << SW_HEADPHONE_INSERT)) ?
+ AKEY_STATE_DOWN : AKEY_STATE_UP;
+ needSwitchUpdate = true;
+ }
+
+ if (args->switchMask & (1 << SW_MICROPHONE_INSERT)) {
+ sMicrophoneState = (args->switchValues & (1 << SW_MICROPHONE_INSERT)) ?
+ AKEY_STATE_DOWN : AKEY_STATE_UP;
+ needSwitchUpdate = true;
+ }
+
+ if (needSwitchUpdate)
+ updateHeadphoneSwitch();
+}
+
+void GeckoInputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args)
+{
+}
+
+int32_t GeckoInputDispatcher::injectInputEvent(
+ const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode,
+ int32_t timeoutMillis, uint32_t policyFlags)
+{
+ return INPUT_EVENT_INJECTION_SUCCEEDED;
+}
+
+void
+GeckoInputDispatcher::setInputWindows(const android::Vector<sp<InputWindowHandle> >& inputWindowHandles)
+{
+}
+
+void
+GeckoInputDispatcher::setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle)
+{
+}
+
+void
+GeckoInputDispatcher::setInputDispatchMode(bool enabled, bool frozen)
+{
+}
+
+status_t
+GeckoInputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor)
+{
+ return OK;
+}
+
+status_t
+GeckoInputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel)
+{
+ return OK;
+}
+
+nsAppShell::nsAppShell()
+ : mNativeCallbackRequest(false)
+ , mEnableDraw(false)
+ , mHandlers()
+ , mPowerKeyChecked(false)
+{
+ gAppShell = this;
+ if (XRE_IsParentProcess()) {
+ Preferences::SetCString("b2g.safe_mode", "unset");
+ }
+}
+
+nsAppShell::~nsAppShell()
+{
+ // mReaderThread and mEventHub will both be null if InitInputDevices
+ // is not called.
+ if (mReaderThread.get()) {
+ // We separate requestExit() and join() here so we can wake the EventHub's
+ // input loop, and stop it from polling for input events
+ mReaderThread->requestExit();
+ mEventHub->wake();
+
+ status_t result = mReaderThread->requestExitAndWait();
+ if (result)
+ LOG("Could not stop reader thread - %d", result);
+ }
+ gAppShell = nullptr;
+}
+
+nsresult
+nsAppShell::Init()
+{
+ nsresult rv = nsBaseAppShell::Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ epollfd = epoll_create(16);
+ NS_ENSURE_TRUE(epollfd >= 0, NS_ERROR_UNEXPECTED);
+
+ int ret = pipe2(signalfds, O_NONBLOCK);
+ NS_ENSURE_FALSE(ret, NS_ERROR_UNEXPECTED);
+
+ rv = AddFdHandler(signalfds[0], pipeHandler, "");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ InitGonkMemoryPressureMonitoring();
+
+ if (XRE_IsParentProcess()) {
+ printf("*****************************************************************\n");
+ printf("***\n");
+ printf("*** This is stdout. Most of the useful output will be in logcat.\n");
+ printf("***\n");
+ printf("*****************************************************************\n");
+ GonkPermissionService::instantiate();
+
+ // Causes the kernel timezone to be set, which in turn causes the
+ // timestamps on SD cards to have the local time rather than UTC time.
+ hal::SetTimezone(hal::GetTimezone());
+ }
+
+ nsCOMPtr<nsIObserverService> obsServ = GetObserverService();
+ if (obsServ) {
+ obsServ->AddObserver(this, "browser-ui-startup-complete", false);
+ obsServ->AddObserver(this, "network-connection-state-changed", false);
+ }
+
+ // Delay initializing input devices until the screen has been
+ // initialized (and we know the resolution).
+ return rv;
+}
+
+void
+nsAppShell::CheckPowerKey()
+{
+ if (mPowerKeyChecked) {
+ return;
+ }
+
+ uint32_t deviceId = 0;
+ int32_t powerState = AKEY_STATE_UNKNOWN;
+
+ // EventHub doesn't report the number of devices.
+ while (powerState != AKEY_STATE_DOWN && deviceId < 32) {
+ powerState = mEventHub->getKeyCodeState(deviceId++, AKEYCODE_POWER);
+ }
+
+ // If Power is pressed while we startup, mark safe mode.
+ // Consumers of the b2g.safe_mode preference need to listen on this
+ // preference change to prevent startup races.
+ nsCOMPtr<nsIRunnable> prefSetter =
+ NS_NewRunnableFunction([powerState] () -> void {
+ Preferences::SetCString("b2g.safe_mode",
+ (powerState == AKEY_STATE_DOWN) ? "yes" : "no");
+ });
+ NS_DispatchToMainThread(prefSetter.forget());
+
+ mPowerKeyChecked = true;
+}
+
+NS_IMETHODIMP
+nsAppShell::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp(aTopic, "network-connection-state-changed")) {
+ NS_ConvertUTF16toUTF8 type(aData);
+ if (!type.IsEmpty()) {
+ hal::NotifyNetworkChange(hal::NetworkInformation(atoi(type.get()), 0, 0));
+ }
+ return NS_OK;
+ } else if (!strcmp(aTopic, "browser-ui-startup-complete")) {
+ if (sDevInputAudioJack) {
+ sHeadphoneState = mReader->getSwitchState(-1, AINPUT_SOURCE_SWITCH, SW_HEADPHONE_INSERT);
+ sMicrophoneState = mReader->getSwitchState(-1, AINPUT_SOURCE_SWITCH, SW_MICROPHONE_INSERT);
+ updateHeadphoneSwitch();
+ }
+ mEnableDraw = true;
+
+ // System is almost booting up. Stop the bootAnim now.
+ StopBootAnimation();
+
+ NotifyEvent();
+ return NS_OK;
+ }
+
+ return nsBaseAppShell::Observe(aSubject, aTopic, aData);
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit()
+{
+ OrientationObserver::ShutDown();
+ nsCOMPtr<nsIObserverService> obsServ = GetObserverService();
+ if (obsServ) {
+ obsServ->RemoveObserver(this, "browser-ui-startup-complete");
+ obsServ->RemoveObserver(this, "network-connection-state-changed");
+ }
+ return nsBaseAppShell::Exit();
+}
+
+void
+nsAppShell::InitInputDevices()
+{
+ sDevInputAudioJack = hal::IsHeadphoneEventFromInputDev();
+ sHeadphoneState = AKEY_STATE_UNKNOWN;
+ sMicrophoneState = AKEY_STATE_UNKNOWN;
+
+ mEventHub = new EventHub();
+ mReaderPolicy = new GeckoInputReaderPolicy();
+ mReaderPolicy->setDisplayInfo();
+ mDispatcher = new GeckoInputDispatcher(mEventHub);
+
+ mReader = new InputReader(mEventHub, mReaderPolicy, mDispatcher);
+ mReaderThread = new InputReaderThread(mReader);
+
+ status_t result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ LOG("Failed to initialize InputReader thread, bad things are going to happen...");
+ }
+}
+
+nsresult
+nsAppShell::AddFdHandler(int fd, FdHandlerCallback handlerFunc,
+ const char* deviceName)
+{
+ epoll_event event = {
+ EPOLLIN,
+ { 0 }
+ };
+
+ FdHandler *handler = mHandlers.AppendElement();
+ handler->fd = fd;
+ strncpy(handler->name, deviceName, sizeof(handler->name) - 1);
+ handler->func = handlerFunc;
+ event.data.u32 = mHandlers.Length() - 1;
+ return epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) ?
+ NS_ERROR_UNEXPECTED : NS_OK;
+}
+
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ mNativeCallbackRequest = true;
+ NotifyEvent();
+}
+
+bool
+nsAppShell::ProcessNextNativeEvent(bool mayWait)
+{
+ PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent",
+ js::ProfileEntry::Category::EVENTS);
+
+ epoll_event events[16] = {{ 0 }};
+
+ int event_count;
+ {
+ PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent::Wait",
+ js::ProfileEntry::Category::EVENTS);
+
+ if ((event_count = epoll_wait(epollfd, events, 16, mayWait ? -1 : 0)) <= 0)
+ return true;
+ }
+
+ for (int i = 0; i < event_count; i++)
+ mHandlers[events[i].data.u32].run();
+
+ if (mDispatcher.get())
+ mDispatcher->dispatchOnce();
+
+ // NativeEventCallback always schedules more if it needs it
+ // so we can coalesce these.
+ // See the implementation in nsBaseAppShell.cpp for more info
+ if (mNativeCallbackRequest) {
+ mNativeCallbackRequest = false;
+ NativeEventCallback();
+ }
+
+ if (gDrawRequest && mEnableDraw) {
+ gDrawRequest = false;
+ nsWindow::DoDraw();
+ }
+
+ return true;
+}
+
+void
+nsAppShell::NotifyNativeEvent()
+{
+ write(signalfds[1], "w", 1);
+}
+
+/* static */ void
+nsAppShell::NotifyScreenInitialized()
+{
+ gAppShell->InitInputDevices();
+
+ // Getting the instance of OrientationObserver to initialize it.
+ OrientationObserver::GetInstance();
+}
+
+/* static */ void
+nsAppShell::NotifyScreenRotation()
+{
+ gAppShell->mReaderPolicy->setDisplayInfo();
+ gAppShell->mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+ hal::NotifyScreenConfigurationChange(screen->GetConfiguration());
+}
diff --git a/widget/gonk/nsAppShell.h b/widget/gonk/nsAppShell.h
new file mode 100644
index 000000000..046a99ea1
--- /dev/null
+++ b/widget/gonk/nsAppShell.h
@@ -0,0 +1,112 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef nsAppShell_h
+#define nsAppShell_h
+
+#include <queue>
+
+#include "mozilla/Mutex.h"
+#include "nsBaseAppShell.h"
+#include "nsTArray.h"
+
+#include "utils/RefBase.h"
+
+namespace mozilla {
+bool ProcessNextEvent();
+void NotifyEvent();
+}
+
+extern bool gDrawRequest;
+
+class FdHandler;
+typedef void(*FdHandlerCallback)(int, FdHandler *);
+
+class FdHandler {
+public:
+ FdHandler()
+ {
+ memset(name, 0, sizeof(name));
+ }
+
+ int fd;
+ char name[64];
+ FdHandlerCallback func;
+ void run()
+ {
+ func(fd, this);
+ }
+};
+
+namespace android {
+class EventHub;
+class InputReader;
+class InputReaderThread;
+}
+
+class GeckoInputReaderPolicy;
+class GeckoInputDispatcher;
+
+class nsAppShell : public nsBaseAppShell {
+public:
+ nsAppShell();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+ NS_IMETHOD Exit() override;
+
+ virtual bool ProcessNextNativeEvent(bool maywait);
+
+ void NotifyNativeEvent();
+
+ static void NotifyScreenInitialized();
+ static void NotifyScreenRotation();
+
+ void CheckPowerKey();
+
+protected:
+ virtual ~nsAppShell();
+
+ virtual void ScheduleNativeEventCallback();
+
+private:
+ nsresult AddFdHandler(int fd, FdHandlerCallback handlerFunc,
+ const char* deviceName);
+ void InitInputDevices();
+
+ // This is somewhat racy but is perfectly safe given how the callback works
+ bool mNativeCallbackRequest;
+
+ // This gets flipped when we observe a browser-ui-startup-complete.
+ // browser-ui-startup-complete means that we're really ready to draw
+ // and can stop the boot animation
+ bool mEnableDraw;
+ nsTArray<FdHandler> mHandlers;
+
+ android::sp<android::EventHub> mEventHub;
+ android::sp<GeckoInputReaderPolicy> mReaderPolicy;
+ android::sp<GeckoInputDispatcher> mDispatcher;
+ android::sp<android::InputReader> mReader;
+ android::sp<android::InputReaderThread> mReaderThread;
+
+ // Guard against checking power key after the first configuration change.
+ bool mPowerKeyChecked;
+};
+
+#endif /* nsAppShell_h */
+
diff --git a/widget/gonk/nsClipboard.cpp b/widget/gonk/nsClipboard.cpp
new file mode 100644
index 000000000..a1eabe8e5
--- /dev/null
+++ b/widget/gonk/nsClipboard.cpp
@@ -0,0 +1,366 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "gfxDrawable.h"
+#include "gfxUtils.h"
+#include "ImageOps.h"
+#include "imgIContainer.h"
+#include "imgTools.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Preferences.h"
+#include "nsArrayUtils.h"
+#include "nsClipboardProxy.h"
+#include "nsISupportsPrimitives.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStringStream.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+using mozilla::dom::ContentChild;
+
+#define LOG_TAG "Clipboard"
+#define LOGI(args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, ## args)
+
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
+
+nsClipboard::nsClipboard()
+ : mClipboard(mozilla::MakeUnique<GonkClipboardData>())
+{
+}
+
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable *aTransferable,
+ nsIClipboardOwner *anOwner,
+ int32_t aWhichClipboard)
+{
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ // Re-direct to the clipboard proxy.
+ RefPtr<nsClipboardProxy> clipboardProxy = new nsClipboardProxy();
+ return clipboardProxy->SetData(aTransferable, anOwner, aWhichClipboard);
+ }
+
+ // Clear out the clipboard in order to set the new data.
+ EmptyClipboard(aWhichClipboard);
+
+ // Use a pref to toggle rich text/non-text support.
+ if (Preferences::GetBool("clipboard.plainTextOnly")) {
+ nsCOMPtr<nsISupports> clip;
+ uint32_t len;
+ nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
+ getter_AddRefs(clip),
+ &len);
+ 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;
+ }
+
+ // Get the types of supported flavors.
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (!flavorList || NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t flavorCount = 0;
+ flavorList->GetLength(&flavorCount);
+ bool imageAdded = false;
+ for (uint32_t i = 0; i < flavorCount; ++i) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+
+ if (currentFlavor) {
+ // MIME type
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // Clip is the data which will be sent to the clipboard.
+ nsCOMPtr<nsISupports> clip;
+ uint32_t len;
+
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ // text/plain
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(clip), &len);
+ nsCOMPtr<nsISupportsString> wideString = do_QueryInterface(clip);
+ if (!wideString || NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsAutoString utf16string;
+ wideString->GetData(utf16string);
+ mClipboard->SetText(utf16string);
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ // text/html
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(clip), &len);
+ nsCOMPtr<nsISupportsString> wideString = do_QueryInterface(clip);
+ if (!wideString || NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsAutoString utf16string;
+ wideString->GetData(utf16string);
+ mClipboard->SetHTML(utf16string);
+ } else if (!imageAdded && // image is added only once to the clipboard.
+ (flavorStr.EqualsLiteral(kNativeImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime))) {
+ // image/[png|jpeg|jpg] or application/x-moz-nativeimage
+
+ // Look through our transfer data for the image.
+ static const char* const imageMimeTypes[] = {
+ kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime };
+
+ nsCOMPtr<nsISupportsInterfacePointer> imgPtr;
+ for (uint32_t i = 0; !imgPtr && i < ArrayLength(imageMimeTypes); ++i) {
+ aTransferable->GetTransferData(imageMimeTypes[i], getter_AddRefs(clip), &len);
+ imgPtr = do_QueryInterface(clip);
+ }
+ if (!imgPtr) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> imageData;
+ imgPtr->GetData(getter_AddRefs(imageData));
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(imageData));
+ if (!image) {
+ continue;
+ }
+
+ RefPtr<gfx::SourceSurface> surface =
+ image->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ continue;
+ }
+
+ RefPtr<gfx::DataSourceSurface> dataSurface;
+ if (surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8) {
+ dataSurface = surface->GetDataSurface();
+ } else {
+ // Convert format to SurfaceFormat::B8G8R8A8.
+ dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(surface, gfx::SurfaceFormat::B8G8R8A8);
+ }
+
+ mClipboard->SetImage(dataSurface);
+ imageAdded = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable *aTransferable,
+ int32_t aWhichClipboard)
+{
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ // Re-direct to the clipboard proxy.
+ RefPtr<nsClipboardProxy> clipboardProxy = new nsClipboardProxy();
+ return clipboardProxy->GetData(aTransferable, aWhichClipboard);
+ }
+
+ // Use a pref to toggle rich text/non-text support.
+ if (Preferences::GetBool("clipboard.plainTextOnly")) {
+ 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);
+ uint32_t len = mClipboard->GetText().Length() * sizeof(char16_t);
+ rv = aTransferable->SetTransferData(kUnicodeMime, genericDataWrapper, len);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ // Get flavor list that includes all acceptable flavors (including
+ // ones obtained through conversion).
+ // Note: We don't need to call nsITransferable::AddDataFlavor here
+ // because ContentParent already did.
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+
+ if (!flavorList || NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Walk through flavors and see which flavor matches the one being pasted.
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+
+ for (uint32_t i = 0; i < flavorCount; ++i) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+
+ if (currentFlavor) {
+ // flavorStr is the mime type.
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // text/plain, text/Unicode
+ if (flavorStr.EqualsLiteral(kUnicodeMime) && mClipboard->HasText()) {
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> dataWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ rv = dataWrapper->SetData(mClipboard->GetText());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper = do_QueryInterface(dataWrapper);
+ uint32_t len = mClipboard->GetText().Length() * sizeof(char16_t);
+ rv = aTransferable->SetTransferData(flavorStr, genericDataWrapper, len);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ break;
+ }
+
+ // text/html
+ if (flavorStr.EqualsLiteral(kHTMLMime) && mClipboard->HasHTML()) {
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> dataWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ rv = dataWrapper->SetData(mClipboard->GetHTML());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper = do_QueryInterface(dataWrapper);
+ uint32_t len = mClipboard->GetHTML().Length() * sizeof(char16_t);
+ rv = aTransferable->SetTransferData(flavorStr, genericDataWrapper, len);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ break;
+ }
+
+ // image/[png|jpeg|jpg]
+ if ((flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime)) &&
+ mClipboard->HasImage() ) {
+ // Get image buffer from clipboard.
+ RefPtr<gfx::DataSourceSurface> image = mClipboard->GetImage();
+
+ // Encode according to MIME type.
+ RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, image->GetSize());
+ nsCOMPtr<imgIContainer> imageContainer(image::ImageOps::CreateFromDrawable(drawable));
+ nsCOMPtr<imgITools> imgTool = do_GetService(NS_IMGTOOLS_CID);
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ nsresult rv = imgTool->EncodeImage(imageContainer,
+ flavorStr,
+ EmptyString(),
+ getter_AddRefs(byteStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ // Set transferable.
+ rv = aTransferable->SetTransferData(flavorStr,
+ byteStream,
+ sizeof(nsIInputStream*));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ if (XRE_IsParentProcess()) {
+ mClipboard->Clear();
+ } else {
+ ContentChild::GetSingleton()->SendEmptyClipboard(aWhichClipboard);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const char **aFlavorList,
+ uint32_t aLength, int32_t aWhichClipboard,
+ bool *aHasType)
+{
+ *aHasType = false;
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ if (XRE_IsParentProcess()) {
+ // Retrieve the union of all aHasType in aFlavorList
+ for (uint32_t i = 0; i < aLength; ++i) {
+ const char *flavor = aFlavorList[i];
+ if (!flavor) {
+ continue;
+ }
+ if (!strcmp(flavor, kUnicodeMime) && mClipboard->HasText()) {
+ *aHasType = true;
+ } else if (!strcmp(flavor, kHTMLMime) && mClipboard->HasHTML()) {
+ *aHasType = true;
+ } else if (!strcmp(flavor, kJPEGImageMime) ||
+ !strcmp(flavor, kJPGImageMime) ||
+ !strcmp(flavor, kPNGImageMime)) {
+ // We will encode the image into any format you want, so we don't
+ // need to check each specific format
+ if (mClipboard->HasImage()) {
+ *aHasType = true;
+ }
+ }
+ }
+ } else {
+ RefPtr<nsClipboardProxy> clipboardProxy = new nsClipboardProxy();
+ return clipboardProxy->HasDataMatchingFlavors(aFlavorList, aLength, aWhichClipboard, aHasType);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool *aIsSupported)
+{
+ *aIsSupported = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool* _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = false;
+ return NS_OK;
+}
+
diff --git a/widget/gonk/nsClipboard.h b/widget/gonk/nsClipboard.h
new file mode 100644
index 000000000..cd2e0dbd5
--- /dev/null
+++ b/widget/gonk/nsClipboard.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 nsClipbard_h__
+#define nsClipbard_h__
+
+#include "GonkClipboardData.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIClipboard.h"
+
+class nsClipboard final : public nsIClipboard
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ nsClipboard();
+
+protected:
+ ~nsClipboard() {}
+
+private:
+ mozilla::UniquePtr<mozilla::GonkClipboardData> mClipboard;
+};
+
+#endif
diff --git a/widget/gonk/nsIdleServiceGonk.cpp b/widget/gonk/nsIdleServiceGonk.cpp
new file mode 100644
index 000000000..dc7588e5e
--- /dev/null
+++ b/widget/gonk/nsIdleServiceGonk.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4: */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nsIdleServiceGonk.h"
+#include "nsIServiceManager.h"
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceGonk, nsIdleService)
+
+bool
+nsIdleServiceGonk::PollIdleTime(uint32_t *aIdleTime)
+{
+ return false;
+}
+
+bool
+nsIdleServiceGonk::UsePollMode()
+{
+ return false;
+}
diff --git a/widget/gonk/nsIdleServiceGonk.h b/widget/gonk/nsIdleServiceGonk.h
new file mode 100644
index 000000000..32520dce6
--- /dev/null
+++ b/widget/gonk/nsIdleServiceGonk.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4: */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef nsIdleServiceGonk_h__
+#define nsIdleServiceGonk_h__
+
+#include "nsIdleService.h"
+
+class nsIdleServiceGonk : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool PollIdleTime(uint32_t* aIdleTime);
+
+ static already_AddRefed<nsIdleServiceGonk> GetInstance()
+ {
+ RefPtr<nsIdleServiceGonk> idleService =
+ nsIdleService::GetInstance().downcast<nsIdleServiceGonk>();
+ if (!idleService) {
+ idleService = new nsIdleServiceGonk();
+ }
+
+ return idleService.forget();
+ }
+
+protected:
+ nsIdleServiceGonk() { }
+ virtual ~nsIdleServiceGonk() { }
+ bool UsePollMode();
+};
+
+#endif // nsIdleServiceGonk_h__
diff --git a/widget/gonk/nsLookAndFeel.cpp b/widget/gonk/nsLookAndFeel.cpp
new file mode 100644
index 000000000..120f257df
--- /dev/null
+++ b/widget/gonk/nsLookAndFeel.cpp
@@ -0,0 +1,465 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nsLookAndFeel.h"
+#include "nsStyleConsts.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "mozilla/gfx/2D.h"
+#include "cutils/properties.h"
+
+static const char16_t UNICODE_BULLET = 0x2022;
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel()
+{
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+}
+
+nsresult
+nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
+{
+ nsresult rv = NS_OK;
+
+#define BASE_ACTIVE_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define BASE_NORMAL_COLOR NS_RGB(0xff,0xff,0xff)
+#define BASE_SELECTED_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define BG_ACTIVE_COLOR NS_RGB(0xff,0xff,0xff)
+#define BG_INSENSITIVE_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define BG_NORMAL_COLOR NS_RGB(0xff,0xff,0xff)
+#define BG_PRELIGHT_COLOR NS_RGB(0xee,0xee,0xee)
+#define BG_SELECTED_COLOR NS_RGB(0x99,0x99,0x99)
+#define DARK_NORMAL_COLOR NS_RGB(0x88,0x88,0x88)
+#define FG_INSENSITIVE_COLOR NS_RGB(0x44,0x44,0x44)
+#define FG_NORMAL_COLOR NS_RGB(0x00,0x00,0x00)
+#define FG_PRELIGHT_COLOR NS_RGB(0x77,0x77,0x77)
+#define FG_SELECTED_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define LIGHT_NORMAL_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define TEXT_ACTIVE_COLOR NS_RGB(0x99,0x99,0x99)
+#define TEXT_NORMAL_COLOR NS_RGB(0x00,0x00,0x00)
+#define TEXT_SELECTED_COLOR NS_RGB(0x00,0x00,0x00)
+
+ 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 eColorID_WindowBackground:
+ aColor = BASE_NORMAL_COLOR;
+ break;
+ case eColorID_WindowForeground:
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID_WidgetBackground:
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_WidgetForeground:
+ aColor = FG_NORMAL_COLOR;
+ break;
+ case eColorID_WidgetSelectBackground:
+ aColor = BG_SELECTED_COLOR;
+ break;
+ case eColorID_WidgetSelectForeground:
+ aColor = FG_SELECTED_COLOR;
+ break;
+ case eColorID_Widget3DHighlight:
+ aColor = NS_RGB(0xa0,0xa0,0xa0);
+ break;
+ case eColorID_Widget3DShadow:
+ aColor = NS_RGB(0x40,0x40,0x40);
+ break;
+ case eColorID_TextBackground:
+ // not used?
+ aColor = BASE_NORMAL_COLOR;
+ break;
+ case eColorID_TextForeground:
+ // not used?
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID_TextSelectBackground:
+ aColor = NS_RGBA(0x33,0xb5,0xe5,0x66);
+ break;
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ // still used
+ aColor = BASE_SELECTED_COLOR;
+ break;
+ case eColorID_TextSelectForegroundCustom:
+ aColor = NS_RGB(0x4d,0x4d,0x4d);
+ break;
+ case eColorID_TextSelectForeground:
+ aColor = NS_CHANGE_COLOR_IF_SAME_AS_BG;
+ break;
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ // still used
+ aColor = TEXT_SELECTED_COLOR;
+ break;
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ break;
+
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case eColorID_activeborder:
+ // active window border
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_activecaption:
+ // active window caption background
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_appworkspace:
+ // MDI background color
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_background:
+ // desktop background
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_captiontext:
+ // text in active window caption, size box, and scrollbar arrow box (!)
+ aColor = FG_NORMAL_COLOR;
+ break;
+ case eColorID_graytext:
+ // disabled text in windows, menus, etc.
+ aColor = FG_INSENSITIVE_COLOR;
+ break;
+ case eColorID_highlight:
+ // background of selected item
+ aColor = BASE_SELECTED_COLOR;
+ break;
+ case eColorID_highlighttext:
+ // text of selected item
+ aColor = TEXT_SELECTED_COLOR;
+ break;
+ case eColorID_inactiveborder:
+ // inactive window border
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_inactivecaption:
+ // inactive window caption
+ aColor = BG_INSENSITIVE_COLOR;
+ break;
+ case eColorID_inactivecaptiontext:
+ // text in inactive window caption
+ aColor = FG_INSENSITIVE_COLOR;
+ break;
+ case eColorID_infobackground:
+ // tooltip background color
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_infotext:
+ // tooltip text color
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID_menu:
+ // menu background
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_menutext:
+ // menu text
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID_scrollbar:
+ // scrollbar gray area
+ aColor = BG_ACTIVE_COLOR;
+ break;
+
+ case eColorID_threedface:
+ case eColorID_buttonface:
+ // 3-D face color
+ aColor = BG_NORMAL_COLOR;
+ break;
+
+ case eColorID_buttontext:
+ // text on push buttons
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+
+ case eColorID_buttonhighlight:
+ // 3-D highlighted edge color
+ case eColorID_threedhighlight:
+ // 3-D highlighted outer edge color
+ aColor = LIGHT_NORMAL_COLOR;
+ break;
+
+ case eColorID_threedlightshadow:
+ // 3-D highlighted inner edge color
+ aColor = BG_NORMAL_COLOR;
+ break;
+
+ case eColorID_buttonshadow:
+ // 3-D shadow edge color
+ case eColorID_threedshadow:
+ // 3-D shadow inner edge color
+ aColor = DARK_NORMAL_COLOR;
+ break;
+
+ case eColorID_threeddarkshadow:
+ // 3-D shadow outer edge color
+ aColor = NS_RGB(0,0,0);
+ break;
+
+ case eColorID_window:
+ case eColorID_windowframe:
+ aColor = BG_NORMAL_COLOR;
+ break;
+
+ case eColorID_windowtext:
+ aColor = FG_NORMAL_COLOR;
+ break;
+
+ case eColorID__moz_eventreerow:
+ case eColorID__moz_field:
+ aColor = BASE_NORMAL_COLOR;
+ break;
+ case eColorID__moz_fieldtext:
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID__moz_dialog:
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID__moz_dialogtext:
+ aColor = FG_NORMAL_COLOR;
+ break;
+ case eColorID__moz_dragtargetzone:
+ aColor = BG_SELECTED_COLOR;
+ break;
+ case eColorID__moz_buttondefault:
+ // default button border color
+ aColor = NS_RGB(0,0,0);
+ break;
+ case eColorID__moz_buttonhoverface:
+ aColor = BG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_buttonhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ aColor = BASE_ACTIVE_COLOR;
+ break;
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aColor = TEXT_ACTIVE_COLOR;
+ break;
+ case eColorID__moz_menuhover:
+ aColor = BG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_menuhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_oddtreerow:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID__moz_comboboxtext:
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID__moz_combobox:
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID__moz_menubartext:
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID__moz_menubarhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ default:
+ /* default color is BLACK */
+ aColor = 0;
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return rv;
+}
+
+nsresult
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ nsresult rv = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ rv = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ aResult = 500;
+ break;
+
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+
+ case eIntID_SubmenuDelay:
+ aResult = 200;
+ break;
+
+ case eIntID_TooltipDelay:
+ aResult = 500;
+ break;
+
+ case eIntID_MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+
+ case eIntID_ScrollArrowStyle:
+ aResult = eScrollArrowStyle_Single;
+ break;
+
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+
+ case eIntID_TouchEnabled:
+ aResult = 1;
+ break;
+
+ case eIntID_WindowsDefaultTheme:
+ case eIntID_WindowsThemeIdentifier:
+ case eIntID_OperatingSystemVersionIdentifier:
+ aResult = 0;
+ rv = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+
+ case eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ break;
+
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+
+ case eIntID_ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+
+ case eIntID_PhysicalHomeButton: {
+ char propValue[PROPERTY_VALUE_MAX];
+ property_get("ro.moz.has_home_button", propValue, "1");
+ aResult = atoi(propValue);
+ break;
+ }
+
+ case eIntID_ContextMenuOffsetVertical:
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+
+ default:
+ aResult = 0;
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+/*virtual*/
+bool
+nsLookAndFeel::GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ aFontName.AssignLiteral("\"Fira Sans\"");
+ aFontStyle.style = NS_FONT_STYLE_NORMAL;
+ aFontStyle.weight = NS_FONT_WEIGHT_NORMAL;
+ aFontStyle.stretch = NS_FONT_STRETCH_NORMAL;
+ aFontStyle.size = 9.0 * 96.0f / 72.0f;
+ aFontStyle.systemFont = true;
+ return true;
+}
+
+/*virtual*/
+bool
+nsLookAndFeel::GetEchoPasswordImpl() {
+ return true;
+}
+
+/*virtual*/
+uint32_t
+nsLookAndFeel::GetPasswordMaskDelayImpl()
+{
+ // Same value on Android framework
+ return 1500;
+}
+
+/* virtual */
+char16_t
+nsLookAndFeel::GetPasswordCharacterImpl()
+{
+ return UNICODE_BULLET;
+}
diff --git a/widget/gonk/nsLookAndFeel.h b/widget/gonk/nsLookAndFeel.h
new file mode 100644
index 000000000..aa7dce823
--- /dev/null
+++ b/widget/gonk/nsLookAndFeel.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "nsXPLookAndFeel.h"
+
+class nsLookAndFeel : public nsXPLookAndFeel
+{
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual bool GetFontImpl(FontID aID, nsString& aName, gfxFontStyle& aStyle,
+ float aDevPixPerCSSPixel);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetEchoPasswordImpl();
+ virtual uint32_t GetPasswordMaskDelayImpl();
+ virtual char16_t GetPasswordCharacterImpl();
+
+protected:
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aColor);
+};
+
+#endif
diff --git a/widget/gonk/nsScreenManagerGonk.cpp b/widget/gonk/nsScreenManagerGonk.cpp
new file mode 100644
index 000000000..e359fd195
--- /dev/null
+++ b/widget/gonk/nsScreenManagerGonk.cpp
@@ -0,0 +1,1081 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android/log.h"
+#include "GLContext.h"
+#include "gfxPrefs.h"
+#include "gfxUtils.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/Hal.h"
+#include "libdisplay/BootAnimation.h"
+#include "libdisplay/GonkDisplay.h"
+#include "nsScreenManagerGonk.h"
+#include "nsThreadUtils.h"
+#include "HwcComposer2D.h"
+#include "VsyncSource.h"
+#include "nsWindow.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/Services.h"
+#include "mozilla/ProcessPriorityManager.h"
+#include "nsIdleService.h"
+#include "nsIObserverService.h"
+#include "nsAppShell.h"
+#include "nsProxyRelease.h"
+#include "nsTArray.h"
+#include "pixelflinger/format.h"
+#include "nsIDisplayInfo.h"
+#include "base/task.h"
+
+#if ANDROID_VERSION >= 17
+#include "libdisplay/DisplaySurface.h"
+#endif
+
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "nsScreenGonk" , ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "nsScreenGonk", ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "nsScreenGonk", ## args)
+
+using namespace mozilla;
+using namespace mozilla::hal;
+using namespace mozilla::gfx;
+using namespace mozilla::gl;
+using namespace mozilla::layers;
+using namespace mozilla::dom;
+
+namespace {
+
+class ScreenOnOffEvent : public mozilla::Runnable {
+public:
+ ScreenOnOffEvent(bool on)
+ : mIsOn(on)
+ {}
+
+ NS_IMETHOD Run() override {
+ // Notify observers that the screen state has just changed.
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(
+ nullptr, "screen-state-changed",
+ mIsOn ? u"on" : u"off"
+ );
+ }
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+ const nsTArray<nsWindow*>& windows = screen->GetTopWindows();
+
+ for (uint32_t i = 0; i < windows.Length(); i++) {
+ nsWindow *win = windows[i];
+
+ if (nsIWidgetListener* listener = win->GetWidgetListener()) {
+ listener->SizeModeChanged(mIsOn ? nsSizeMode_Fullscreen : nsSizeMode_Minimized);
+ }
+ }
+
+ return NS_OK;
+ }
+
+private:
+ bool mIsOn;
+};
+
+static void
+displayEnabledCallback(bool enabled)
+{
+ RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
+ screenManager->DisplayEnabled(enabled);
+}
+
+} // namespace
+
+static uint32_t
+SurfaceFormatToColorDepth(int32_t aSurfaceFormat)
+{
+ switch (aSurfaceFormat) {
+ case GGL_PIXEL_FORMAT_RGB_565:
+ return 16;
+ case GGL_PIXEL_FORMAT_RGBA_8888:
+ return 32;
+ }
+ return 24; // GGL_PIXEL_FORMAT_RGBX_8888
+}
+
+// nsScreenGonk.cpp
+
+nsScreenGonk::nsScreenGonk(uint32_t aId,
+ GonkDisplay::DisplayType aDisplayType,
+ const GonkDisplay::NativeData& aNativeData,
+ NotifyDisplayChangedEvent aEventVisibility)
+ : mId(aId)
+ , mEventVisibility(aEventVisibility)
+ , mNativeWindow(aNativeData.mNativeWindow)
+ , mDpi(aNativeData.mXdpi)
+ , mScreenRotation(nsIScreen::ROTATION_0_DEG)
+ , mPhysicalScreenRotation(nsIScreen::ROTATION_0_DEG)
+#if ANDROID_VERSION >= 17
+ , mDisplaySurface(aNativeData.mDisplaySurface)
+#endif
+ , mIsMirroring(false)
+ , mDisplayType(aDisplayType)
+ , mEGLDisplay(EGL_NO_DISPLAY)
+ , mEGLSurface(EGL_NO_SURFACE)
+ , mGLContext(nullptr)
+ , mFramebuffer(nullptr)
+ , mMappedBuffer(nullptr)
+{
+ if (mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_WIDTH, &mVirtualBounds.width) ||
+ mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_HEIGHT, &mVirtualBounds.height) ||
+ mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_FORMAT, &mSurfaceFormat)) {
+ NS_RUNTIMEABORT("Failed to get native window size, aborting...");
+ }
+
+ mNaturalBounds = mVirtualBounds;
+
+ if (IsPrimaryScreen()) {
+ char propValue[PROPERTY_VALUE_MAX];
+ property_get("ro.sf.hwrotation", propValue, "0");
+ mPhysicalScreenRotation = atoi(propValue) / 90;
+ }
+
+ mColorDepth = SurfaceFormatToColorDepth(mSurfaceFormat);
+}
+
+static void
+ReleaseGLContextSync(mozilla::gl::GLContext* aGLContext)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ aGLContext->Release();
+}
+
+nsScreenGonk::~nsScreenGonk()
+{
+ // Release GLContext on compositor thread
+ if (mGLContext) {
+ CompositorThreadHolder::Loop()->PostTask(
+ NewRunnableFunction(&ReleaseGLContextSync,
+ mGLContext.forget().take()));
+ mGLContext = nullptr;
+ }
+}
+
+bool
+nsScreenGonk::IsPrimaryScreen()
+{
+ return mDisplayType == GonkDisplay::DISPLAY_PRIMARY;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetId(uint32_t *outId)
+{
+ *outId = mId;
+ return NS_OK;
+}
+
+uint32_t
+nsScreenGonk::GetId()
+{
+ return mId;
+}
+
+NotifyDisplayChangedEvent
+nsScreenGonk::GetEventVisibility()
+{
+ return mEventVisibility;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetRect(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight)
+{
+ *outLeft = mVirtualBounds.x;
+ *outTop = mVirtualBounds.y;
+
+ *outWidth = mVirtualBounds.width;
+ *outHeight = mVirtualBounds.height;
+
+ return NS_OK;
+}
+
+LayoutDeviceIntRect
+nsScreenGonk::GetRect()
+{
+ return mVirtualBounds;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetAvailRect(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight)
+{
+ return GetRect(outLeft, outTop, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetPixelDepth(int32_t *aPixelDepth)
+{
+ // XXX: this should actually return 32 when we're using 24-bit
+ // color, because we use RGBX.
+ *aPixelDepth = mColorDepth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetColorDepth(int32_t *aColorDepth)
+{
+ *aColorDepth = mColorDepth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetRotation(uint32_t* aRotation)
+{
+ *aRotation = mScreenRotation;
+ return NS_OK;
+}
+
+float
+nsScreenGonk::GetDpi()
+{
+ return mDpi;
+}
+
+int32_t
+nsScreenGonk::GetSurfaceFormat()
+{
+ return mSurfaceFormat;
+}
+
+ANativeWindow*
+nsScreenGonk::GetNativeWindow()
+{
+ return mNativeWindow.get();
+}
+
+NS_IMETHODIMP
+nsScreenGonk::SetRotation(uint32_t aRotation)
+{
+ if (!(aRotation <= ROTATION_270_DEG)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (mScreenRotation == aRotation) {
+ return NS_OK;
+ }
+
+ mScreenRotation = aRotation;
+ uint32_t rotation = EffectiveScreenRotation();
+ if (rotation == nsIScreen::ROTATION_90_DEG ||
+ rotation == nsIScreen::ROTATION_270_DEG) {
+ mVirtualBounds = LayoutDeviceIntRect(0, 0,
+ mNaturalBounds.height,
+ mNaturalBounds.width);
+ } else {
+ mVirtualBounds = mNaturalBounds;
+ }
+
+ nsAppShell::NotifyScreenRotation();
+
+ for (unsigned int i = 0; i < mTopWindows.Length(); i++) {
+ mTopWindows[i]->Resize(mVirtualBounds.width,
+ mVirtualBounds.height,
+ true);
+ }
+
+ return NS_OK;
+}
+
+LayoutDeviceIntRect
+nsScreenGonk::GetNaturalBounds()
+{
+ return mNaturalBounds;
+}
+
+uint32_t
+nsScreenGonk::EffectiveScreenRotation()
+{
+ return (mScreenRotation + mPhysicalScreenRotation) % (360 / 90);
+}
+
+// NB: This isn't gonk-specific, but gonk is the only widget backend
+// that does this calculation itself, currently.
+static ScreenOrientationInternal
+ComputeOrientation(uint32_t aRotation, const LayoutDeviceIntSize& aScreenSize)
+{
+ bool naturallyPortrait = (aScreenSize.height > aScreenSize.width);
+ switch (aRotation) {
+ case nsIScreen::ROTATION_0_DEG:
+ return (naturallyPortrait ? eScreenOrientation_PortraitPrimary :
+ eScreenOrientation_LandscapePrimary);
+ case nsIScreen::ROTATION_90_DEG:
+ // Arbitrarily choosing 90deg to be primary "unnatural"
+ // rotation.
+ return (naturallyPortrait ? eScreenOrientation_LandscapePrimary :
+ eScreenOrientation_PortraitPrimary);
+ case nsIScreen::ROTATION_180_DEG:
+ return (naturallyPortrait ? eScreenOrientation_PortraitSecondary :
+ eScreenOrientation_LandscapeSecondary);
+ case nsIScreen::ROTATION_270_DEG:
+ return (naturallyPortrait ? eScreenOrientation_LandscapeSecondary :
+ eScreenOrientation_PortraitSecondary);
+ default:
+ MOZ_CRASH("Gonk screen must always have a known rotation");
+ }
+}
+
+static uint16_t
+RotationToAngle(uint32_t aRotation)
+{
+ uint16_t angle = 90 * aRotation;
+ MOZ_ASSERT(angle == 0 || angle == 90 || angle == 180 || angle == 270);
+ return angle;
+}
+
+ScreenConfiguration
+nsScreenGonk::GetConfiguration()
+{
+ ScreenOrientationInternal orientation =
+ ComputeOrientation(mScreenRotation, mNaturalBounds.Size());
+
+ // NB: perpetuating colorDepth == pixelDepth illusion here, for
+ // consistency.
+ return ScreenConfiguration(mVirtualBounds.ToUnknownRect(), orientation,
+ RotationToAngle(mScreenRotation),
+ mColorDepth, mColorDepth);
+}
+
+void
+nsScreenGonk::RegisterWindow(nsWindow* aWindow)
+{
+ mTopWindows.AppendElement(aWindow);
+}
+
+void
+nsScreenGonk::UnregisterWindow(nsWindow* aWindow)
+{
+ mTopWindows.RemoveElement(aWindow);
+}
+
+void
+nsScreenGonk::BringToTop(nsWindow* aWindow)
+{
+ mTopWindows.RemoveElement(aWindow);
+ mTopWindows.InsertElementAt(0, aWindow);
+}
+
+static gralloc_module_t const*
+gralloc_module()
+{
+ hw_module_t const *module;
+ if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module)) {
+ return nullptr;
+ }
+ return reinterpret_cast<gralloc_module_t const*>(module);
+}
+
+static SurfaceFormat
+HalFormatToSurfaceFormat(int aHalFormat)
+{
+ switch (aHalFormat) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ // Needs RB swap
+ return SurfaceFormat::B8G8R8A8;
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ // Needs RB swap
+ return SurfaceFormat::B8G8R8X8;
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ return SurfaceFormat::B8G8R8A8;
+ case HAL_PIXEL_FORMAT_RGB_565:
+ return SurfaceFormat::R5G6B5_UINT16;
+ default:
+ MOZ_CRASH("Unhandled HAL pixel format");
+ return SurfaceFormat::UNKNOWN; // not reached
+ }
+}
+
+static bool
+NeedsRBSwap(int aHalFormat)
+{
+ switch (aHalFormat) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ return true;
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ return true;
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ return false;
+ case HAL_PIXEL_FORMAT_RGB_565:
+ return false;
+ default:
+ MOZ_CRASH("Unhandled HAL pixel format");
+ return false; // not reached
+ }
+}
+
+already_AddRefed<DrawTarget>
+nsScreenGonk::StartRemoteDrawing()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(!mFramebuffer);
+ MOZ_ASSERT(!mMappedBuffer);
+
+ mFramebuffer = DequeueBuffer();
+ int width = mFramebuffer->width, height = mFramebuffer->height;
+ if (gralloc_module()->lock(gralloc_module(), mFramebuffer->handle,
+ GRALLOC_USAGE_SW_READ_NEVER |
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_FB,
+ 0, 0, width, height,
+ reinterpret_cast<void**>(&mMappedBuffer))) {
+ EndRemoteDrawing();
+ return nullptr;
+ }
+ SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
+ mFramebufferTarget = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO,
+ mMappedBuffer,
+ IntSize(width, height),
+ mFramebuffer->stride * gfx::BytesPerPixel(format),
+ format);
+ if (!mFramebufferTarget) {
+ MOZ_CRASH("nsWindow::StartRemoteDrawing failed in CreateDrawTargetForData");
+ }
+ if (!mBackBuffer ||
+ mBackBuffer->GetSize() != mFramebufferTarget->GetSize() ||
+ mBackBuffer->GetFormat() != mFramebufferTarget->GetFormat()) {
+ mBackBuffer = mFramebufferTarget->CreateSimilarDrawTarget(
+ mFramebufferTarget->GetSize(), mFramebufferTarget->GetFormat());
+ }
+ RefPtr<DrawTarget> buffer(mBackBuffer);
+ return buffer.forget();
+}
+
+void
+nsScreenGonk::EndRemoteDrawing()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ if (mFramebufferTarget && mFramebuffer) {
+ IntSize size = mFramebufferTarget->GetSize();
+ Rect rect(0, 0, size.width, size.height);
+ RefPtr<SourceSurface> source = mBackBuffer->Snapshot();
+ mFramebufferTarget->DrawSurface(source, rect, rect);
+
+ // Convert from BGR to RGB
+ // XXX this is a temporary solution. It consumes extra cpu cycles,
+ // it should not be used on product device.
+ if (NeedsRBSwap(GetSurfaceFormat())) {
+ LOGE("Very slow composition path, it should not be used on product!!!");
+ SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
+ gfxUtils::ConvertBGRAtoRGBA(
+ mMappedBuffer,
+ mFramebuffer->stride * mFramebuffer->height * gfx::BytesPerPixel(format));
+ }
+ }
+ if (mMappedBuffer) {
+ MOZ_ASSERT(mFramebuffer);
+ gralloc_module()->unlock(gralloc_module(), mFramebuffer->handle);
+ mMappedBuffer = nullptr;
+ }
+ if (mFramebuffer) {
+ QueueBuffer(mFramebuffer);
+ }
+ mFramebuffer = nullptr;
+ mFramebufferTarget = nullptr;
+}
+
+ANativeWindowBuffer*
+nsScreenGonk::DequeueBuffer()
+{
+ ANativeWindowBuffer* buf = nullptr;
+#if ANDROID_VERSION >= 17
+ int fenceFd = -1;
+ mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf, &fenceFd);
+ android::sp<android::Fence> fence(new android::Fence(fenceFd));
+#if ANDROID_VERSION == 17
+ fence->waitForever(1000, "nsScreenGonk_DequeueBuffer");
+ // 1000 is what Android uses. It is a warning timeout in ms.
+ // This timeout was removed in ANDROID_VERSION 18.
+#else
+ fence->waitForever("nsScreenGonk_DequeueBuffer");
+#endif
+#else
+ mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf);
+#endif
+ return buf;
+}
+
+bool
+nsScreenGonk::QueueBuffer(ANativeWindowBuffer* buf)
+{
+#if ANDROID_VERSION >= 17
+ int ret = mNativeWindow->queueBuffer(mNativeWindow.get(), buf, -1);
+ return ret == 0;
+#else
+ int ret = mNativeWindow->queueBuffer(mNativeWindow.get(), buf);
+ return ret == 0;
+#endif
+}
+
+nsresult
+nsScreenGonk::MakeSnapshot(ANativeWindowBuffer* aBuffer)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(aBuffer);
+
+ layers::CompositorBridgeParent* compositorParent = mCompositorBridgeParent;
+ if (!compositorParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int width = aBuffer->width, height = aBuffer->height;
+ uint8_t* mappedBuffer = nullptr;
+ if (gralloc_module()->lock(gralloc_module(), aBuffer->handle,
+ GRALLOC_USAGE_SW_READ_OFTEN |
+ GRALLOC_USAGE_SW_WRITE_OFTEN,
+ 0, 0, width, height,
+ reinterpret_cast<void**>(&mappedBuffer))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
+ RefPtr<DrawTarget> mTarget =
+ Factory::CreateDrawTargetForData(
+ BackendType::CAIRO,
+ mappedBuffer,
+ IntSize(width, height),
+ aBuffer->stride * gfx::BytesPerPixel(format),
+ format);
+ if (!mTarget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gfx::IntRect rect = GetRect().ToUnknownRect();
+ compositorParent->ForceComposeToTarget(mTarget, &rect);
+
+ // Convert from BGR to RGB
+ // XXX this is a temporary solution. It consumes extra cpu cycles,
+ if (NeedsRBSwap(GetSurfaceFormat())) {
+ LOGE("Slow path of making Snapshot!!!");
+ SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
+ gfxUtils::ConvertBGRAtoRGBA(
+ mappedBuffer,
+ aBuffer->stride * aBuffer->height * gfx::BytesPerPixel(format));
+ mappedBuffer = nullptr;
+ }
+ gralloc_module()->unlock(gralloc_module(), aBuffer->handle);
+ return NS_OK;
+}
+
+void
+nsScreenGonk::SetCompositorBridgeParent(layers::CompositorBridgeParent* aCompositorBridgeParent)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mCompositorBridgeParent = aCompositorBridgeParent;
+}
+
+#if ANDROID_VERSION >= 17
+android::DisplaySurface*
+nsScreenGonk::GetDisplaySurface()
+{
+ return mDisplaySurface.get();
+}
+
+int
+nsScreenGonk::GetPrevDispAcquireFd()
+{
+ if (!mDisplaySurface.get()) {
+ return -1;
+ }
+ return mDisplaySurface->GetPrevDispAcquireFd();
+}
+#endif
+
+GonkDisplay::DisplayType
+nsScreenGonk::GetDisplayType()
+{
+ return mDisplayType;
+}
+
+void
+nsScreenGonk::SetEGLInfo(hwc_display_t aDisplay,
+ hwc_surface_t aSurface,
+ gl::GLContext* aGLContext)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ mEGLDisplay = aDisplay;
+ mEGLSurface = aSurface;
+ mGLContext = aGLContext;
+}
+
+hwc_display_t
+nsScreenGonk::GetEGLDisplay()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ return mEGLDisplay;
+}
+
+hwc_surface_t
+nsScreenGonk::GetEGLSurface()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ return mEGLSurface;
+}
+
+already_AddRefed<mozilla::gl::GLContext>
+nsScreenGonk::GetGLContext()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RefPtr<mozilla::gl::GLContext>glContext = mGLContext;
+ return glContext.forget();
+}
+
+static void
+UpdateMirroringWidgetSync(nsMainThreadPtrHandle<nsScreenGonk>&& aScreen, nsWindow* aWindow)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ already_AddRefed<nsWindow> window(aWindow);
+ aScreen->UpdateMirroringWidget(window);
+}
+
+bool
+nsScreenGonk::EnableMirroring()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsPrimaryScreen());
+
+ RefPtr<nsScreenGonk> primaryScreen = nsScreenManagerGonk::GetPrimaryScreen();
+ NS_ENSURE_TRUE(primaryScreen, false);
+
+ bool ret = primaryScreen->SetMirroringScreen(this);
+ NS_ENSURE_TRUE(ret, false);
+
+ // Create a widget for mirroring
+ nsWidgetInitData initData;
+ initData.mScreenId = mId;
+ RefPtr<nsWindow> window = new nsWindow();
+ nsresult rv = window->Create(nullptr, nullptr, mNaturalBounds, &initData);
+ NS_ENSURE_SUCCESS(rv, false);
+ MOZ_ASSERT(static_cast<nsWindow*>(window)->GetScreen() == this);
+
+ // Update mMirroringWidget on compositor thread
+ nsMainThreadPtrHandle<nsScreenGonk> primary =
+ nsMainThreadPtrHandle<nsScreenGonk>(new nsMainThreadPtrHolder<nsScreenGonk>(primaryScreen, false));
+ CompositorThreadHolder::Loop()->PostTask(
+ NewRunnableFunction(&UpdateMirroringWidgetSync,
+ primary,
+ window.forget().take()));
+
+ mIsMirroring = true;
+ return true;
+}
+
+bool
+nsScreenGonk::DisableMirroring()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsPrimaryScreen());
+
+ mIsMirroring = false;
+ RefPtr<nsScreenGonk> primaryScreen = nsScreenManagerGonk::GetPrimaryScreen();
+ NS_ENSURE_TRUE(primaryScreen, false);
+
+ bool ret = primaryScreen->ClearMirroringScreen(this);
+ NS_ENSURE_TRUE(ret, false);
+
+ // Update mMirroringWidget on compositor thread
+ nsMainThreadPtrHandle<nsScreenGonk> primary =
+ nsMainThreadPtrHandle<nsScreenGonk>(new nsMainThreadPtrHolder<nsScreenGonk>(primaryScreen, false));
+ CompositorThreadHolder::Loop()->PostTask(
+ NewRunnableFunction(&UpdateMirroringWidgetSync,
+ primary,
+ nullptr));
+ return true;
+}
+
+bool
+nsScreenGonk::SetMirroringScreen(nsScreenGonk* aScreen)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(IsPrimaryScreen());
+
+ if (mMirroringScreen) {
+ return false;
+ }
+ mMirroringScreen = aScreen;
+ return true;
+}
+
+bool
+nsScreenGonk::ClearMirroringScreen(nsScreenGonk* aScreen)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(IsPrimaryScreen());
+
+ if (mMirroringScreen != aScreen) {
+ return false;
+ }
+ mMirroringScreen = nullptr;
+ return true;
+}
+
+void
+nsScreenGonk::UpdateMirroringWidget(already_AddRefed<nsWindow>& aWindow)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(IsPrimaryScreen());
+
+ if (mMirroringWidget) {
+ nsCOMPtr<nsIWidget> widget = mMirroringWidget.forget();
+ NS_ReleaseOnMainThread(widget.forget());
+ }
+ mMirroringWidget = aWindow;
+}
+
+nsWindow*
+nsScreenGonk::GetMirroringWidget()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(IsPrimaryScreen());
+
+ return mMirroringWidget;
+}
+
+NS_IMPL_ISUPPORTS(nsScreenManagerGonk, nsIScreenManager)
+
+nsScreenManagerGonk::nsScreenManagerGonk()
+ : mInitialized(false)
+#if ANDROID_VERSION >= 19
+ , mDisplayEnabled(false)
+#endif
+{
+}
+
+nsScreenManagerGonk::~nsScreenManagerGonk()
+{
+}
+
+static StaticRefPtr<nsScreenManagerGonk> sScreenManagerGonk;
+
+/* static */ already_AddRefed<nsScreenManagerGonk>
+nsScreenManagerGonk::GetInstance()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Avoid creating nsScreenManagerGonk from content process.
+ if (!XRE_IsParentProcess()) {
+ MOZ_CRASH("Non-chrome processes should not get here.");
+ }
+
+ // Avoid creating multiple nsScreenManagerGonk instance inside main process.
+ if (!sScreenManagerGonk) {
+ sScreenManagerGonk = new nsScreenManagerGonk();
+ ClearOnShutdown(&sScreenManagerGonk);
+ }
+
+ RefPtr<nsScreenManagerGonk> screenMgr = sScreenManagerGonk.get();
+ return screenMgr.forget();
+}
+
+/* static */ already_AddRefed< nsScreenGonk>
+nsScreenManagerGonk::GetPrimaryScreen()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<nsScreenManagerGonk> manager = nsScreenManagerGonk::GetInstance();
+ nsCOMPtr<nsIScreen> screen;
+ manager->GetPrimaryScreen(getter_AddRefs(screen));
+ MOZ_ASSERT(screen);
+ return already_AddRefed<nsScreenGonk>(
+ static_cast<nsScreenGonk*>(screen.forget().take()));
+}
+
+void
+nsScreenManagerGonk::Initialize()
+{
+ if (mInitialized) {
+ return;
+ }
+
+ mScreenOnEvent = new ScreenOnOffEvent(true);
+ mScreenOffEvent = new ScreenOnOffEvent(false);
+ GetGonkDisplay()->OnEnabled(displayEnabledCallback);
+
+ AddScreen(GonkDisplay::DISPLAY_PRIMARY);
+
+ nsAppShell::NotifyScreenInitialized();
+ mInitialized = true;
+}
+
+void
+nsScreenManagerGonk::DisplayEnabled(bool aEnabled)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+#if ANDROID_VERSION >= 19
+ /* Bug 1244044
+ * This function could be called before |mCompositorVsyncScheduler| is set.
+ * To avoid this issue, keep the value stored in |mDisplayEnabled|.
+ */
+ mDisplayEnabled = aEnabled;
+ if (mCompositorVsyncScheduler) {
+ mCompositorVsyncScheduler->SetDisplay(mDisplayEnabled);
+ }
+#endif
+
+ VsyncControl(aEnabled);
+ NS_DispatchToMainThread(aEnabled ? mScreenOnEvent : mScreenOffEvent);
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::GetPrimaryScreen(nsIScreen **outScreen)
+{
+ NS_IF_ADDREF(*outScreen = mScreens[0].get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::ScreenForId(uint32_t aId,
+ nsIScreen **outScreen)
+{
+ for (size_t i = 0; i < mScreens.Length(); i++) {
+ if (mScreens[i]->GetId() == aId) {
+ NS_IF_ADDREF(*outScreen = mScreens[i].get());
+ return NS_OK;
+ }
+ }
+
+ *outScreen = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::ScreenForRect(int32_t inLeft,
+ int32_t inTop,
+ int32_t inWidth,
+ int32_t inHeight,
+ nsIScreen **outScreen)
+{
+ // Since all screens have independent coordinate system, we could
+ // only return the primary screen no matter what rect is given.
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::ScreenForNativeWidget(void *aWidget, nsIScreen **outScreen)
+{
+ for (size_t i = 0; i < mScreens.Length(); i++) {
+ if (aWidget == mScreens[i]->GetNativeWindow()) {
+ NS_IF_ADDREF(*outScreen = mScreens[i].get());
+ return NS_OK;
+ }
+ }
+
+ *outScreen = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::GetNumberOfScreens(uint32_t *aNumberOfScreens)
+{
+ *aNumberOfScreens = mScreens.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::GetSystemDefaultScale(float *aDefaultScale)
+{
+ *aDefaultScale = 1.0f;
+ return NS_OK;
+}
+
+void
+nsScreenManagerGonk::VsyncControl(bool aEnabled)
+{
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NewRunnableMethod<bool>(this,
+ &nsScreenManagerGonk::VsyncControl,
+ aEnabled));
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ VsyncSource::Display &display = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay();
+ if (aEnabled) {
+ display.EnableVsync();
+ } else {
+ display.DisableVsync();
+ }
+}
+
+uint32_t
+nsScreenManagerGonk::GetIdFromType(GonkDisplay::DisplayType aDisplayType)
+{
+ // This is the only place where we make the assumption that
+ // display type is equivalent to screen id.
+
+ // Bug 1138287 will address the conversion from type to id.
+ return aDisplayType;
+}
+
+bool
+nsScreenManagerGonk::IsScreenConnected(uint32_t aId)
+{
+ for (size_t i = 0; i < mScreens.Length(); ++i) {
+ if (mScreens[i]->GetId() == aId) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace {
+
+// A concrete class as a subject for 'display-changed' observer event.
+class DisplayInfo : public nsIDisplayInfo {
+public:
+ NS_DECL_ISUPPORTS
+
+ DisplayInfo(uint32_t aId, bool aConnected)
+ : mId(aId)
+ , mConnected(aConnected)
+ {
+ }
+
+ NS_IMETHODIMP GetId(int32_t *aId)
+ {
+ *aId = mId;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP GetConnected(bool *aConnected)
+ {
+ *aConnected = mConnected;
+ return NS_OK;
+ }
+
+private:
+ virtual ~DisplayInfo() {}
+
+ uint32_t mId;
+ bool mConnected;
+};
+
+NS_IMPL_ISUPPORTS(DisplayInfo, nsIDisplayInfo, nsISupports)
+
+class NotifyTask : public mozilla::Runnable {
+public:
+ NotifyTask(uint32_t aId, bool aConnected)
+ : mDisplayInfo(new DisplayInfo(aId, aConnected))
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(mDisplayInfo, "display-changed", nullptr);
+ }
+
+ return NS_OK;
+ }
+private:
+ RefPtr<DisplayInfo> mDisplayInfo;
+};
+
+void
+NotifyDisplayChange(uint32_t aId, bool aConnected)
+{
+ NS_DispatchToMainThread(new NotifyTask(aId, aConnected));
+}
+
+} // end of unnamed namespace.
+
+nsresult
+nsScreenManagerGonk::AddScreen(GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink,
+ NotifyDisplayChangedEvent aEventVisibility)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(aDisplayType < GonkDisplay::DisplayType::NUM_DISPLAY_TYPES,
+ NS_ERROR_FAILURE);
+
+ uint32_t id = GetIdFromType(aDisplayType);
+ NS_ENSURE_TRUE(!IsScreenConnected(id), NS_ERROR_FAILURE);
+
+ GonkDisplay::NativeData nativeData =
+ GetGonkDisplay()->GetNativeData(aDisplayType, aSink);
+ nsScreenGonk* screen = new nsScreenGonk(id,
+ aDisplayType,
+ nativeData,
+ aEventVisibility);
+ mScreens.AppendElement(screen);
+
+ if (aEventVisibility == NotifyDisplayChangedEvent::Observable) {
+ NotifyDisplayChange(id, true);
+ }
+
+ // By default, non primary screen does mirroring.
+ if (aDisplayType != GonkDisplay::DISPLAY_PRIMARY &&
+ gfxPrefs::ScreenMirroringEnabled()) {
+ screen->EnableMirroring();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsScreenManagerGonk::RemoveScreen(GonkDisplay::DisplayType aDisplayType)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(aDisplayType < GonkDisplay::DisplayType::NUM_DISPLAY_TYPES,
+ NS_ERROR_FAILURE);
+
+ NotifyDisplayChangedEvent eventVisibility = NotifyDisplayChangedEvent::Observable;
+ uint32_t screenId = GetIdFromType(aDisplayType);
+ NS_ENSURE_TRUE(IsScreenConnected(screenId), NS_ERROR_FAILURE);
+
+ for (size_t i = 0; i < mScreens.Length(); i++) {
+ if (mScreens[i]->GetId() == screenId) {
+ if (mScreens[i]->IsMirroring()) {
+ mScreens[i]->DisableMirroring();
+ }
+ eventVisibility = mScreens[i]->GetEventVisibility();
+ mScreens.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ if (eventVisibility == NotifyDisplayChangedEvent::Observable) {
+ NotifyDisplayChange(screenId, false);
+ }
+ return NS_OK;
+}
+
+#if ANDROID_VERSION >= 19
+void
+nsScreenManagerGonk::SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler *aObserver)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We assume on b2g that there is only 1 CompositorBridgeParent
+ MOZ_ASSERT(mCompositorVsyncScheduler == nullptr);
+ MOZ_ASSERT(aObserver);
+ mCompositorVsyncScheduler = aObserver;
+ mCompositorVsyncScheduler->SetDisplay(mDisplayEnabled);
+}
+#endif
diff --git a/widget/gonk/nsScreenManagerGonk.h b/widget/gonk/nsScreenManagerGonk.h
new file mode 100644
index 000000000..33ef5edb8
--- /dev/null
+++ b/widget/gonk/nsScreenManagerGonk.h
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef nsScreenManagerGonk_h___
+#define nsScreenManagerGonk_h___
+
+#include "cutils/properties.h"
+#include "hardware/hwcomposer.h"
+
+#include "libdisplay/GonkDisplay.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Mutex.h"
+#include "nsBaseScreen.h"
+#include "nsCOMPtr.h"
+#include "nsIScreenManager.h"
+#include "nsProxyRelease.h"
+
+#include <android/native_window.h>
+
+class nsWindow;
+
+namespace android {
+ class DisplaySurface;
+ class IGraphicBufferProducer;
+};
+
+namespace mozilla {
+ class Runnable;
+namespace gl {
+ class GLContext;
+}
+namespace layers {
+class CompositorVsyncScheduler;
+class CompositorBridgeParent;
+}
+}
+
+enum class NotifyDisplayChangedEvent : int8_t {
+ Observable,
+ Suppressed
+};
+
+class nsScreenGonk : public nsBaseScreen
+{
+ typedef mozilla::hal::ScreenConfiguration ScreenConfiguration;
+ typedef mozilla::GonkDisplay GonkDisplay;
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+ typedef mozilla::layers::CompositorBridgeParent CompositorBridgeParent;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+public:
+ nsScreenGonk(uint32_t aId,
+ GonkDisplay::DisplayType aDisplayType,
+ const GonkDisplay::NativeData& aNativeData,
+ NotifyDisplayChangedEvent aEventVisibility);
+
+ ~nsScreenGonk();
+
+ NS_IMETHOD GetId(uint32_t* aId);
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
+ NS_IMETHOD GetRotation(uint32_t* aRotation);
+ NS_IMETHOD SetRotation(uint32_t aRotation);
+
+ uint32_t GetId();
+ NotifyDisplayChangedEvent GetEventVisibility();
+ LayoutDeviceIntRect GetRect();
+ float GetDpi();
+ int32_t GetSurfaceFormat();
+ ANativeWindow* GetNativeWindow();
+ LayoutDeviceIntRect GetNaturalBounds();
+ uint32_t EffectiveScreenRotation();
+ ScreenConfiguration GetConfiguration();
+ bool IsPrimaryScreen();
+
+ already_AddRefed<DrawTarget> StartRemoteDrawing();
+ void EndRemoteDrawing();
+
+ nsresult MakeSnapshot(ANativeWindowBuffer* aBuffer);
+ void SetCompositorBridgeParent(CompositorBridgeParent* aCompositorBridgeParent);
+
+#if ANDROID_VERSION >= 17
+ android::DisplaySurface* GetDisplaySurface();
+ int GetPrevDispAcquireFd();
+#endif
+ GonkDisplay::DisplayType GetDisplayType();
+
+ void RegisterWindow(nsWindow* aWindow);
+ void UnregisterWindow(nsWindow* aWindow);
+ void BringToTop(nsWindow* aWindow);
+
+ const nsTArray<nsWindow*>& GetTopWindows() const
+ {
+ return mTopWindows;
+ }
+
+ // Non-primary screen only
+ bool EnableMirroring();
+ bool DisableMirroring();
+ bool IsMirroring()
+ {
+ return mIsMirroring;
+ }
+
+ // Primary screen only
+ bool SetMirroringScreen(nsScreenGonk* aScreen);
+ bool ClearMirroringScreen(nsScreenGonk* aScreen);
+
+ // Called only on compositor thread
+ void SetEGLInfo(hwc_display_t aDisplay, hwc_surface_t aSurface,
+ mozilla::gl::GLContext* aGLContext);
+ hwc_display_t GetEGLDisplay();
+ hwc_surface_t GetEGLSurface();
+ already_AddRefed<mozilla::gl::GLContext> GetGLContext();
+ void UpdateMirroringWidget(already_AddRefed<nsWindow>& aWindow); // Primary screen only
+ nsWindow* GetMirroringWidget(); // Primary screen only
+
+protected:
+ ANativeWindowBuffer* DequeueBuffer();
+ bool QueueBuffer(ANativeWindowBuffer* buf);
+
+ uint32_t mId;
+ NotifyDisplayChangedEvent mEventVisibility;
+ int32_t mColorDepth;
+ android::sp<ANativeWindow> mNativeWindow;
+ float mDpi;
+ int32_t mSurfaceFormat;
+ LayoutDeviceIntRect mNaturalBounds; // Screen bounds w/o rotation taken into account.
+ LayoutDeviceIntRect mVirtualBounds; // Screen bounds w/ rotation taken into account.
+ uint32_t mScreenRotation;
+ uint32_t mPhysicalScreenRotation;
+ nsTArray<nsWindow*> mTopWindows;
+#if ANDROID_VERSION >= 17
+ android::sp<android::DisplaySurface> mDisplaySurface;
+#endif
+ bool mIsMirroring; // Non-primary screen only
+ RefPtr<nsScreenGonk> mMirroringScreen; // Primary screen only
+ mozilla::Atomic<CompositorBridgeParent*> mCompositorBridgeParent;
+
+ // Accessed and updated only on compositor thread
+ GonkDisplay::DisplayType mDisplayType;
+ hwc_display_t mEGLDisplay;
+ hwc_surface_t mEGLSurface;
+ RefPtr<mozilla::gl::GLContext> mGLContext;
+ RefPtr<nsWindow> mMirroringWidget; // Primary screen only
+
+ // If we're using a BasicCompositor, these fields are temporarily
+ // set during frame composition. They wrap the hardware
+ // framebuffer.
+ RefPtr<DrawTarget> mFramebufferTarget;
+ ANativeWindowBuffer* mFramebuffer;
+ /**
+ * Points to a mapped gralloc buffer between calls to lock and unlock.
+ * Should be null outside of the lock-unlock pair.
+ */
+ uint8_t* mMappedBuffer;
+ // If we're using a BasicCompositor, this is our window back
+ // buffer. The gralloc framebuffer driver expects us to draw the
+ // entire framebuffer on every frame, but gecko expects the
+ // windowing system to be tracking buffer updates for invalidated
+ // regions. We get stuck holding that bag.
+ //
+ // Only accessed on the compositor thread, except during
+ // destruction.
+ RefPtr<DrawTarget> mBackBuffer;
+};
+
+class nsScreenManagerGonk final : public nsIScreenManager
+{
+public:
+ typedef mozilla::GonkDisplay GonkDisplay;
+
+public:
+ nsScreenManagerGonk();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+ static already_AddRefed<nsScreenManagerGonk> GetInstance();
+ static already_AddRefed<nsScreenGonk> GetPrimaryScreen();
+
+ void Initialize();
+ void DisplayEnabled(bool aEnabled);
+
+ nsresult AddScreen(GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink = nullptr,
+ NotifyDisplayChangedEvent aEventVisibility = NotifyDisplayChangedEvent::Observable);
+
+ nsresult RemoveScreen(GonkDisplay::DisplayType aDisplayType);
+
+#if ANDROID_VERSION >= 19
+ void SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler* aObserver);
+#endif
+
+protected:
+ ~nsScreenManagerGonk();
+ void VsyncControl(bool aEnabled);
+ uint32_t GetIdFromType(GonkDisplay::DisplayType aDisplayType);
+ bool IsScreenConnected(uint32_t aId);
+
+ bool mInitialized;
+ nsTArray<RefPtr<nsScreenGonk>> mScreens;
+ RefPtr<mozilla::Runnable> mScreenOnEvent;
+ RefPtr<mozilla::Runnable> mScreenOffEvent;
+
+#if ANDROID_VERSION >= 19
+ bool mDisplayEnabled;
+ RefPtr<mozilla::layers::CompositorVsyncScheduler> mCompositorVsyncScheduler;
+#endif
+};
+
+#endif /* nsScreenManagerGonk_h___ */
diff --git a/widget/gonk/nsWidgetFactory.cpp b/widget/gonk/nsWidgetFactory.cpp
new file mode 100644
index 000000000..1c7525544
--- /dev/null
+++ b/widget/gonk/nsWidgetFactory.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/basictypes.h"
+
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+
+#include "nsWindow.h"
+#include "nsLookAndFeel.h"
+#include "nsAppShellSingleton.h"
+#include "nsScreenManagerGonk.h"
+#include "nsIdleServiceGonk.h"
+#include "nsTransferable.h"
+#include "nsClipboard.h"
+#include "nsClipboardHelper.h"
+
+#include "nsHTMLFormatConverter.h"
+#include "nsXULAppAPI.h"
+
+#include "PuppetWidget.h"
+
+using namespace mozilla::widget;
+
+// taken from android/nsWidgetFactory.cpp. GfxInfo is a legacy kludge, unfortunately
+// for the time being we still have to implement it on all platforms.
+#include "GfxInfo.h"
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+}
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsScreenManagerGonk, nsScreenManagerGonk::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(PuppetScreenManager)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceGonk, nsIdleServiceGonk::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
+
+static nsresult
+ScreenManagerConstructor(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ return (XRE_IsParentProcess()) ?
+ nsScreenManagerGonkConstructor(aOuter, aIID, aResult) :
+ PuppetScreenManagerConstructor(aOuter, aIID, aResult);
+}
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_CHILD_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor },
+ { &kNS_SCREENMANAGER_CID, false, nullptr, ScreenManagerConstructor },
+ { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
+ { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceGonkConstructor },
+ { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
+ { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
+ { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor },
+ { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/gonk;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/child_window/gonk;1", &kNS_CHILD_CID },
+ { "@mozilla.org/widget/appshell/gonk;1", &kNS_APPSHELL_CID },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID },
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { nullptr }
+};
+
+static void
+nsWidgetGonkModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ nsLookAndFeel::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ nullptr,
+ nullptr,
+ nsAppShellInit,
+ nsWidgetGonkModuleDtor
+};
+
+NSMODULE_DEFN(nsWidgetGonkModule) = &kWidgetModule;
diff --git a/widget/gonk/nsWindow.cpp b/widget/gonk/nsWindow.cpp
new file mode 100644
index 000000000..e11b7f233
--- /dev/null
+++ b/widget/gonk/nsWindow.cpp
@@ -0,0 +1,744 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nsWindow.h"
+
+#include "mozilla/DebugOnly.h"
+
+#include <fcntl.h>
+
+#include "android/log.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Services.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "GLContextProvider.h"
+#include "GLContext.h"
+#include "GLContextEGL.h"
+#include "nsAppShell.h"
+#include "nsScreenManagerGonk.h"
+#include "nsTArray.h"
+#include "nsIWidgetListener.h"
+#include "ClientLayerManager.h"
+#include "BasicLayers.h"
+#include "libdisplay/GonkDisplay.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/CompositorSession.h"
+#include "mozilla/TouchEvents.h"
+#include "HwcComposer2D.h"
+
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "Gonk", ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "Gonk", ## args)
+
+#define IS_TOPLEVEL() (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog)
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::hal;
+using namespace mozilla::gfx;
+using namespace mozilla::gl;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+
+static nsWindow *gFocusedWindow = nullptr;
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
+
+nsWindow::nsWindow()
+{
+ RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
+ screenManager->Initialize();
+
+ // This is a hack to force initialization of the compositor
+ // resources, if we're going to use omtc.
+ //
+ // NB: GetPlatform() will create the gfxPlatform, which wants
+ // to know the color depth, which asks our native window.
+ // This has to happen after other init has finished.
+ gfxPlatform::GetPlatform();
+ if (!ShouldUseOffMainThreadCompositing()) {
+ MOZ_CRASH("How can we render apps, then?");
+ }
+}
+
+nsWindow::~nsWindow()
+{
+ if (mScreen->IsPrimaryScreen()) {
+ mComposer2D->SetCompositorBridgeParent(nullptr);
+ }
+}
+
+void
+nsWindow::DoDraw(void)
+{
+ if (!hal::GetScreenEnabled()) {
+ gDrawRequest = true;
+ return;
+ }
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+ const nsTArray<nsWindow*>& windows = screen->GetTopWindows();
+
+ if (windows.IsEmpty()) {
+ LOG(" no window to draw, bailing");
+ return;
+ }
+
+ RefPtr<nsWindow> targetWindow = (nsWindow *)windows[0];
+ while (targetWindow->GetLastChild()) {
+ targetWindow = (nsWindow *)targetWindow->GetLastChild();
+ }
+
+ nsIWidgetListener* listener = targetWindow->GetWidgetListener();
+ if (listener) {
+ listener->WillPaintWindow(targetWindow);
+ }
+
+ listener = targetWindow->GetWidgetListener();
+ if (listener) {
+ LayerManager* lm = targetWindow->GetLayerManager();
+ if (mozilla::layers::LayersBackend::LAYERS_CLIENT == lm->GetBackendType()) {
+ // No need to do anything, the compositor will handle drawing
+ } else {
+ NS_RUNTIMEABORT("Unexpected layer manager type");
+ }
+
+ listener->DidPaintWindow();
+ }
+}
+
+void
+nsWindow::ConfigureAPZControllerThread()
+{
+ APZThreadUtils::SetControllerThread(CompositorThreadHolder::Loop());
+}
+
+/*static*/ nsEventStatus
+nsWindow::DispatchKeyInput(WidgetKeyboardEvent& aEvent)
+{
+ if (!gFocusedWindow) {
+ return nsEventStatus_eIgnore;
+ }
+
+ gFocusedWindow->UserActivity();
+
+ nsEventStatus status;
+ aEvent.mWidget = gFocusedWindow;
+ gFocusedWindow->DispatchEvent(&aEvent, status);
+ return status;
+}
+
+/*static*/ void
+nsWindow::DispatchTouchInput(MultiTouchInput& aInput)
+{
+ APZThreadUtils::AssertOnControllerThread();
+
+ if (!gFocusedWindow) {
+ return;
+ }
+
+ gFocusedWindow->DispatchTouchInputViaAPZ(aInput);
+}
+
+class DispatchTouchInputOnMainThread : public mozilla::Runnable
+{
+public:
+ DispatchTouchInputOnMainThread(const MultiTouchInput& aInput,
+ const ScrollableLayerGuid& aGuid,
+ const uint64_t& aInputBlockId,
+ nsEventStatus aApzResponse)
+ : mInput(aInput)
+ , mGuid(aGuid)
+ , mInputBlockId(aInputBlockId)
+ , mApzResponse(aApzResponse)
+ {}
+
+ NS_IMETHOD Run() override {
+ if (gFocusedWindow) {
+ gFocusedWindow->DispatchTouchEventForAPZ(mInput, mGuid, mInputBlockId, mApzResponse);
+ }
+ return NS_OK;
+ }
+
+private:
+ MultiTouchInput mInput;
+ ScrollableLayerGuid mGuid;
+ uint64_t mInputBlockId;
+ nsEventStatus mApzResponse;
+};
+
+void
+nsWindow::DispatchTouchInputViaAPZ(MultiTouchInput& aInput)
+{
+ APZThreadUtils::AssertOnControllerThread();
+
+ if (!mAPZC) {
+ // In general mAPZC should not be null, but during initial setup
+ // it might be, so we handle that case by ignoring touch input there.
+ return;
+ }
+
+ // First send it through the APZ code
+ mozilla::layers::ScrollableLayerGuid guid;
+ uint64_t inputBlockId;
+ nsEventStatus result = mAPZC->ReceiveInputEvent(aInput, &guid, &inputBlockId);
+ // If the APZ says to drop it, then we drop it
+ if (result == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ // Can't use NS_NewRunnableMethod because it only takes up to one arg and
+ // we need more. Also we can't pass in |this| to the task because nsWindow
+ // refcounting is not threadsafe. Instead we just use the gFocusedWindow
+ // static ptr inside the task.
+ NS_DispatchToMainThread(new DispatchTouchInputOnMainThread(
+ aInput, guid, inputBlockId, result));
+}
+
+void
+nsWindow::DispatchTouchEventForAPZ(const MultiTouchInput& aInput,
+ const ScrollableLayerGuid& aGuid,
+ const uint64_t aInputBlockId,
+ nsEventStatus aApzResponse)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ UserActivity();
+
+ // Convert it to an event we can send to Gecko
+ WidgetTouchEvent event = aInput.ToWidgetTouchEvent(this);
+
+ // Dispatch the event into the gecko root process for "normal" flow.
+ // The event might get sent to a child process,
+ // but if it doesn't we need to notify the APZ of various things.
+ // All of that happens in ProcessUntransformedAPZEvent
+ ProcessUntransformedAPZEvent(&event, aGuid, aInputBlockId, aApzResponse);
+}
+
+class DispatchTouchInputOnControllerThread : public Runnable
+{
+public:
+ DispatchTouchInputOnControllerThread(const MultiTouchInput& aInput)
+ : mInput(aInput)
+ {}
+
+ NS_IMETHOD Run() override {
+ if (gFocusedWindow) {
+ gFocusedWindow->DispatchTouchInputViaAPZ(mInput);
+ }
+ return NS_OK;
+ }
+
+private:
+ MultiTouchInput mInput;
+};
+
+nsresult
+nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (aPointerState == TOUCH_HOVER) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mSynthesizedTouchInput) {
+ mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
+ }
+
+ // We should probably use a real timestamp here, but this is B2G and
+ // so this probably never even exercised any more.
+ uint32_t time = 0;
+ TimeStamp timestamp = TimeStamp::FromSystemTime(time);
+
+ MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
+ mSynthesizedTouchInput.get(), time, timestamp, aPointerId, aPointerState,
+ aPoint, aPointerPressure, aPointerOrientation);
+
+ // Can't use NewRunnableMethod here because that will pass a const-ref
+ // argument to DispatchTouchInputViaAPZ whereas that function takes a
+ // non-const ref. At this callsite we don't care about the mutations that
+ // the function performs so this is fine. Also we can't pass |this| to the
+ // task because nsWindow refcounting is not threadsafe. Instead we just use
+ // the gFocusedWindow static ptr instead the task.
+ APZThreadUtils::RunOnControllerThread(
+ MakeAndAddRef<DispatchTouchInputOnControllerThread>(inputToDispatch));
+
+ return NS_OK;
+}
+
+nsresult
+nsWindow::Create(nsIWidget* aParent,
+ void* aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ BaseCreate(aParent, aInitData);
+
+ nsCOMPtr<nsIScreen> screen;
+
+ uint32_t screenId = aParent ? ((nsWindow*)aParent)->mScreen->GetId() :
+ aInitData->mScreenId;
+
+ RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
+ screenManager->ScreenForId(screenId, getter_AddRefs(screen));
+
+ mScreen = static_cast<nsScreenGonk*>(screen.get());
+
+ mBounds = aRect;
+
+ mParent = (nsWindow *)aParent;
+ mVisible = false;
+
+ if (!aParent) {
+ mBounds = mScreen->GetRect();
+ }
+
+ mComposer2D = HwcComposer2D::GetInstance();
+
+ if (!IS_TOPLEVEL()) {
+ return NS_OK;
+ }
+
+ mScreen->RegisterWindow(this);
+
+ Resize(0, 0, mBounds.width, mBounds.height, false);
+
+ return NS_OK;
+}
+
+void
+nsWindow::Destroy()
+{
+ mOnDestroyCalled = true;
+ mScreen->UnregisterWindow(this);
+ if (this == gFocusedWindow) {
+ gFocusedWindow = nullptr;
+ }
+ nsBaseWidget::OnDestroy();
+}
+
+NS_IMETHODIMP
+nsWindow::Show(bool aState)
+{
+ if (mWindowType == eWindowType_invisible) {
+ return NS_OK;
+ }
+
+ if (mVisible == aState) {
+ return NS_OK;
+ }
+
+ mVisible = aState;
+ if (!IS_TOPLEVEL()) {
+ return mParent ? mParent->Show(aState) : NS_OK;
+ }
+
+ if (aState) {
+ BringToTop();
+ } else {
+ const nsTArray<nsWindow*>& windows =
+ mScreen->GetTopWindows();
+ for (unsigned int i = 0; i < windows.Length(); i++) {
+ nsWindow *win = windows[i];
+ if (!win->mVisible) {
+ continue;
+ }
+ win->BringToTop();
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+nsWindow::IsVisible() const
+{
+ return mVisible;
+}
+
+NS_IMETHODIMP
+nsWindow::Move(double aX,
+ double aY)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aWidth,
+ double aHeight,
+ bool aRepaint)
+{
+ return Resize(0, 0, aWidth, aHeight, aRepaint);
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint)
+{
+ mBounds = LayoutDeviceIntRect(NSToIntRound(aX), NSToIntRound(aY),
+ NSToIntRound(aWidth), NSToIntRound(aHeight));
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+
+ if (aRepaint) {
+ Invalidate(mBounds);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Enable(bool aState)
+{
+ return NS_OK;
+}
+
+bool
+nsWindow::IsEnabled() const
+{
+ return true;
+}
+
+NS_IMETHODIMP
+nsWindow::SetFocus(bool aRaise)
+{
+ if (aRaise) {
+ BringToTop();
+ }
+
+ if (!IS_TOPLEVEL() && mScreen->IsPrimaryScreen()) {
+ // We should only set focused window on non-toplevel primary window.
+ gFocusedWindow = this;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>&)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ nsWindow *top = mParent;
+ while (top && top->mParent) {
+ top = top->mParent;
+ }
+ const nsTArray<nsWindow*>& windows = mScreen->GetTopWindows();
+ if (top != windows[0] && this != windows[0]) {
+ return NS_OK;
+ }
+
+ gDrawRequest = true;
+ mozilla::NotifyEvent();
+ return NS_OK;
+}
+
+LayoutDeviceIntPoint
+nsWindow::WidgetToScreenOffset()
+{
+ LayoutDeviceIntPoint p(0, 0);
+ nsWindow *w = this;
+
+ while (w && w->mParent) {
+ p.x += w->mBounds.x;
+ p.y += w->mBounds.y;
+
+ w = w->mParent;
+ }
+
+ return p;
+}
+
+void*
+nsWindow::GetNativeData(uint32_t aDataType)
+{
+ switch (aDataType) {
+ case NS_NATIVE_WINDOW:
+ // Called before primary display's EGLSurface creation.
+ return mScreen->GetNativeWindow();
+ case NS_NATIVE_OPENGL_CONTEXT:
+ return mScreen->GetGLContext().take();
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ // There is only one IME context on Gonk.
+ return NS_ONLY_ONE_NATIVE_IME_CONTEXT;
+ }
+ }
+
+ return nullptr;
+}
+
+void
+nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal)
+{
+ switch (aDataType) {
+ case NS_NATIVE_OPENGL_CONTEXT:
+ GLContext* context = reinterpret_cast<GLContext*>(aVal);
+ if (!context) {
+ mScreen->SetEGLInfo(EGL_NO_DISPLAY,
+ EGL_NO_SURFACE,
+ nullptr);
+ return;
+ }
+ mScreen->SetEGLInfo(GLContextEGL::Cast(context)->GetEGLDisplay(),
+ GLContextEGL::Cast(context)->GetEGLSurface(),
+ context);
+ return;
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::DispatchEvent(WidgetGUIEvent* aEvent, nsEventStatus& aStatus)
+{
+ if (mWidgetListener) {
+ aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ mInputContext = aContext;
+}
+
+NS_IMETHODIMP_(InputContext)
+nsWindow::GetInputContext()
+{
+ return mInputContext;
+}
+
+nsresult
+nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen*)
+{
+ if (mWindowType != eWindowType_toplevel) {
+ // Ignore fullscreen request for non-toplevel windows.
+ NS_WARNING("MakeFullScreen() on a dialog or child widget?");
+ nsBaseWidget::InfallibleMakeFullScreen(aFullScreen);
+ return NS_OK;
+ }
+
+ if (aFullScreen) {
+ // Fullscreen is "sticky" for toplevel widgets on gonk: we
+ // must paint the entire screen, and should only have one
+ // toplevel widget, so it doesn't make sense to ever "exit"
+ // fullscreen. If we do, we can leave parts of the screen
+ // unpainted.
+ nsIntRect virtualBounds;
+ mScreen->GetRect(&virtualBounds.x, &virtualBounds.y,
+ &virtualBounds.width, &virtualBounds.height);
+ Resize(virtualBounds.x, virtualBounds.y,
+ virtualBounds.width, virtualBounds.height,
+ /*repaint*/true);
+ }
+
+ if (nsIWidgetListener* listener = GetWidgetListener()) {
+ listener->FullscreenChanged(aFullScreen);
+ }
+ return NS_OK;
+}
+
+already_AddRefed<DrawTarget>
+nsWindow::StartRemoteDrawing()
+{
+ RefPtr<DrawTarget> buffer = mScreen->StartRemoteDrawing();
+ return buffer.forget();
+}
+
+void
+nsWindow::EndRemoteDrawing()
+{
+ mScreen->EndRemoteDrawing();
+}
+
+float
+nsWindow::GetDPI()
+{
+ return mScreen->GetDpi();
+}
+
+double
+nsWindow::GetDefaultScaleInternal()
+{
+ float dpi = GetDPI();
+ // The mean pixel density for mdpi devices is 160dpi, 240dpi for hdpi,
+ // and 320dpi for xhdpi, respectively.
+ // We'll take the mid-value between these three numbers as the boundary.
+ if (dpi < 200.0) {
+ return 1.0; // mdpi devices.
+ }
+ if (dpi < 280.0) {
+ return 1.5; // hdpi devices.
+ }
+ // xhdpi devices and beyond.
+ return floor(dpi / 150.0 + 0.5);
+}
+
+LayerManager *
+nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
+ LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence)
+{
+ if (mLayerManager) {
+ // This layer manager might be used for painting outside of DoDraw(), so we need
+ // to set the correct rotation on it.
+ if (ClientLayerManager* manager = mLayerManager->AsClientLayerManager()) {
+ uint32_t rotation = mScreen->EffectiveScreenRotation();
+ manager->SetDefaultTargetConfiguration(mozilla::layers::BufferMode::BUFFER_NONE,
+ ScreenRotation(rotation));
+ }
+ return mLayerManager;
+ }
+
+ const nsTArray<nsWindow*>& windows = mScreen->GetTopWindows();
+ nsWindow *topWindow = windows[0];
+
+ if (!topWindow) {
+ LOGW(" -- no topwindow\n");
+ return nullptr;
+ }
+
+ CreateCompositor();
+ if (RefPtr<CompositorBridgeParent> bridge = GetCompositorBridgeParent()) {
+ mScreen->SetCompositorBridgeParent(bridge);
+ if (mScreen->IsPrimaryScreen()) {
+ mComposer2D->SetCompositorBridgeParent(bridge);
+ }
+ }
+ MOZ_ASSERT(mLayerManager);
+ return mLayerManager;
+}
+
+void
+nsWindow::DestroyCompositor()
+{
+ if (RefPtr<CompositorBridgeParent> bridge = GetCompositorBridgeParent()) {
+ mScreen->SetCompositorBridgeParent(nullptr);
+ if (mScreen->IsPrimaryScreen()) {
+ // Unset CompositorBridgeParent
+ mComposer2D->SetCompositorBridgeParent(nullptr);
+ }
+ }
+ nsBaseWidget::DestroyCompositor();
+}
+
+void
+nsWindow::BringToTop()
+{
+ const nsTArray<nsWindow*>& windows = mScreen->GetTopWindows();
+ if (!windows.IsEmpty()) {
+ if (nsIWidgetListener* listener = windows[0]->GetWidgetListener()) {
+ listener->WindowDeactivated();
+ }
+ }
+
+ mScreen->BringToTop(this);
+
+ if (mWidgetListener) {
+ mWidgetListener->WindowActivated();
+ }
+
+ Invalidate(mBounds);
+}
+
+void
+nsWindow::UserActivity()
+{
+ if (!mIdleService) {
+ mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
+ }
+
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+}
+
+uint32_t
+nsWindow::GetGLFrameBufferFormat()
+{
+ if (mLayerManager &&
+ mLayerManager->GetBackendType() == mozilla::layers::LayersBackend::LAYERS_OPENGL) {
+ // We directly map the hardware fb on Gonk. The hardware fb
+ // has RGB format.
+ return LOCAL_GL_RGB;
+ }
+ return LOCAL_GL_NONE;
+}
+
+LayoutDeviceIntRect
+nsWindow::GetNaturalBounds()
+{
+ return mScreen->GetNaturalBounds();
+}
+
+nsScreenGonk*
+nsWindow::GetScreen()
+{
+ return mScreen;
+}
+
+bool
+nsWindow::NeedsPaint()
+{
+ if (!mLayerManager) {
+ return false;
+ }
+ return nsIWidget::NeedsPaint();
+}
+
+Composer2D*
+nsWindow::GetComposer2D()
+{
+ if (mScreen->GetDisplayType() == GonkDisplay::DISPLAY_VIRTUAL) {
+ return nullptr;
+ }
+
+ return mComposer2D;
+}
+
+CompositorBridgeParent*
+nsWindow::GetCompositorBridgeParent() const
+{
+ return mCompositorSession ? mCompositorSession->GetInProcessBridge() : nullptr;
+}
diff --git a/widget/gonk/nsWindow.h b/widget/gonk/nsWindow.h
new file mode 100644
index 000000000..6106982f9
--- /dev/null
+++ b/widget/gonk/nsWindow.h
@@ -0,0 +1,154 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef nsWindow_h
+#define nsWindow_h
+
+#include "InputData.h"
+#include "mozilla/UniquePtr.h"
+#include "nsBaseWidget.h"
+#include "nsRegion.h"
+#include "nsIIdleServiceInternal.h"
+#include "Units.h"
+
+class ANativeWindowBuffer;
+
+namespace widget {
+struct InputContext;
+struct InputContextAction;
+}
+
+namespace mozilla {
+class HwcComposer2D;
+}
+
+class nsScreenGonk;
+
+class nsWindow : public nsBaseWidget
+{
+public:
+ nsWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ static void DoDraw(void);
+ static nsEventStatus DispatchKeyInput(mozilla::WidgetKeyboardEvent& aEvent);
+ static void DispatchTouchInput(mozilla::MultiTouchInput& aInput);
+
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ void* aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) override;
+ virtual void Destroy();
+
+ NS_IMETHOD Show(bool aState);
+ virtual bool IsVisible() const;
+ NS_IMETHOD Move(double aX,
+ double aY);
+ NS_IMETHOD Resize(double aWidth,
+ double aHeight,
+ bool aRepaint);
+ NS_IMETHOD Resize(double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint);
+ NS_IMETHOD Enable(bool aState);
+ virtual bool IsEnabled() const;
+ NS_IMETHOD SetFocus(bool aRaise = false);
+ NS_IMETHOD ConfigureChildren(const nsTArray<nsIWidget::Configuration>&);
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect);
+ virtual void* GetNativeData(uint32_t aDataType);
+ virtual void SetNativeData(uint32_t aDataType, uintptr_t aVal);
+ NS_IMETHOD SetTitle(const nsAString& aTitle)
+ {
+ return NS_OK;
+ }
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset();
+ void DispatchTouchInputViaAPZ(mozilla::MultiTouchInput& aInput);
+ void DispatchTouchEventForAPZ(const mozilla::MultiTouchInput& aInput,
+ const ScrollableLayerGuid& aGuid,
+ const uint64_t aInputBlockId,
+ nsEventStatus aApzResponse);
+ NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus);
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+
+ virtual nsresult MakeFullScreen(
+ bool aFullScreen, nsIScreen* aTargetScreen = nullptr) override;
+
+ virtual already_AddRefed<mozilla::gfx::DrawTarget>
+ StartRemoteDrawing() override;
+ virtual void EndRemoteDrawing() override;
+
+ virtual float GetDPI();
+ virtual double GetDefaultScaleInternal();
+ virtual mozilla::layers::LayerManager*
+ GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT);
+ virtual void DestroyCompositor();
+
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction);
+ NS_IMETHOD_(InputContext) GetInputContext();
+
+ virtual uint32_t GetGLFrameBufferFormat() override;
+
+ virtual LayoutDeviceIntRect GetNaturalBounds() override;
+ virtual bool NeedsPaint();
+
+ virtual Composer2D* GetComposer2D() override;
+
+ void ConfigureAPZControllerThread() override;
+
+ nsScreenGonk* GetScreen();
+
+protected:
+ nsWindow* mParent;
+ bool mVisible;
+ InputContext mInputContext;
+ nsCOMPtr<nsIIdleServiceInternal> mIdleService;
+
+ virtual ~nsWindow();
+
+ void BringToTop();
+
+ // Call this function when the users activity is the direct cause of an
+ // event (like a keypress or mouse click).
+ void UserActivity();
+
+ bool UseExternalCompositingSurface() const override {
+ return true;
+ }
+ CompositorBridgeParent* GetCompositorBridgeParent() const;
+
+private:
+ // This is used by SynthesizeNativeTouchPoint to maintain state between
+ // multiple synthesized points
+ mozilla::UniquePtr<mozilla::MultiTouchInput> mSynthesizedTouchInput;
+
+ RefPtr<nsScreenGonk> mScreen;
+
+ RefPtr<mozilla::HwcComposer2D> mComposer2D;
+};
+
+#endif /* nsWindow_h */
diff --git a/widget/gtk/CompositorWidgetChild.cpp b/widget/gtk/CompositorWidgetChild.cpp
new file mode 100644
index 000000000..066251060
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.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 "CompositorWidgetChild.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetChild::CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver)
+ : mVsyncDispatcher(aVsyncDispatcher)
+ , mVsyncObserver(aVsyncObserver)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+}
+
+CompositorWidgetChild::~CompositorWidgetChild()
+{
+}
+
+bool
+CompositorWidgetChild::RecvObserveVsync()
+{
+ mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
+ return true;
+}
+
+bool
+CompositorWidgetChild::RecvUnobserveVsync()
+{
+ mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+ return true;
+}
+
+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 000000000..403b90506
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.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_CompositorWidgetChild_h
+#define widget_gtk_CompositorWidgetChild_h
+
+#include "X11CompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetChild.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetChild final
+ : public PCompositorWidgetChild
+ , public CompositorWidgetDelegate
+{
+public:
+ CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver);
+ ~CompositorWidgetChild() override;
+
+ bool RecvObserveVsync() override;
+ bool 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 000000000..c882f4f08
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.cpp
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CompositorWidgetParent.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetParent::CompositorWidgetParent(const CompositorWidgetInitData& aInitData)
+ : X11CompositorWidget(aInitData)
+{
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+}
+
+CompositorWidgetParent::~CompositorWidgetParent()
+{
+}
+
+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;
+}
+
+bool
+CompositorWidgetParent::RecvNotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize)
+{
+ NotifyClientSizeChanged(aClientSize);
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/CompositorWidgetParent.h b/widget/gtk/CompositorWidgetParent.h
new file mode 100644
index 000000000..e80c0f8b2
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.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 widget_gtk_CompositorWidgetParent_h
+#define widget_gtk_CompositorWidgetParent_h
+
+#include "X11CompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetParent final
+ : public PCompositorWidgetParent,
+ public X11CompositorWidget
+{
+public:
+ explicit CompositorWidgetParent(const CompositorWidgetInitData& aInitData);
+ ~CompositorWidgetParent() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override { }
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ RefPtr<VsyncObserver> GetVsyncObserver() const override;
+
+ bool RecvNotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+
+private:
+ RefPtr<VsyncObserver> mVsyncObserver;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_CompositorWidgetParent_h
diff --git a/widget/gtk/IMContextWrapper.cpp b/widget/gtk/IMContextWrapper.cpp
new file mode 100644
index 000000000..58d7a3681
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -0,0 +1,2359 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 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/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.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*
+GetEnabledStateName(uint32_t aState)
+{
+ switch (aState) {
+ case IMEState::DISABLED:
+ return "DISABLED";
+ case IMEState::ENABLED:
+ return "ENABLED";
+ case IMEState::PASSWORD:
+ return "PASSWORD";
+ case IMEState::PLUGIN:
+ return "PLUG_IN";
+ default:
+ return "UNKNOWN ENABLED STATUS!!";
+ }
+}
+
+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 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() {}
+};
+
+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(uint8_t 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)", 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() {};
+};
+
+const static bool kUseSimpleContextDefault = MOZ_WIDGET_GTK == 2;
+
+/******************************************************************************
+ * IMContextWrapper
+ ******************************************************************************/
+
+IMContextWrapper* IMContextWrapper::sLastFocusedContext = nullptr;
+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)
+ , mIsIMFocused(false)
+ , mIsDeletingSurrounding(false)
+ , mLayoutChanged(false)
+ , mSetCursorPositionOnKeyEvent(true)
+ , mPendingResettingIMContext(false)
+ , mRetrieveSurroundingSignalReceived(false)
+{
+ static bool sFirstInstance = true;
+ if (sFirstInstance) {
+ sFirstInstance = false;
+ sUseSimpleContext =
+ Preferences::GetBool(
+ "intl.ime.use_simple_context_on_password_field",
+ kUseSimpleContextDefault);
+ }
+ Init();
+}
+
+void
+IMContextWrapper::Init()
+{
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p Init(), mOwnerWindow=0x%p",
+ this, mOwnerWindow));
+
+ MozContainer* container = mOwnerWindow->GetMozContainer();
+ NS_PRECONDITION(container, "container is null");
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
+
+ // 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);
+
+ // 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);
+}
+
+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;
+}
+
+nsIMEUpdatePreference
+IMContextWrapper::GetIMEUpdatePreference() const
+{
+ // While a plugin has focus, IMContextWrapper doesn't need any
+ // notifications.
+ if (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
+ return nsIMEUpdatePreference();
+ }
+
+ nsIMEUpdatePreference::Notifications notifications =
+ nsIMEUpdatePreference::NOTIFY_NOTHING;
+ // If it's not enabled, we don't need position change notification.
+ if (IsEnabled()) {
+ notifications |= nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE;
+ }
+ nsIMEUpdatePreference updatePreference(notifications);
+ return updatePreference;
+}
+
+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));
+
+ NS_PRECONDITION(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 = IMEState::DISABLED;
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p OnDestroyWindow(), succeeded, Completely destroyed",
+ this));
+}
+
+// Work around gtk bug http://bugzilla.gnome.org/show_bug.cgi?id=483223:
+// (and the similar issue of GTK+ IIIM)
+// The GTK+ XIM and IIIM modules register handlers for the "closed" signal
+// on the display, but:
+// * The signal handlers are not disconnected when the module is unloaded.
+//
+// The GTK+ XIM module has another problem:
+// * When the signal handler is run (with the module loaded) it tries
+// XFree (and fails) on a pointer that did not come from Xmalloc.
+//
+// To prevent these modules from being unloaded, use static variables to
+// hold ref of GtkIMContext class.
+// For GTK+ XIM module, to prevent the signal handler from being run,
+// find the signal handlers and remove them.
+//
+// GtkIMContextXIMs share XOpenIM connections and display closed signal
+// handlers (where possible).
+
+void
+IMContextWrapper::PrepareToDestroyContext(GtkIMContext* aContext)
+{
+#if (MOZ_WIDGET_GTK == 2)
+ GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT(aContext);
+ GtkIMContext *slave = multicontext->slave;
+#else
+ GtkIMContext *slave = nullptr; //TODO GTK3
+#endif
+ if (!slave) {
+ return;
+ }
+
+ GType slaveType = G_TYPE_FROM_INSTANCE(slave);
+ const gchar *im_type_name = g_type_name(slaveType);
+ if (strcmp(im_type_name, "GtkIMContextIIIM") == 0) {
+ // Add a reference to prevent the IIIM module from being unloaded
+ static gpointer gtk_iiim_context_class =
+ g_type_class_ref(slaveType);
+ // Mute unused variable warning:
+ (void)gtk_iiim_context_class;
+ }
+}
+
+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();
+}
+
+bool
+IMContextWrapper::OnKeyEvent(nsWindow* aCaller,
+ GdkEventKey* aEvent,
+ bool aKeyDownEventWasSent /* = false */)
+{
+ NS_PRECONDITION(aEvent, "aEvent must be non-null");
+
+ if (!mInputContext.mIMEState.MaybeEditable() ||
+ MOZ_UNLIKELY(IsDestroyed())) {
+ return false;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnKeyEvent(aCaller=0x%p, aKeyDownEventWasSent=%s), "
+ "mCompositionState=%s, current context=0x%p, active context=0x%p, "
+ "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X }",
+ this, aCaller, ToChar(aKeyDownEventWasSent),
+ GetCompositionStateName(), GetCurrentContext(), GetActiveContext(),
+ aEvent, GetEventType(aEvent), gdk_keyval_name(aEvent->keyval),
+ gdk_keyval_to_unicode(aEvent->keyval)));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnKeyEvent(), FAILED, the caller isn't focused "
+ "window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return false;
+ }
+
+ // 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 false;
+ }
+
+ if (mSetCursorPositionOnKeyEvent) {
+ SetCursorPosition(currentContext);
+ mSetCursorPositionOnKeyEvent = false;
+ }
+
+ mKeyDownEventWasSent = aKeyDownEventWasSent;
+ mFilterKeyEvent = true;
+ mProcessingKeyEvent = aEvent;
+ gboolean isFiltered =
+ gtk_im_context_filter_keypress(currentContext, aEvent);
+ mProcessingKeyEvent = nullptr;
+
+ // We filter the key event if the event was not committed (because
+ // it's probably part of a composition) or if the key event was
+ // committed _and_ changed. This way we still let key press
+ // events go through as simple key press events instead of
+ // composed characters.
+ bool filterThisEvent = isFiltered && mFilterKeyEvent;
+
+ if (IsComposingOnCurrentContext() && !isFiltered) {
+ if (aEvent->type == GDK_KEY_PRESS) {
+ if (!mDispatchedCompositionString.IsEmpty()) {
+ // If there is composition string, we shouldn't dispatch
+ // any keydown events during composition.
+ filterThisEvent = true;
+ } else {
+ // 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.
+ DispatchCompositionCommitEvent(currentContext, &EmptyString());
+ filterThisEvent = false;
+ }
+ } else {
+ // Key release event may not be consumed by IM, however, we
+ // shouldn't dispatch any keyup event during composition.
+ filterThisEvent = true;
+ }
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p OnKeyEvent(), succeeded, filterThisEvent=%s "
+ "(isFiltered=%s, mFilterKeyEvent=%s), mCompositionState=%s",
+ this, ToChar(filterThisEvent), ToChar(isFiltered),
+ ToChar(mFilterKeyEvent), GetCompositionStateName()));
+
+ return filterThisEvent;
+}
+
+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.
+ mSelectedString.Truncate();
+ mSelection.Clear();
+}
+
+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, GetEnabledStateName(aContext->mIMEState.mEnabled),
+ 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.MaybeEditable()) {
+ EndIMEComposition(mLastFocusedWindow);
+ Blur();
+ }
+
+ mInputContext = *aContext;
+
+ if (changingEnabledState) {
+#if (MOZ_WIDGET_GTK == 3)
+ static bool sInputPurposeSupported = !gtk_check_version(3, 6, 0);
+ if (sInputPurposeSupported && mInputContext.mIMEState.MaybeEditable()) {
+ 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 == IMEState::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;
+ }
+
+ g_object_set(currentContext, "input-purpose", purpose, nullptr);
+ }
+ }
+#endif // #if (MOZ_WIDGET_GTK == 3)
+
+ // 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 == IMEState::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 == IMEState::ENABLED ||
+ mInputContext.mIMEState.mEnabled == IMEState::PLUGIN ||
+ (!sUseSimpleContext &&
+ mInputContext.mIMEState.mEnabled == IMEState::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;
+
+ 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={ mOffset=%u, Length()=%u, mReversed=%s, "
+ "mWritingMode=%s, mCausedByComposition=%s, "
+ "mCausedBySelectionEvent=%s, mOccurredDuringComposition=%s "
+ "} }), mCompositionState=%s, mIsDeletingSurrounding=%s, "
+ "mRetrieveSurroundingSignalReceived=%s",
+ this, aCaller, selectionChangeData.mOffset,
+ selectionChangeData.Length(),
+ ToChar(selectionChangeData.mReversed),
+ GetWritingModeName(selectionChangeData.GetWritingMode()).get(),
+ ToChar(selectionChangeData.mCausedByComposition),
+ ToChar(selectionChangeData.mCausedBySelectionEvent),
+ ToChar(selectionChangeData.mOccurredDuringComposition),
+ 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 mSelectedString? 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::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",
+ 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 OnStartCompositionNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return;
+ }
+
+ mComposingContext = static_cast<GtkIMContext*>(g_object_ref(aContext));
+
+ 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)",
+ this, aContext));
+
+ // 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;
+ }
+
+ 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)",
+ this, aContext));
+
+ // 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;
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(aContext, compositionString);
+ if (!IsComposing() && compositionString.IsEmpty()) {
+ 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;
+ }
+
+ 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;
+
+ 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) && !commitString[0]) {
+ return;
+ }
+
+ // If IME doesn't change their keyevent that generated this commit,
+ // don't send it through XIM - just send it as a normal key press
+ // event.
+ // 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 &&
+ 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 (!strcmp(commitString, keyval_utf8)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "we'll send normal key event",
+ this));
+ mFilterKeyEvent = false;
+ 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(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::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;
+ }
+
+ // 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 (mProcessingKeyEvent && !mKeyDownEventWasSent &&
+ mProcessingKeyEvent->type == GDK_KEY_PRESS) {
+ // If this composition is started by a native keydown event, we need to
+ // dispatch our keydown event here (before composition start).
+ bool isCancelled;
+ mLastFocusedWindow->DispatchKeyDownEvent(mProcessingKeyEvent,
+ &isCancelled);
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p DispatchCompositionStart(), FAILED, keydown event "
+ "is dispatched",
+ this));
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, the focused "
+ "widget was destroyed/changed by keydown event",
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ 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(&mSelectedString))) {
+ // 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;
+ }
+
+ 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;
+ }
+ }
+
+ 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);
+
+ mCompositionState = eCompositionState_NotComposing;
+ mCompositionStart = UINT32_MAX;
+ mCompositionTargetRange.Clear();
+ mDispatchedCompositionString.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=%d)",
+ 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();
+ do {
+ TextRange range;
+ if (!SetTextRange(iter, preedit_string, caretOffsetInUTF16, range)) {
+ continue;
+ }
+ MOZ_ASSERT(range.Length());
+ minOffsetOfClauses = std::min(minOffsetOfClauses, range.mStartOffset);
+ 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);
+ 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)));
+ }
+
+ 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 (!attrUnderline && !attrForeground && !attrBackground) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Warning,
+ ("0x%p SetTextRange(), FAILED, due to no attr, "
+ "aTextRange= { mStartOffset=%u, mEndOffset=%u }",
+ this, aTextRange.mStartOffset, aTextRange.mEndOffset));
+ return false;
+ }
+
+ // 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, mLength=%u, mWritingMode=%s }",
+ this, aContext, mCompositionTargetRange.mOffset,
+ mCompositionTargetRange.mLength,
+ mSelection.mOffset, mSelection.mLength,
+ 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 charRect(true,
+ useCaret ? eQueryCaretRect :
+ eQueryTextRect,
+ mLastFocusedWindow);
+ if (useCaret) {
+ charRect.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;
+ charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset,
+ length);
+ } else {
+ charRect.InitForQueryTextRect(mCompositionTargetRange.mOffset, 1);
+ }
+ }
+ InitEvent(charRect);
+ nsEventStatus status;
+ mLastFocusedWindow->DispatchEvent(&charRect, status);
+ if (!charRect.mSucceeded) {
+ 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 = charRect.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 = mSelectedString.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.mLength;
+ }
+
+ 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);
+ NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
+
+ nsAutoString textContent(queryTextContentEvent.mReply.mString);
+ if (selOffset + selLength > textContent.Length()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, The selection is "
+ "invalid, textContent.Length()=%u",
+ this, textContent.Length()));
+ 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.
+ if (EditorHasCompositionString() &&
+ mDispatchedCompositionString != mSelectedString) {
+ textContent.Replace(mCompositionStart,
+ mDispatchedCompositionString.Length(), mSelectedString);
+ }
+
+ // 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, &mSelectedString)) {
+ 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);
+ NS_ENSURE_TRUE(queryTextContentEvent.mSucceeded, NS_ERROR_FAILURE);
+ if (queryTextContentEvent.mReply.mString.IsEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, there is no contents",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8Str(
+ nsDependentSubstring(queryTextContentEvent.mReply.mString,
+ 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: %d)",
+ this, offsetInUTF8Characters));
+ return NS_ERROR_FAILURE;
+ }
+
+ AppendUTF16toUTF8(
+ nsDependentSubstring(queryTextContentEvent.mReply.mString, 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: %d, computed end offset: %d)",
+ 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;
+ }
+
+ // 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() &&
+ (!mSelection.Collapsed() || !aSelectedString)) {
+ 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 selection(true, eQuerySelectedText,
+ mLastFocusedWindow);
+ InitEvent(selection);
+ mLastFocusedWindow->DispatchEvent(&selection, status);
+ if (NS_WARN_IF(!selection.mSucceeded)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "failure of query selection event",
+ this));
+ return false;
+ }
+
+ mSelection.Assign(selection);
+ 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(selection.mReply.mString);
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p EnsureToCacheSelection(), Succeeded, mSelection="
+ "{ mOffset=%u, mLength=%u, mWritingMode=%s }",
+ this, mSelection.mOffset, mSelection.mLength,
+ GetWritingModeName(mSelection.mWritingMode).get()));
+ return true;
+}
+
+/******************************************************************************
+ * IMContextWrapper::Selection
+ ******************************************************************************/
+
+void
+IMContextWrapper::Selection::Assign(const IMENotification& aIMENotification)
+{
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
+ mOffset = aIMENotification.mSelectionChangeData.mOffset;
+ mLength = aIMENotification.mSelectionChangeData.Length();
+ mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
+}
+
+void
+IMContextWrapper::Selection::Assign(const WidgetQueryContentEvent& aEvent)
+{
+ MOZ_ASSERT(aEvent.mMessage == eQuerySelectedText);
+ MOZ_ASSERT(aEvent.mSucceeded);
+ mOffset = aEvent.mReply.mOffset;
+ mLength = aEvent.mReply.mString.Length();
+ mWritingMode = aEvent.GetWritingMode();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/IMContextWrapper.h b/widget/gtk/IMContextWrapper.h
new file mode 100644
index 000000000..e869c160a
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.h
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 {
+
+class IMContextWrapper final : public TextEventDispatcherListener
+{
+public:
+ // TextEventDispatcherListener implementation
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(void) OnRemovedFrom(
+ TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void) WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress,
+ void* aData) override;
+
+public:
+ // 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);
+
+ // "Enabled" means the users can use all IMEs.
+ // I.e., the focus is in the normal editors.
+ bool IsEnabled() const;
+
+ nsIMEUpdatePreference GetIMEUpdatePreference() 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);
+
+ // 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 keypress event starts composition, this returns TRUE but
+ // this dispatches keydown event before compositionstart event.
+ bool OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent,
+ bool aKeyDownEventWasSent = 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();
+
+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;
+
+ // mSelectedString is the selected string which was removed by first
+ // compositionchange event.
+ nsString mSelectedString;
+
+ // OnKeyEvent() temporarily sets mProcessingKeyEvent to the given native
+ // event.
+ GdkEventKey* mProcessingKeyEvent;
+
+ 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 {
+ 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";
+ }
+ }
+
+ struct Selection final
+ {
+ uint32_t mOffset;
+ uint32_t mLength;
+ WritingMode mWritingMode;
+
+ Selection()
+ : mOffset(UINT32_MAX)
+ , mLength(UINT32_MAX)
+ {
+ }
+
+ void Clear()
+ {
+ mOffset = UINT32_MAX;
+ mLength = UINT32_MAX;
+ mWritingMode = WritingMode();
+ }
+
+ void Assign(const IMENotification& aIMENotification);
+ void Assign(const WidgetQueryContentEvent& aSelectedTextEvent);
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ bool Collapsed() const { return !mLength; }
+ uint32_t EndOffset() const
+ {
+ if (NS_WARN_IF(!IsValid())) {
+ return UINT32_MAX;
+ }
+ CheckedInt<uint32_t> endOffset =
+ CheckedInt<uint32_t>(mOffset) + mLength;
+ 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;
+ // mFilterKeyEvent is used by OnKeyEvent(). If the commit event should
+ // be processed as simple key event, this is set to TRUE by the commit
+ // handler.
+ bool mFilterKeyEvent;
+ // mKeyDownEventWasSent is used by OnKeyEvent() and
+ // DispatchCompositionStart(). DispatchCompositionStart() dispatches
+ // a keydown event if the composition start is caused by a native
+ // keypress event. If this is true, the keydown event has been dispatched.
+ // Then, DispatchCompositionStart() doesn't dispatch keydown event.
+ bool mKeyDownEventWasSent;
+ // 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;
+
+ // 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 current composition of IME. All native composition events
+ // during this processing are 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.
+ * - DispatchCompositionStart
+ * - DispatchCompositionChangeEvent
+ * - DispatchCompositionCommitEvent
+ */
+
+ /**
+ * 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/InProcessX11CompositorWidget.cpp b/widget/gtk/InProcessX11CompositorWidget.cpp
new file mode 100644
index 000000000..9580b3150
--- /dev/null
+++ b/widget/gtk/InProcessX11CompositorWidget.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 "InProcessX11CompositorWidget.h"
+
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+/* static */ RefPtr<CompositorWidget>
+CompositorWidget::CreateLocal(const CompositorWidgetInitData& aInitData, nsIWidget* aWidget)
+{
+ return new InProcessX11CompositorWidget(aInitData, static_cast<nsWindow*>(aWidget));
+}
+
+InProcessX11CompositorWidget::InProcessX11CompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow)
+ : X11CompositorWidget(aInitData, aWindow)
+{
+}
+
+void
+InProcessX11CompositorWidget::ObserveVsync(VsyncObserver* aObserver)
+{
+ if (RefPtr<CompositorVsyncDispatcher> cvd = mWidget->GetCompositorVsyncDispatcher()) {
+ cvd->SetCompositorVsyncObserver(aObserver);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/InProcessX11CompositorWidget.h b/widget/gtk/InProcessX11CompositorWidget.h
new file mode 100644
index 000000000..7f4077451
--- /dev/null
+++ b/widget/gtk/InProcessX11CompositorWidget.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_InProcessX11CompositorWidgetParent_h
+#define widget_gtk_InProcessX11CompositorWidgetParent_h
+
+#include "X11CompositorWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class InProcessX11CompositorWidget final : public X11CompositorWidget
+{
+public:
+ InProcessX11CompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow);
+
+ // CompositorWidgetDelegate
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_InProcessX11CompositorWidgetParent_h
diff --git a/widget/gtk/NativeKeyBindings.cpp b/widget/gtk/NativeKeyBindings.cpp
new file mode 100644
index 000000000..55a3508e2
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 nsIWidget::DoCommandCallback gCurrentCallback;
+static void *gCurrentCallbackData;
+static bool gHandled;
+
+// Common GtkEntry and GtkTextView signals
+static void
+copy_clipboard_cb(GtkWidget *w, gpointer user_data)
+{
+ gCurrentCallback(CommandCopy, gCurrentCallbackData);
+ g_signal_stop_emission_by_name(w, "copy_clipboard");
+ gHandled = true;
+}
+
+static void
+cut_clipboard_cb(GtkWidget *w, gpointer user_data)
+{
+ gCurrentCallback(CommandCut, gCurrentCallbackData);
+ 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
+ { CommandDeleteCharBackward, CommandDeleteCharForward }, // CHARS
+ { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORD_ENDS
+ { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORDS
+ { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINES
+ { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINE_ENDS
+ { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPH_ENDS
+ { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPHS
+ // 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
+ { CommandDoNothing, CommandDoNothing } // 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");
+ bool forward = count > 0;
+
+#if (MOZ_WIDGET_GTK == 3)
+ // 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);
+ }
+#endif
+
+ 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) {
+ gCurrentCallback(CommandWordNext, gCurrentCallbackData);
+ gCurrentCallback(CommandWordPrevious, gCurrentCallbackData);
+ } else {
+ gCurrentCallback(CommandWordPrevious, gCurrentCallbackData);
+ gCurrentCallback(CommandWordNext, gCurrentCallbackData);
+ }
+ } 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) {
+ gCurrentCallback(CommandBeginLine, gCurrentCallbackData);
+ } else {
+ gCurrentCallback(CommandEndLine, gCurrentCallbackData);
+ }
+ }
+
+ Command command = sDeleteCommands[del_type][forward];
+ if (!command) {
+ return; // unsupported command
+ }
+
+ unsigned int absCount = Abs(count);
+ for (unsigned int i = 0; i < absCount; ++i) {
+ gCurrentCallback(command, gCurrentCallbackData);
+ }
+}
+
+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
+ { CommandCharPrevious, CommandCharNext },
+ { CommandSelectCharPrevious, CommandSelectCharNext }
+ },
+ { // VISUAL_POSITIONS
+ { CommandCharPrevious, CommandCharNext },
+ { CommandSelectCharPrevious, CommandSelectCharNext }
+ },
+ { // WORDS
+ { CommandWordPrevious, CommandWordNext },
+ { CommandSelectWordPrevious, CommandSelectWordNext }
+ },
+ { // DISPLAY_LINES
+ { CommandLinePrevious, CommandLineNext },
+ { CommandSelectLinePrevious, CommandSelectLineNext }
+ },
+ { // DISPLAY_LINE_ENDS
+ { CommandBeginLine, CommandEndLine },
+ { CommandSelectBeginLine, CommandSelectEndLine }
+ },
+ { // PARAGRAPHS
+ { CommandLinePrevious, CommandLineNext },
+ { CommandSelectLinePrevious, CommandSelectLineNext }
+ },
+ { // PARAGRAPH_ENDS
+ { CommandBeginLine, CommandEndLine },
+ { CommandSelectBeginLine, CommandSelectEndLine }
+ },
+ { // PAGES
+ { CommandMovePageUp, CommandMovePageDown },
+ { CommandSelectPageUp, CommandSelectPageDown }
+ },
+ { // BUFFER_ENDS
+ { CommandMoveTop, CommandMoveBottom },
+ { CommandSelectTop, CommandSelectBottom }
+ },
+ { // HORIZONTAL_PAGES (unsupported)
+ { CommandDoNothing, CommandDoNothing },
+ { CommandDoNothing, CommandDoNothing }
+ }
+};
+
+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");
+ 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) {
+ return; // unsupported command
+ }
+
+ unsigned int absCount = Abs(count);
+ for (unsigned int i = 0; i < absCount; ++i) {
+ gCurrentCallback(command, gCurrentCallbackData);
+ }
+}
+
+static void
+paste_clipboard_cb(GtkWidget *w, gpointer user_data)
+{
+ gCurrentCallback(CommandPaste, gCurrentCallbackData);
+ 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)
+{
+ gCurrentCallback(CommandSelectAll, gCurrentCallbackData);
+ 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);
+}
+
+bool
+NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ // 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 false;
+ }
+
+ guint keyval;
+
+ if (aEvent.mCharCode) {
+ keyval = gdk_unicode_to_keyval(aEvent.mCharCode);
+ } else {
+ keyval =
+ static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->keyval;
+ }
+
+ if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) {
+ return true;
+ }
+
+ 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 (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) {
+ return true;
+ }
+ }
+ }
+
+/*
+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));
+*/
+
+ return false;
+}
+
+bool
+NativeKeyBindings::ExecuteInternal(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ guint aKeyval)
+{
+ guint modifiers =
+ static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->state;
+
+ gCurrentCallback = aCallback;
+ gCurrentCallbackData = aCallbackData;
+
+ gHandled = false;
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_bindings_activate(GTK_OBJECT(mNativeTarget),
+ aKeyval, GdkModifierType(modifiers));
+#else
+ gtk_bindings_activate(G_OBJECT(mNativeTarget),
+ aKeyval, GdkModifierType(modifiers));
+#endif
+
+ gCurrentCallback = nullptr;
+ gCurrentCallbackData = nullptr;
+
+ return gHandled;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/NativeKeyBindings.h b/widget/gtk/NativeKeyBindings.h
new file mode 100644
index 000000000..f1632f9c5
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_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;
+ typedef nsIWidget::DoCommandCallback DoCommandCallback;
+
+public:
+ static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
+ static void Shutdown();
+
+ void Init(NativeKeyBindingsType aType);
+
+ bool Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData);
+
+private:
+ ~NativeKeyBindings();
+
+ bool ExecuteInternal(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ 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 000000000..178fe78e4
--- /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 000000000..145d39546
--- /dev/null
+++ b/widget/gtk/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,24 @@
+/* -*- 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.
+
+using mozilla::LayoutDeviceIntSize from "Units.h";
+
+namespace mozilla {
+namespace widget {
+
+struct CompositorWidgetInitData
+{
+ uintptr_t XWindow;
+ nsCString XDisplayString;
+
+ LayoutDeviceIntSize InitialClientSize;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WakeLockListener.cpp b/widget/gtk/WakeLockListener.cpp
new file mode 100644
index 000000000..54bad17ff
--- /dev/null
+++ b/widget/gtk/WakeLockListener.cpp
@@ -0,0 +1,365 @@
+/* -*- 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>
+
+#include "mozilla/ipc/DBusMessageRefPtr.h"
+#include "mozilla/ipc/DBusPendingCallRefPtr.h"
+
+#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;
+
+NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener)
+
+WakeLockListener* WakeLockListener::sSingleton = nullptr;
+
+
+enum DesktopEnvironment {
+ FreeDesktop,
+ GNOME,
+ Unsupported,
+};
+
+class WakeLockTopic
+{
+public:
+ WakeLockTopic(const nsAString& aTopic, DBusConnection* aConnection)
+ : 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);
+
+ 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);
+}
+
+
+bool
+WakeLockTopic::SendInhibit()
+{
+ bool sendOk = false;
+
+ switch (mDesktopEnvironment)
+ {
+ case FreeDesktop:
+ sendOk = SendFreeDesktopInhibitMessage();
+ break;
+ case GNOME:
+ sendOk = SendGNOMEInhibitMessage();
+ break;
+ 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 (!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;
+ } else {
+ NS_ASSERTION(mDesktopEnvironment == GNOME, "Unknown desktop environment");
+ 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(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);
+ }
+}
+
+/* static */ WakeLockListener*
+WakeLockListener::GetSingleton(bool aCreate)
+{
+ if (!sSingleton && aCreate) {
+ sSingleton = new WakeLockListener();
+ sSingleton->AddRef();
+ }
+
+ return sSingleton;
+}
+
+/* static */ void
+WakeLockListener::Shutdown()
+{
+ sSingleton->Release();
+ sSingleton = nullptr;
+}
+
+nsresult
+WakeLockListener::Callback(const nsAString& topic, const nsAString& state)
+{
+ if (!mConnection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if(!topic.Equals(NS_LITERAL_STRING("screen")))
+ 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");
+
+ return shouldLock ?
+ topicLock->InhibitScreensaver() :
+ topicLock->UninhibitScreensaver();
+}
+
+#endif
diff --git a/widget/gtk/WakeLockListener.h b/widget/gtk/WakeLockListener.h
new file mode 100644
index 000000000..fc7281822
--- /dev/null
+++ b/widget/gtk/WakeLockListener.h
@@ -0,0 +1,53 @@
+/* -*- 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 <unistd.h>
+
+#ifndef __WakeLockListener_h__
+#define __WakeLockListener_h__
+
+#include "nsHashKeys.h"
+#include "nsClassHashtable.h"
+
+#include "nsIDOMWakeLockListener.h"
+
+#ifdef MOZ_ENABLE_DBUS
+#include "mozilla/ipc/DBusConnectionRefPtr.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;
+
+ static 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/WidgetStyleCache.cpp b/widget/gtk/WidgetStyleCache.cpp
new file mode 100644
index 000000000..fd099681f
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -0,0 +1,1162 @@
+/* -*- 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"
+
+#define STATE_FLAG_DIR_LTR (1U << 7)
+#define STATE_FLAG_DIR_RTL (1U << 8)
+#if GTK_CHECK_VERSION(3,8,0)
+static_assert(GTK_STATE_FLAG_DIR_LTR == STATE_FLAG_DIR_LTR &&
+ GTK_STATE_FLAG_DIR_RTL == STATE_FLAG_DIR_RTL,
+ "incorrect direction state flags");
+#endif
+
+static GtkWidget* sWidgetStorage[MOZ_GTK_WIDGET_NODE_COUNT];
+static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT];
+
+static bool sStyleContextNeedsRestore;
+#ifdef DEBUG
+static GtkStyleContext* sCurrentStyleContext;
+#endif
+static GtkStyleContext*
+GetWidgetRootStyle(WidgetNodeType aNodeType);
+static GtkStyleContext*
+GetCssNodeStyleInternal(WidgetNodeType aNodeType);
+
+static GtkWidget*
+CreateWindowWidget()
+{
+ GtkWidget *widget = gtk_window_new(GTK_WINDOW_POPUP);
+ 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 aWidgetType, 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*
+CreateTextViewWidget()
+{
+ GtkWidget* widget = gtk_text_view_new();
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_SCROLLED_WINDOW)),
+ 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;
+}
+
+static GtkWidget*
+CreateWidget(WidgetNodeType aWidgetType)
+{
+ switch (aWidgetType) {
+ 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(aWidgetType,
+ GTK_ORIENTATION_HORIZONTAL);
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ return CreateScrollbarWidget(aWidgetType,
+ 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:
+ return CreateEntryWidget();
+ case MOZ_GTK_SCROLLED_WINDOW:
+ return CreateScrolledWindowWidget();
+ case MOZ_GTK_TEXT_VIEW:
+ return CreateTextViewWidget();
+ 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();
+ default:
+ /* Not implemented */
+ return nullptr;
+ }
+}
+
+GtkWidget*
+GetWidget(WidgetNodeType aWidgetType)
+{
+ GtkWidget* widget = sWidgetStorage[aWidgetType];
+ if (!widget) {
+ widget = CreateWidget(aWidgetType);
+ sWidgetStorage[aWidgetType] = widget;
+ }
+ return widget;
+}
+
+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);
+ GList* classes = gtk_style_context_list_classes(widgetStyle);
+ for (GList* link = classes; link; link = link->next) {
+ gtk_style_context_add_class(context, static_cast<gchar*>(link->data));
+ }
+ g_list_free(classes);
+
+ // 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);
+
+ 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_IMAGEMENUITEM:
+ style = CreateStyleForWidget(gtk_image_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_CHECKMENUITEM_CONTAINER:
+ style = CreateStyleForWidget(gtk_check_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_RADIOMENUITEM_CONTAINER:
+ style = CreateStyleForWidget(gtk_radio_menu_item_new(nullptr),
+ MOZ_GTK_MENUPOPUP);
+ 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));
+}
+
+static GtkStyleContext*
+GetWidgetStyleWithClass(WidgetNodeType aWidgetType, const gchar* aStyleClass)
+{
+ GtkStyleContext* style = GetWidgetRootStyle(aWidgetType);
+ gtk_style_context_save(style);
+ MOZ_ASSERT(!sStyleContextNeedsRestore);
+ sStyleContextNeedsRestore = true;
+ 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:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO,
+ MOZ_GTK_RADIOMENUITEM_CONTAINER);
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK,
+ MOZ_GTK_CHECKMENUITEM_CONTAINER);
+ 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_TOOLTIP:
+ // 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_GRIPPER:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_GRIPPER,
+ GTK_STYLE_CLASS_GRIP);
+ case MOZ_GTK_INFO_BAR:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_INFO_BAR,
+ GTK_STYLE_CLASS_INFO);
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_SPINBUTTON,
+ GTK_STYLE_CLASS_ENTRY);
+ case MOZ_GTK_SCROLLED_WINDOW:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
+ GTK_STYLE_CLASS_FRAME);
+ case MOZ_GTK_TEXT_VIEW:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW,
+ GTK_STYLE_CLASS_VIEW);
+ case MOZ_GTK_FRAME_BORDER:
+ style = CreateChildCSSNode("border", MOZ_GTK_FRAME);
+ break;
+ case MOZ_GTK_TREEVIEW_VIEW:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW,
+ GTK_STYLE_CLASS_VIEW);
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ // TODO - create from CSS node
+ return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW,
+ GTK_STYLE_CLASS_EXPANDER);
+ 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 = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK,
+ GTK_STYLE_CLASS_TOP);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ return style;
+ }
+ case MOZ_GTK_TAB_BOTTOM:
+ {
+ // TODO - create from CSS node
+ style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK,
+ GTK_STYLE_CLASS_BOTTOM);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ return style;
+ }
+ 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);
+ }
+ 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)
+{
+ switch (aNodeType) {
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ GTK_STYLE_CLASS_SLIDER);
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
+ GTK_STYLE_CLASS_SLIDER);
+ case MOZ_GTK_RADIOBUTTON:
+ return GetWidgetStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER,
+ GTK_STYLE_CLASS_RADIO);
+ case MOZ_GTK_CHECKBUTTON:
+ return GetWidgetStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER,
+ GTK_STYLE_CLASS_CHECK);
+ case MOZ_GTK_RADIOMENUITEM:
+ return GetWidgetStyleWithClass(MOZ_GTK_RADIOMENUITEM_CONTAINER,
+ GTK_STYLE_CLASS_RADIO);
+ case MOZ_GTK_CHECKMENUITEM:
+ return GetWidgetStyleWithClass(MOZ_GTK_CHECKMENUITEM_CONTAINER,
+ GTK_STYLE_CLASS_CHECK);
+ case MOZ_GTK_PROGRESS_TROUGH:
+ return GetWidgetStyleWithClass(MOZ_GTK_PROGRESSBAR,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_TOOLTIP: {
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style)
+ return style;
+
+ // The tooltip style class is added first in CreateTooltipWidget() so
+ // that gtk_widget_path_append_for_widget() in CreateStyleForWidget()
+ // will find it.
+ GtkWidget* tooltipWindow = CreateTooltipWidget();
+ style = CreateStyleForWidget(tooltipWindow, nullptr);
+ gtk_widget_destroy(tooltipWindow); // Release GtkWindow self-reference.
+ sStyleStorage[aNodeType] = style;
+ return style;
+ }
+ case MOZ_GTK_GRIPPER:
+ return GetWidgetStyleWithClass(MOZ_GTK_GRIPPER,
+ GTK_STYLE_CLASS_GRIP);
+ case MOZ_GTK_INFO_BAR:
+ return GetWidgetStyleWithClass(MOZ_GTK_INFO_BAR,
+ GTK_STYLE_CLASS_INFO);
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ return GetWidgetStyleWithClass(MOZ_GTK_SPINBUTTON,
+ GTK_STYLE_CLASS_ENTRY);
+ case MOZ_GTK_SCROLLED_WINDOW:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
+ GTK_STYLE_CLASS_FRAME);
+ case MOZ_GTK_TEXT_VIEW:
+ return GetWidgetStyleWithClass(MOZ_GTK_TEXT_VIEW,
+ GTK_STYLE_CLASS_VIEW);
+ case MOZ_GTK_FRAME_BORDER:
+ return GetWidgetRootStyle(MOZ_GTK_FRAME);
+ case MOZ_GTK_TREEVIEW_VIEW:
+ return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW,
+ GTK_STYLE_CLASS_VIEW);
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ return GetWidgetStyleWithClass(MOZ_GTK_TREEVIEW,
+ GTK_STYLE_CLASS_EXPANDER);
+ case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL,
+ GTK_STYLE_CLASS_PANE_SEPARATOR);
+ case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL,
+ GTK_STYLE_CLASS_PANE_SEPARATOR);
+ case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_SCALE_TROUGH_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
+ GTK_STYLE_CLASS_TROUGH);
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
+ GTK_STYLE_CLASS_SLIDER);
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ return GetWidgetStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
+ GTK_STYLE_CLASS_SLIDER);
+ case MOZ_GTK_TAB_TOP:
+ {
+ GtkStyleContext* style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK,
+ GTK_STYLE_CLASS_TOP);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ return style;
+ }
+ case MOZ_GTK_TAB_BOTTOM:
+ {
+ GtkStyleContext* style = GetWidgetStyleWithClass(MOZ_GTK_NOTEBOOK,
+ GTK_STYLE_CLASS_BOTTOM);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ return style;
+ }
+ 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);
+ }
+}
+
+void
+ResetWidgetCache(void)
+{
+ MOZ_ASSERT(!sStyleContextNeedsRestore);
+#ifdef DEBUG
+ MOZ_ASSERT(!sCurrentStyleContext);
+#endif
+
+ 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]);
+
+ /* Clear already freed arrays */
+ mozilla::PodArrayZero(sWidgetStorage);
+}
+
+GtkStyleContext*
+ClaimStyleContext(WidgetNodeType aNodeType, GtkTextDirection aDirection,
+ GtkStateFlags aStateFlags, StyleFlags aFlags)
+{
+ MOZ_ASSERT(!sStyleContextNeedsRestore);
+ GtkStyleContext* style;
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ style = GetWidgetStyleInternal(aNodeType);
+ } else {
+ style = GetCssNodeStyleInternal(aNodeType);
+ }
+#ifdef DEBUG
+ MOZ_ASSERT(!sCurrentStyleContext);
+ sCurrentStyleContext = style;
+#endif
+ 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 saved contexts to avoid performing
+ // build_properties() (in 3.16 stylecontext.c) unnecessarily early.
+ if (stateChanged && !sStyleContextNeedsRestore) {
+ gtk_style_context_invalidate(style);
+ }
+ return style;
+}
+
+void
+ReleaseStyleContext(GtkStyleContext* aStyleContext)
+{
+ if (sStyleContextNeedsRestore) {
+ gtk_style_context_restore(aStyleContext);
+ }
+ sStyleContextNeedsRestore = false;
+#ifdef DEBUG
+ MOZ_ASSERT(sCurrentStyleContext == aStyleContext);
+ sCurrentStyleContext = nullptr;
+#endif
+}
diff --git a/widget/gtk/WidgetStyleCache.h b/widget/gtk/WidgetStyleCache.h
new file mode 100644
index 000000000..2cbb5f960
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.h
@@ -0,0 +1,48 @@
+/* -*- 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"
+
+
+typedef unsigned StyleFlags;
+enum : StyleFlags {
+ NO_STYLE_FLAGS,
+ WHATEVER_MIGHT_BE_NEEDED = 1U << 0,
+};
+
+GtkWidget*
+GetWidget(WidgetNodeType aNodeType);
+
+/*
+ * 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);
+
+// Callers must call ReleaseStyleContext() on the returned context.
+GtkStyleContext*
+ClaimStyleContext(WidgetNodeType aNodeType,
+ GtkTextDirection aDirection = GTK_TEXT_DIR_NONE,
+ GtkStateFlags aStateFlags = GTK_STATE_FLAG_NORMAL,
+ StyleFlags aFlags = NO_STYLE_FLAGS);
+void
+ReleaseStyleContext(GtkStyleContext* style);
+
+void
+ResetWidgetCache(void);
+
+#endif // WidgetStyleCache_h
diff --git a/widget/gtk/WidgetTraceEvent.cpp b/widget/gtk/WidgetTraceEvent.cpp
new file mode 100644
index 000000000..c09944ac0
--- /dev/null
+++ b/widget/gtk/WidgetTraceEvent.cpp
@@ -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 "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 000000000..393f66908
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.cpp
@@ -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/. */
+
+#include "WidgetUtilsGtk.h"
+
+namespace mozilla {
+
+namespace widget {
+
+int32_t WidgetUtilsGTK::IsTouchDeviceSupportPresent()
+{
+#if GTK_CHECK_VERSION(3,4,0)
+ 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;
+#else
+ return 0;
+#endif
+}
+
+} // namespace widget
+
+} // namespace mozilla
diff --git a/widget/gtk/WidgetUtilsGtk.h b/widget/gtk/WidgetUtilsGtk.h
new file mode 100644
index 000000000..8c132c11b
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.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 WidgetUtilsGtk_h__
+#define WidgetUtilsGtk_h__
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace widget {
+
+class WidgetUtilsGTK
+{
+public:
+ /* See WidgetUtils::IsTouchDeviceSupportPresent(). */
+ static int32_t IsTouchDeviceSupportPresent();
+};
+
+} // namespace widget
+
+} // namespace mozilla
+
+#endif // WidgetUtilsGtk_h__
diff --git a/widget/gtk/WindowSurfaceProvider.cpp b/widget/gtk/WindowSurfaceProvider.cpp
new file mode 100644
index 000000000..526fe6a25
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.cpp
@@ -0,0 +1,114 @@
+/* -*- 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 "WindowSurfaceProvider.h"
+
+#include "gfxPlatformGtk.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "WindowSurfaceX11Image.h"
+#include "WindowSurfaceX11SHM.h"
+#include "WindowSurfaceXRender.h"
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+WindowSurfaceProvider::WindowSurfaceProvider()
+ : mXDisplay(nullptr)
+ , mXWindow(0)
+ , mXVisual(nullptr)
+ , mXDepth(0)
+ , mWindowSurface(nullptr)
+{
+}
+
+void WindowSurfaceProvider::Initialize(
+ Display* aDisplay,
+ Window aWindow,
+ Visual* aVisual,
+ int aDepth)
+{
+ // 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;
+}
+void WindowSurfaceProvider::CleanupResources()
+{
+ mWindowSurface = nullptr;
+}
+
+UniquePtr<WindowSurface>
+WindowSurfaceProvider::CreateWindowSurface()
+{
+ // 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
+
+#ifdef MOZ_WIDGET_GTK
+ if (gfxVars::UseXRender()) {
+ LOGDRAW(("Drawing to nsWindow %p using XRender\n", (void*)this));
+ return MakeUnique<WindowSurfaceXRender>(mXDisplay, mXWindow, mXVisual, mXDepth);
+ }
+#endif // MOZ_WIDGET_GTK
+
+#ifdef MOZ_HAVE_SHMIMAGE
+ if (nsShmImage::UseShm()) {
+ LOGDRAW(("Drawing to nsWindow %p using MIT-SHM\n", (void*)this));
+ return MakeUnique<WindowSurfaceX11SHM>(mXDisplay, mXWindow, mXVisual, mXDepth);
+ }
+#endif // MOZ_HAVE_SHMIMAGE
+
+ LOGDRAW(("Drawing to nsWindow %p using XPutImage\n", (void*)this));
+ return MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual, mXDepth);
+}
+
+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<DrawTarget> dt = nullptr;
+ if (!(dt = mWindowSurface->Lock(aInvalidRegion)) &&
+ !mWindowSurface->IsFallback()) {
+ gfxWarningOnce() << "Failed to lock WindowSurface, falling back to XPutImage backend.";
+ mWindowSurface = MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual, mXDepth);
+ dt = mWindowSurface->Lock(aInvalidRegion);
+ }
+ return dt.forget();
+}
+
+void
+WindowSurfaceProvider::EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion)
+{
+ if (mWindowSurface)
+ mWindowSurface->Commit(aInvalidRegion);
+}
+
+} // namespace mozilla
+} // namespace widget
diff --git a/widget/gtk/WindowSurfaceProvider.h b/widget/gtk/WindowSurfaceProvider.h
new file mode 100644
index 000000000..73b23031e
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
+
+#include "mozilla/widget/WindowSurface.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/gfx/2D.h"
+#include "Units.h"
+
+#include <X11/Xlib.h> // for Window, Display, Visual, etc.
+
+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 X11CompositorWidget, 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);
+
+ /**
+ * Releases any surfaces created by this provider.
+ * This is used by X11CompositorWidget 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,
+ LayoutDeviceIntRegion& aInvalidRegion);
+
+private:
+ UniquePtr<WindowSurface> CreateWindowSurface();
+
+ Display* mXDisplay;
+ Window mXWindow;
+ Visual* mXVisual;
+ int mXDepth;
+
+ UniquePtr<WindowSurface> mWindowSurface;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
diff --git a/widget/gtk/WindowSurfaceX11.cpp b/widget/gtk/WindowSurfaceX11.cpp
new file mode 100644
index 000000000..32e3c43ef
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.cpp
@@ -0,0 +1,64 @@
+/* -*- 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 "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:
+ // 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 (aVisual->red_mask == 0xff0000 &&
+ aVisual->green_mask == 0xff00 &&
+ aVisual->blue_mask == 0xff) {
+ gfx::BackendType backend = gfxPlatform::GetPlatform()->GetDefaultContentBackend();
+ return backend == gfx::BackendType::CAIRO ? gfx::SurfaceFormat::B8G8R8X8
+ : gfx::SurfaceFormat::B8G8R8A8;
+ }
+ 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 000000000..46bc10d10
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.h
@@ -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/. */
+
+#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 000000000..5de028804
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.cpp
@@ -0,0 +1,119 @@
+/* -*- 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 "WindowSurfaceX11Image.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "gfxPlatform.h"
+#include "gfx2DGlue.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceX11Image::WindowSurfaceX11Image(Display* aDisplay,
+ Window aWindow,
+ Visual* aVisual,
+ unsigned int aDepth)
+ : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth)
+{
+}
+
+WindowSurfaceX11Image::~WindowSurfaceX11Image()
+{
+}
+
+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;
+ }
+
+ mImageSurface = new gfxImageSurface(size, format);
+ if (mImageSurface->CairoStatus()) {
+ return nullptr;
+ }
+ }
+
+ gfxImageFormat format = mImageSurface->Format();
+ // Cairo prefers compositing to BGRX instead of BGRA where possible.
+ 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));
+}
+
+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(0, 0, bounds.XMost(), bounds.YMost());
+ 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());
+ }
+
+ 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 000000000..236e275d6
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+
+#ifdef MOZ_X11
+
+#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);
+ ~WindowSurfaceX11Image();
+
+ already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+ bool IsFallback() const override { return true; }
+
+private:
+ RefPtr<gfxXlibSurface> mWindowSurface;
+ RefPtr<gfxImageSurface> mImageSurface;
+};
+
+} // 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 000000000..015d623b3
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.cpp
@@ -0,0 +1,83 @@
+/* -*- 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 "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::GetPlatform()->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 000000000..044fd3412
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _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/X11CompositorWidget.cpp b/widget/gtk/X11CompositorWidget.cpp
new file mode 100644
index 000000000..05113a0c3
--- /dev/null
+++ b/widget/gtk/X11CompositorWidget.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "X11CompositorWidget.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 {
+
+X11CompositorWidget::X11CompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow)
+ : mWidget(aWindow)
+{
+ // 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;
+ XGetWindowAttributes(mXDisplay, mXWindow, &windowAttrs);
+
+ Visual* visual = windowAttrs.visual;
+ int depth = windowAttrs.depth;
+
+ // Initialize the window surface provider
+ mProvider.Initialize(
+ mXDisplay,
+ mXWindow,
+ visual,
+ depth
+ );
+
+ mClientSize = aInitData.InitialClientSize();
+}
+
+X11CompositorWidget::~X11CompositorWidget()
+{
+ mProvider.CleanupResources();
+
+ // If we created our own display connection, we need to destroy it
+ if (!mWidget && mXDisplay) {
+ XCloseDisplay(mXDisplay);
+ mXDisplay = nullptr;
+ }
+}
+
+already_AddRefed<gfx::DrawTarget>
+X11CompositorWidget::StartRemoteDrawing()
+{
+ return nullptr;
+}
+void
+X11CompositorWidget::EndRemoteDrawing()
+{
+}
+
+already_AddRefed<gfx::DrawTarget>
+X11CompositorWidget::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode)
+{
+ return mProvider.StartRemoteDrawingInRegion(aInvalidRegion,
+ aBufferMode);
+}
+
+void X11CompositorWidget::EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion)
+{
+ mProvider.EndRemoteDrawingInRegion(aDrawTarget,
+ aInvalidRegion);
+}
+
+nsIWidget* X11CompositorWidget::RealWidget()
+{
+ return mWidget;
+}
+
+void
+X11CompositorWidget::NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize)
+{
+ mClientSize = aClientSize;
+}
+
+LayoutDeviceIntSize
+X11CompositorWidget::GetClientSize()
+{
+ return mClientSize;
+}
+
+uintptr_t
+X11CompositorWidget::GetWidgetKey()
+{
+ return reinterpret_cast<uintptr_t>(mWidget);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/X11CompositorWidget.h b/widget/gtk/X11CompositorWidget.h
new file mode 100644
index 000000000..c0e0edeb3
--- /dev/null
+++ b/widget/gtk/X11CompositorWidget.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 widget_gtk_X11CompositorWidget_h
+#define widget_gtk_X11CompositorWidget_h
+
+#include "mozilla/widget/CompositorWidget.h"
+#include "WindowSurfaceProvider.h"
+
+class nsIWidget;
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetDelegate
+{
+public:
+ virtual void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) = 0;
+};
+
+class X11CompositorWidget
+ : public CompositorWidget
+ , public CompositorWidgetDelegate
+{
+public:
+ X11CompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow = nullptr);
+ ~X11CompositorWidget();
+
+ // 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,
+ LayoutDeviceIntRegion& aInvalidRegion) override;
+ uintptr_t GetWidgetKey() override;
+
+ void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+ LayoutDeviceIntSize GetClientSize() override;
+
+ nsIWidget* RealWidget() override;
+ X11CompositorWidget* AsX11() override { return this; }
+ CompositorWidgetDelegate* AsDelegate() override { return this; }
+
+ Display* XDisplay() const { return mXDisplay; }
+ Window XWindow() const { return mXWindow; }
+
+protected:
+ nsWindow* mWidget;
+
+private:
+ LayoutDeviceIntSize mClientSize;
+
+ Display* mXDisplay;
+ Window mXWindow;
+ WindowSurfaceProvider mProvider;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_X11CompositorWidget_h
diff --git a/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h
new file mode 100644
index 000000000..068c54b54
--- /dev/null
+++ b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h
@@ -0,0 +1,31 @@
+#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_0
+
+#include_next <gdk/gdkversionmacros.h>
+
+/* GDK_AVAILABLE_IN_ALL was introduced in 3.10 */
+#ifndef GDK_AVAILABLE_IN_ALL
+#define GDK_AVAILABLE_IN_ALL
+#endif
+
+#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-gtk3/gtk/gtkenums.h b/widget/gtk/compat-gtk3/gtk/gtkenums.h
new file mode 100644
index 000000000..68e4d736d
--- /dev/null
+++ b/widget/gtk/compat-gtk3/gtk/gtkenums.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GTKENUMS_WRAPPER_H
+#define GTKENUMS_WRAPPER_H
+
+#include_next <gtk/gtkenums.h>
+
+#include <gtk/gtkversion.h>
+
+#if !GTK_CHECK_VERSION(3, 6, 0)
+enum GtkInputPurpose
+{
+ GTK_INPUT_PURPOSE_FREE_FORM,
+ GTK_INPUT_PURPOSE_ALPHA,
+ GTK_INPUT_PURPOSE_DIGITS,
+ GTK_INPUT_PURPOSE_NUMBER,
+ GTK_INPUT_PURPOSE_PHONE,
+ GTK_INPUT_PURPOSE_URL,
+ GTK_INPUT_PURPOSE_EMAIL,
+ GTK_INPUT_PURPOSE_NAME,
+ GTK_INPUT_PURPOSE_PASSWORD,
+ GTK_INPUT_PURPOSE_PIN
+};
+#endif // 3.6.0
+
+#endif /* GTKENUMS_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkdnd.h b/widget/gtk/compat/gdk/gdkdnd.h
new file mode 100644
index 000000000..e20cdea84
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkdnd.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 000000000..d310ae616
--- /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 000000000..6476b4494
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkvisual.h
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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 000000000..40efbb07d
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkwindow.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 000000000..240c12e30
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkx.h
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 000000000..1e728197e
--- /dev/null
+++ b/widget/gtk/compat/glib/gmem.h
@@ -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/. */
+
+#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 000000000..b1d4d1405
--- /dev/null
+++ b/widget/gtk/compat/gtk/gtkwidget.h
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 000000000..488061d40
--- /dev/null
+++ b/widget/gtk/compat/gtk/gtkwindow.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 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/crashtests/540078-1.xhtml b/widget/gtk/crashtests/540078-1.xhtml
new file mode 100644
index 000000000..9effb4ed6
--- /dev/null
+++ b/widget/gtk/crashtests/540078-1.xhtml
@@ -0,0 +1,2 @@
+<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 000000000..8463f67f0
--- /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 000000000..9ae47c223
--- /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/gtk2drawing.c b/widget/gtk/gtk2drawing.c
new file mode 100644
index 000000000..7fcac4de6
--- /dev/null
+++ b/widget/gtk/gtk2drawing.c
@@ -0,0 +1,3502 @@
+/* -*- 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/. */
+
+/*
+ * 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 "prinrval.h"
+
+#include <math.h>
+#include <stdbool.h> // for MOZ_ASSERT_UNREACHABLE
+
+#define XTHICKNESS(style) (style->xthickness)
+#define YTHICKNESS(style) (style->ythickness)
+#define WINDOW_IS_MAPPED(window) ((window) && GDK_IS_WINDOW(window) && gdk_window_is_visible(window))
+
+static GtkWidget* gProtoWindow;
+static GtkWidget* gProtoLayout;
+static GtkWidget* gButtonWidget;
+static GtkWidget* gToggleButtonWidget;
+static GtkWidget* gButtonArrowWidget;
+static GtkWidget* gCheckboxWidget;
+static GtkWidget* gRadiobuttonWidget;
+static GtkWidget* gHorizScrollbarWidget;
+static GtkWidget* gVertScrollbarWidget;
+static GtkWidget* gSpinWidget;
+static GtkWidget* gHScaleWidget;
+static GtkWidget* gVScaleWidget;
+static GtkWidget* gEntryWidget;
+static GtkWidget* gComboBoxWidget;
+static GtkWidget* gComboBoxButtonWidget;
+static GtkWidget* gComboBoxArrowWidget;
+static GtkWidget* gComboBoxSeparatorWidget;
+static GtkWidget* gComboBoxEntryWidget;
+static GtkWidget* gComboBoxEntryTextareaWidget;
+static GtkWidget* gComboBoxEntryButtonWidget;
+static GtkWidget* gComboBoxEntryArrowWidget;
+static GtkWidget* gHandleBoxWidget;
+static GtkWidget* gToolbarWidget;
+static GtkWidget* gFrameWidget;
+static GtkWidget* gStatusbarWidget;
+static GtkWidget* gProgressWidget;
+static GtkWidget* gTabWidget;
+static GtkWidget* gTooltipWidget;
+static GtkWidget* gMenuBarWidget;
+static GtkWidget* gMenuBarItemWidget;
+static GtkWidget* gMenuPopupWidget;
+static GtkWidget* gMenuItemWidget;
+static GtkWidget* gImageMenuItemWidget;
+static GtkWidget* gCheckMenuItemWidget;
+static GtkWidget* gTreeViewWidget;
+static GtkTreeViewColumn* gMiddleTreeViewColumn;
+static GtkWidget* gTreeHeaderCellWidget;
+static GtkWidget* gTreeHeaderSortArrowWidget;
+static GtkWidget* gExpanderWidget;
+static GtkWidget* gToolbarSeparatorWidget;
+static GtkWidget* gMenuSeparatorWidget;
+static GtkWidget* gHPanedWidget;
+static GtkWidget* gVPanedWidget;
+static GtkWidget* gScrolledWindowWidget;
+
+static style_prop_t style_prop_func;
+static gboolean have_arrow_scaling;
+static gboolean is_initialized;
+
+/* Because we have such an unconventional way of drawing widgets, signal to the GTK theme engine
+ that they are drawing for Mozilla instead of a conventional GTK app so they can do any specific
+ things they may want to do. */
+static void
+moz_gtk_set_widget_name(GtkWidget* widget)
+{
+ gtk_widget_set_name(widget, "MozillaGtkWidget");
+}
+
+gint
+moz_gtk_enable_style_props(style_prop_t styleGetProp)
+{
+ style_prop_func = styleGetProp;
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_window_widget()
+{
+ if (!gProtoWindow) {
+ gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_widget_realize(gProtoWindow);
+ moz_gtk_set_widget_name(gProtoWindow);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+setup_widget_prototype(GtkWidget* widget)
+{
+ ensure_window_widget();
+ if (!gProtoLayout) {
+ gProtoLayout = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(gProtoWindow), gProtoLayout);
+ }
+
+ gtk_container_add(GTK_CONTAINER(gProtoLayout), widget);
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_button_widget()
+{
+ if (!gButtonWidget) {
+ gButtonWidget = gtk_button_new_with_label("M");
+ setup_widget_prototype(gButtonWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_hpaned_widget()
+{
+ if (!gHPanedWidget) {
+ gHPanedWidget = gtk_hpaned_new();
+ setup_widget_prototype(gHPanedWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_vpaned_widget()
+{
+ if (!gVPanedWidget) {
+ gVPanedWidget = gtk_vpaned_new();
+ setup_widget_prototype(gVPanedWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_toggle_button_widget()
+{
+ if (!gToggleButtonWidget) {
+ gToggleButtonWidget = gtk_toggle_button_new();
+ setup_widget_prototype(gToggleButtonWidget);
+ /* toggle button must be set active to get the right style on hover. */
+ GTK_TOGGLE_BUTTON(gToggleButtonWidget)->active = TRUE;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_button_arrow_widget()
+{
+ if (!gButtonArrowWidget) {
+ ensure_toggle_button_widget();
+
+ gButtonArrowWidget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
+ gtk_container_add(GTK_CONTAINER(gToggleButtonWidget), gButtonArrowWidget);
+ gtk_widget_realize(gButtonArrowWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_checkbox_widget()
+{
+ if (!gCheckboxWidget) {
+ gCheckboxWidget = gtk_check_button_new_with_label("M");
+ setup_widget_prototype(gCheckboxWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_radiobutton_widget()
+{
+ if (!gRadiobuttonWidget) {
+ gRadiobuttonWidget = gtk_radio_button_new_with_label(NULL, "M");
+ setup_widget_prototype(gRadiobuttonWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_scrollbar_widget()
+{
+ if (!gVertScrollbarWidget) {
+ gVertScrollbarWidget = gtk_vscrollbar_new(NULL);
+ setup_widget_prototype(gVertScrollbarWidget);
+ }
+ if (!gHorizScrollbarWidget) {
+ gHorizScrollbarWidget = gtk_hscrollbar_new(NULL);
+ setup_widget_prototype(gHorizScrollbarWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_spin_widget()
+{
+ if (!gSpinWidget) {
+ gSpinWidget = gtk_spin_button_new(NULL, 1, 0);
+ setup_widget_prototype(gSpinWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_scale_widget()
+{
+ if (!gHScaleWidget) {
+ gHScaleWidget = gtk_hscale_new(NULL);
+ setup_widget_prototype(gHScaleWidget);
+ }
+ if (!gVScaleWidget) {
+ gVScaleWidget = gtk_vscale_new(NULL);
+ setup_widget_prototype(gVScaleWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_entry_widget()
+{
+ if (!gEntryWidget) {
+ gEntryWidget = gtk_entry_new();
+ setup_widget_prototype(gEntryWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+/* 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 gProtoWindow and as
+ * such GTK holds a strong reference to them. */
+static void
+moz_gtk_get_combo_box_inner_button(GtkWidget *widget, gpointer client_data)
+{
+ if (GTK_IS_TOGGLE_BUTTON(widget)) {
+ gComboBoxButtonWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxButtonWidget);
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+}
+
+static void
+moz_gtk_get_combo_box_button_inner_widgets(GtkWidget *widget,
+ gpointer client_data)
+{
+ if (GTK_IS_SEPARATOR(widget)) {
+ gComboBoxSeparatorWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxSeparatorWidget);
+ } else if (GTK_IS_ARROW(widget)) {
+ gComboBoxArrowWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxArrowWidget);
+ } else
+ return;
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+}
+
+static gint
+ensure_combo_box_widgets()
+{
+ GtkWidget* buttonChild;
+
+ if (gComboBoxButtonWidget && gComboBoxArrowWidget)
+ return MOZ_GTK_SUCCESS;
+
+ /* Create a ComboBox if needed */
+ if (!gComboBoxWidget) {
+ gComboBoxWidget = gtk_combo_box_new();
+ setup_widget_prototype(gComboBoxWidget);
+ }
+
+ /* Get its inner Button */
+ gtk_container_forall(GTK_CONTAINER(gComboBoxWidget),
+ moz_gtk_get_combo_box_inner_button,
+ NULL);
+
+ if (gComboBoxButtonWidget) {
+ /* Get the widgets inside the Button */
+ buttonChild = GTK_BIN(gComboBoxButtonWidget)->child;
+ if (GTK_IS_HBOX(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. */
+ gtk_container_forall(GTK_CONTAINER(buttonChild),
+ moz_gtk_get_combo_box_button_inner_widgets,
+ NULL);
+ } else if(GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ gComboBoxArrowWidget = buttonChild;
+ g_object_add_weak_pointer(G_OBJECT(buttonChild), (gpointer)
+ &gComboBoxArrowWidget);
+ gtk_widget_realize(gComboBoxArrowWidget);
+ g_object_set_data(G_OBJECT(gComboBoxArrowWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ } else {
+ /* Shouldn't be reached with current internal gtk implementation; we
+ * use a generic toggle button as last resort fallback to avoid
+ * crashing. */
+ ensure_toggle_button_widget();
+ gComboBoxButtonWidget = gToggleButtonWidget;
+ }
+
+ if (!gComboBoxArrowWidget) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ ensure_button_arrow_widget();
+ gComboBoxArrowWidget = gButtonArrowWidget;
+ }
+
+ /* We don't test the validity of gComboBoxSeparatorWidget since there
+ * is none when "appears-as-list" = TRUE or "cell-view" = FALSE; if it
+ * is invalid we just won't paint it. */
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* We need to have pointers to the inner widgets (entry, button, arrow) of
+ * the ComboBoxEntry 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 gProtoWindow and as
+ * such GTK holds a strong reference to them. */
+static void
+moz_gtk_get_combo_box_entry_inner_widgets(GtkWidget *widget,
+ gpointer client_data)
+{
+ if (GTK_IS_TOGGLE_BUTTON(widget)) {
+ gComboBoxEntryButtonWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxEntryButtonWidget);
+ } else if (GTK_IS_ENTRY(widget)) {
+ gComboBoxEntryTextareaWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxEntryTextareaWidget);
+ } else
+ return;
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+}
+
+static void
+moz_gtk_get_combo_box_entry_arrow(GtkWidget *widget, gpointer client_data)
+{
+ if (GTK_IS_ARROW(widget)) {
+ gComboBoxEntryArrowWidget = widget;
+ g_object_add_weak_pointer(G_OBJECT(widget),
+ (gpointer) &gComboBoxEntryArrowWidget);
+ gtk_widget_realize(widget);
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+}
+
+static gint
+ensure_combo_box_entry_widgets()
+{
+ GtkWidget* buttonChild;
+
+ if (gComboBoxEntryTextareaWidget &&
+ gComboBoxEntryButtonWidget &&
+ gComboBoxEntryArrowWidget)
+ return MOZ_GTK_SUCCESS;
+
+ /* Create a ComboBoxEntry if needed */
+ if (!gComboBoxEntryWidget) {
+ gComboBoxEntryWidget = gtk_combo_box_entry_new();
+ setup_widget_prototype(gComboBoxEntryWidget);
+ }
+
+ /* Get its inner Entry and Button */
+ gtk_container_forall(GTK_CONTAINER(gComboBoxEntryWidget),
+ moz_gtk_get_combo_box_entry_inner_widgets,
+ NULL);
+
+ if (!gComboBoxEntryTextareaWidget) {
+ ensure_entry_widget();
+ gComboBoxEntryTextareaWidget = gEntryWidget;
+ }
+
+ if (gComboBoxEntryButtonWidget) {
+ /* Get the Arrow inside the Button */
+ buttonChild = GTK_BIN(gComboBoxEntryButtonWidget)->child;
+ if (GTK_IS_HBOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because ComboBoxEntry
+ * inherits from ComboBox which needs to place a cell renderer,
+ * a separator, and an arrow in the button when appears-as-list
+ * is FALSE. Here the hbox should only contain an arrow, since
+ * a ComboBoxEntry doesn't need all those widgets in the
+ * button. */
+ gtk_container_forall(GTK_CONTAINER(buttonChild),
+ moz_gtk_get_combo_box_entry_arrow,
+ NULL);
+ } else if(GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ gComboBoxEntryArrowWidget = buttonChild;
+ g_object_add_weak_pointer(G_OBJECT(buttonChild), (gpointer)
+ &gComboBoxEntryArrowWidget);
+ gtk_widget_realize(gComboBoxEntryArrowWidget);
+ g_object_set_data(G_OBJECT(gComboBoxEntryArrowWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ } else {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we use a generic toggle button as last resort fallback to avoid
+ * crashing. */
+ ensure_toggle_button_widget();
+ gComboBoxEntryButtonWidget = gToggleButtonWidget;
+ }
+
+ if (!gComboBoxEntryArrowWidget) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ ensure_button_arrow_widget();
+ gComboBoxEntryArrowWidget = gButtonArrowWidget;
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+
+static gint
+ensure_handlebox_widget()
+{
+ if (!gHandleBoxWidget) {
+ gHandleBoxWidget = gtk_handle_box_new();
+ setup_widget_prototype(gHandleBoxWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_toolbar_widget()
+{
+ if (!gToolbarWidget) {
+ ensure_handlebox_widget();
+ gToolbarWidget = gtk_toolbar_new();
+ gtk_container_add(GTK_CONTAINER(gHandleBoxWidget), gToolbarWidget);
+ gtk_widget_realize(gToolbarWidget);
+ g_object_set_data(G_OBJECT(gToolbarWidget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_toolbar_separator_widget()
+{
+ if (!gToolbarSeparatorWidget) {
+ ensure_toolbar_widget();
+ gToolbarSeparatorWidget = GTK_WIDGET(gtk_separator_tool_item_new());
+ setup_widget_prototype(gToolbarSeparatorWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_tooltip_widget()
+{
+ if (!gTooltipWidget) {
+ gTooltipWidget = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_widget_realize(gTooltipWidget);
+ moz_gtk_set_widget_name(gTooltipWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_tab_widget()
+{
+ if (!gTabWidget) {
+ gTabWidget = gtk_notebook_new();
+ setup_widget_prototype(gTabWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_progress_widget()
+{
+ if (!gProgressWidget) {
+ gProgressWidget = gtk_progress_bar_new();
+ setup_widget_prototype(gProgressWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_statusbar_widget()
+{
+ if (!gStatusbarWidget) {
+ gStatusbarWidget = gtk_statusbar_new();
+ setup_widget_prototype(gStatusbarWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_frame_widget()
+{
+ if (!gFrameWidget) {
+ ensure_statusbar_widget();
+ gFrameWidget = gtk_frame_new(NULL);
+ gtk_container_add(GTK_CONTAINER(gStatusbarWidget), gFrameWidget);
+ gtk_widget_realize(gFrameWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_bar_widget()
+{
+ if (!gMenuBarWidget) {
+ gMenuBarWidget = gtk_menu_bar_new();
+ setup_widget_prototype(gMenuBarWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_bar_item_widget()
+{
+ if (!gMenuBarItemWidget) {
+ ensure_menu_bar_widget();
+ gMenuBarItemWidget = gtk_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuBarWidget),
+ gMenuBarItemWidget);
+ gtk_widget_realize(gMenuBarItemWidget);
+ g_object_set_data(G_OBJECT(gMenuBarItemWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_popup_widget()
+{
+ if (!gMenuPopupWidget) {
+ ensure_menu_bar_item_widget();
+ gMenuPopupWidget = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(gMenuBarItemWidget),
+ gMenuPopupWidget);
+ gtk_widget_realize(gMenuPopupWidget);
+ g_object_set_data(G_OBJECT(gMenuPopupWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_item_widget()
+{
+ if (!gMenuItemWidget) {
+ ensure_menu_popup_widget();
+ gMenuItemWidget = gtk_menu_item_new_with_label("M");
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget),
+ gMenuItemWidget);
+ gtk_widget_realize(gMenuItemWidget);
+ g_object_set_data(G_OBJECT(gMenuItemWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_image_menu_item_widget()
+{
+ if (!gImageMenuItemWidget) {
+ ensure_menu_popup_widget();
+ gImageMenuItemWidget = gtk_image_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget),
+ gImageMenuItemWidget);
+ gtk_widget_realize(gImageMenuItemWidget);
+ g_object_set_data(G_OBJECT(gImageMenuItemWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_menu_separator_widget()
+{
+ if (!gMenuSeparatorWidget) {
+ ensure_menu_popup_widget();
+ gMenuSeparatorWidget = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget),
+ gMenuSeparatorWidget);
+ gtk_widget_realize(gMenuSeparatorWidget);
+ g_object_set_data(G_OBJECT(gMenuSeparatorWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_check_menu_item_widget()
+{
+ if (!gCheckMenuItemWidget) {
+ ensure_menu_popup_widget();
+ gCheckMenuItemWidget = gtk_check_menu_item_new_with_label("M");
+ gtk_menu_shell_append(GTK_MENU_SHELL(gMenuPopupWidget),
+ gCheckMenuItemWidget);
+ gtk_widget_realize(gCheckMenuItemWidget);
+ g_object_set_data(G_OBJECT(gCheckMenuItemWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_tree_view_widget()
+{
+ if (!gTreeViewWidget) {
+ gTreeViewWidget = gtk_tree_view_new();
+ setup_widget_prototype(gTreeViewWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_tree_header_cell_widget()
+{
+ if(!gTreeHeaderCellWidget) {
+ /*
+ * 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* lastTreeViewColumn;
+
+ ensure_tree_view_widget();
+
+ /* 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(gTreeViewWidget), firstTreeViewColumn);
+
+ gMiddleTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(gMiddleTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget),
+ gMiddleTreeViewColumn);
+
+ lastTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(gTreeViewWidget), lastTreeViewColumn);
+
+ /* Use the middle column's header for our button */
+ gTreeHeaderCellWidget = gMiddleTreeViewColumn->button;
+ gTreeHeaderSortArrowWidget = gMiddleTreeViewColumn->arrow;
+ g_object_set_data(G_OBJECT(gTreeHeaderCellWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ g_object_set_data(G_OBJECT(gTreeHeaderSortArrowWidget),
+ "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_expander_widget()
+{
+ if (!gExpanderWidget) {
+ gExpanderWidget = gtk_expander_new("M");
+ setup_widget_prototype(gExpanderWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+ensure_scrolled_window_widget()
+{
+ if (!gScrolledWindowWidget) {
+ gScrolledWindowWidget = gtk_scrolled_window_new(NULL, NULL);
+ setup_widget_prototype(gScrolledWindowWidget);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static GtkStateType
+ConvertGtkState(GtkWidgetState* state)
+{
+ if (state->disabled)
+ return GTK_STATE_INSENSITIVE;
+ else if (state->depressed)
+ return (state->inHover ? GTK_STATE_PRELIGHT : GTK_STATE_ACTIVE);
+ else if (state->inHover)
+ return (state->active ? GTK_STATE_ACTIVE : GTK_STATE_PRELIGHT);
+ else
+ return GTK_STATE_NORMAL;
+}
+
+static gint
+TSOffsetStyleGCArray(GdkGC** gcs, gint xorigin, gint yorigin)
+{
+ int i;
+ /* there are 5 gc's in each array, for each of the widget states */
+ for (i = 0; i < 5; ++i)
+ gdk_gc_set_ts_origin(gcs[i], xorigin, yorigin);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+TSOffsetStyleGCs(GtkStyle* style, gint xorigin, gint yorigin)
+{
+ TSOffsetStyleGCArray(style->fg_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->bg_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->light_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->dark_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->mid_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->text_gc, xorigin, yorigin);
+ TSOffsetStyleGCArray(style->base_gc, xorigin, yorigin);
+ gdk_gc_set_ts_origin(style->black_gc, xorigin, yorigin);
+ gdk_gc_set_ts_origin(style->white_gc, xorigin, yorigin);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_init()
+{
+ GtkWidgetClass *entry_class;
+
+ if (is_initialized)
+ return MOZ_GTK_SUCCESS;
+
+ is_initialized = TRUE;
+ have_arrow_scaling = (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 12));
+
+ /* Add style property to GtkEntry.
+ * Adding the style property to the normal GtkEntry class means that it
+ * will work without issues inside GtkComboBox and for Spinbuttons. */
+ entry_class = g_type_class_ref(GTK_TYPE_ENTRY);
+ gtk_widget_class_install_style_property(entry_class,
+ g_param_spec_boolean("honors-transparent-bg-hint",
+ "Transparent BG enabling flag",
+ "If TRUE, the theme is able to draw the GtkEntry on non-prefilled background.",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ return MOZ_GTK_SUCCESS;
+}
+
+GdkColormap*
+moz_gtk_widget_get_colormap()
+{
+ /* Child widgets inherit the colormap from the GtkWindow. */
+ ensure_window_widget();
+ return gtk_widget_get_colormap(gProtoWindow);
+}
+
+gint
+moz_gtk_checkbox_get_metrics(gint* indicator_size, gint* indicator_spacing)
+{
+ ensure_checkbox_widget();
+
+ gtk_widget_style_get (gCheckboxWidget,
+ "indicator_size", indicator_size,
+ "indicator_spacing", indicator_spacing,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing)
+{
+ ensure_radiobutton_widget();
+
+ gtk_widget_style_get (gRadiobuttonWidget,
+ "indicator_size", indicator_size,
+ "indicator_spacing", indicator_spacing,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width)
+{
+ gboolean interior_focus;
+ gint focus_width = 0;
+
+ ensure_entry_widget();
+ gtk_widget_style_get(gEntryWidget,
+ "interior-focus", &interior_focus,
+ "focus-line-width", &focus_width,
+ NULL);
+ if (interior_focus) {
+ *focus_h_width = XTHICKNESS(gEntryWidget->style) + focus_width;
+ *focus_v_width = YTHICKNESS(gEntryWidget->style) + focus_width;
+ } else {
+ *focus_h_width = focus_width;
+ *focus_v_width = focus_width;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_widget_get_focus(GtkWidget* widget, gboolean* interior_focus,
+ gint* focus_width, gint* focus_pad)
+{
+ gtk_widget_style_get (widget,
+ "interior-focus", interior_focus,
+ "focus-line-width", focus_width,
+ "focus-padding", focus_pad,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding)
+{
+ ensure_menu_item_widget();
+
+ gtk_widget_style_get (gMenuItemWidget,
+ "horizontal-padding", horizontal_padding,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding)
+{
+ ensure_check_menu_item_widget();
+
+ gtk_widget_style_get (gCheckMenuItemWidget,
+ "horizontal-padding", horizontal_padding,
+ NULL);
+
+ 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;
+
+ ensure_button_widget();
+ gtk_widget_style_get(gButtonWidget,
+ "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;
+
+ ensure_button_widget();
+ gtk_widget_style_get(gButtonWidget,
+ "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)
+{
+ if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+ ensure_hpaned_widget();
+ gtk_widget_style_get(gHPanedWidget, "handle_size", size, NULL);
+ } else {
+ ensure_vpaned_widget();
+ gtk_widget_style_get(gVPanedWidget, "handle_size", size, NULL);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_button_get_inner_border(GtkWidget* widget, GtkBorder* inner_border)
+{
+ static const GtkBorder default_inner_border = { 1, 1, 1, 1 };
+ GtkBorder *tmp_border;
+
+ gtk_widget_style_get (widget, "inner-border", &tmp_border, NULL);
+
+ if (tmp_border) {
+ *inner_border = *tmp_border;
+ gtk_border_free(tmp_border);
+ }
+ else
+ *inner_border = default_inner_border;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_button_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkReliefStyle relief, GtkWidget* widget,
+ GtkTextDirection direction)
+{
+ GtkShadowType shadow_type;
+ GtkStyle* style = widget->style;
+ GtkStateType button_state = ConvertGtkState(state);
+ gint x = rect->x, y=rect->y, width=rect->width, height=rect->height;
+
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ moz_gtk_widget_get_focus(widget, &interior_focus, &focus_width, &focus_pad);
+
+ if (WINDOW_IS_MAPPED(drawable)) {
+ gdk_window_set_back_pixmap(drawable, NULL, TRUE);
+ gdk_window_clear_area(drawable, cliprect->x, cliprect->y,
+ cliprect->width, cliprect->height);
+ }
+
+ gtk_widget_set_state(widget, button_state);
+ gtk_widget_set_direction(widget, direction);
+
+ if (state->isDefault)
+ GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_DEFAULT);
+
+ GTK_BUTTON(widget)->relief = relief;
+
+ /* Some theme engines love to cause us pain in that gtk_paint_focus is a
+ no-op on buttons and button-like widgets. They only listen to this flag. */
+ if (state->focused && !state->disabled)
+ GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
+
+ if (!interior_focus && state->focused) {
+ x += focus_width + focus_pad;
+ y += focus_width + focus_pad;
+ width -= 2 * (focus_width + focus_pad);
+ height -= 2 * (focus_width + focus_pad);
+ }
+
+ shadow_type = button_state == GTK_STATE_ACTIVE ||
+ state->depressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+
+ 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_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN, cliprect,
+ widget, "buttondefault", 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);
+ }
+
+ if (relief != GTK_RELIEF_NONE || state->depressed ||
+ (button_state != GTK_STATE_NORMAL &&
+ button_state != GTK_STATE_INSENSITIVE)) {
+ TSOffsetStyleGCs(style, x, y);
+ /* 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_paint_box(style, drawable, button_state, shadow_type, cliprect,
+ widget, "button", x, y, width, height);
+ }
+
+ if (state->focused) {
+ if (interior_focus) {
+ x += widget->style->xthickness + focus_pad;
+ y += widget->style->ythickness + focus_pad;
+ width -= 2 * (widget->style->xthickness + focus_pad);
+ height -= 2 * (widget->style->ythickness + focus_pad);
+ } else {
+ x -= focus_width + focus_pad;
+ y -= focus_width + focus_pad;
+ width += 2 * (focus_width + focus_pad);
+ height += 2 * (focus_width + focus_pad);
+ }
+
+ TSOffsetStyleGCs(style, x, y);
+ gtk_paint_focus(style, drawable, button_state, cliprect,
+ widget, "button", x, y, width, height);
+ }
+
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_DEFAULT);
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toggle_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean selected, gboolean inconsistent,
+ gboolean isradio, GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = (selected)?GTK_SHADOW_IN:GTK_SHADOW_OUT;
+ gint indicator_size, indicator_spacing;
+ gint x, y, width, height;
+ gint focus_x, focus_y, focus_width, focus_height;
+ GtkWidget *w;
+ GtkStyle *style;
+
+ if (isradio) {
+ moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
+ w = gRadiobuttonWidget;
+ } else {
+ moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
+ w = gCheckboxWidget;
+ }
+
+ // XXX we should assert rect->height >= indicator_size too
+ // after bug 369581 is fixed.
+ MOZ_ASSERT(rect->width >= indicator_size,
+ "GetMinimumWidgetSize was ignored");
+
+ // Paint it center aligned in the rect.
+ x = rect->x + (rect->width - indicator_size) / 2;
+ y = rect->y + (rect->height - indicator_size) / 2;
+ width = indicator_size;
+ height = indicator_size;
+
+ focus_x = x - indicator_spacing;
+ focus_y = y - indicator_spacing;
+ focus_width = width + 2 * indicator_spacing;
+ focus_height = height + 2 * indicator_spacing;
+
+ style = w->style;
+ TSOffsetStyleGCs(style, x, y);
+
+ gtk_widget_set_sensitive(w, !state->disabled);
+ gtk_widget_set_direction(w, direction);
+ GTK_TOGGLE_BUTTON(w)->active = selected;
+
+ if (isradio) {
+ gtk_paint_option(style, drawable, state_type, shadow_type, cliprect,
+ gRadiobuttonWidget, "radiobutton", x, y,
+ width, height);
+ if (state->focused) {
+ gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect,
+ gRadiobuttonWidget, "radiobutton", focus_x, focus_y,
+ focus_width, focus_height);
+ }
+ }
+ else {
+ /*
+ * 'indeterminate' type on checkboxes. In GTK, the shadow type
+ * must also be changed for the state to be drawn.
+ */
+ if (inconsistent) {
+ gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(gCheckboxWidget), TRUE);
+ shadow_type = GTK_SHADOW_ETCHED_IN;
+ } else {
+ gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(gCheckboxWidget), FALSE);
+ }
+
+ gtk_paint_check(style, drawable, state_type, shadow_type, cliprect,
+ gCheckboxWidget, "checkbutton", x, y, width, height);
+ if (state->focused) {
+ gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect,
+ gCheckboxWidget, "checkbutton", focus_x, focus_y,
+ focus_width, focus_height);
+ }
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+calculate_button_inner_rect(GtkWidget* button, GdkRectangle* rect,
+ GdkRectangle* inner_rect,
+ GtkTextDirection direction,
+ gboolean ignore_focus)
+{
+ GtkBorder inner_border;
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+ GtkStyle* style;
+
+ style = button->style;
+
+ /* This mirrors gtkbutton's child positioning */
+ moz_gtk_button_get_inner_border(button, &inner_border);
+ moz_gtk_widget_get_focus(button, &interior_focus,
+ &focus_width, &focus_pad);
+
+ if (ignore_focus)
+ focus_width = focus_pad = 0;
+
+ inner_rect->x = rect->x + XTHICKNESS(style) + focus_width + focus_pad;
+ inner_rect->x += direction == GTK_TEXT_DIR_LTR ?
+ inner_border.left : inner_border.right;
+ inner_rect->y = rect->y + inner_border.top + YTHICKNESS(style) +
+ focus_width + focus_pad;
+ inner_rect->width = MAX(1, rect->width - inner_border.left -
+ inner_border.right - (XTHICKNESS(style) + focus_pad + focus_width) * 2);
+ inner_rect->height = MAX(1, rect->height - inner_border.top -
+ inner_border.bottom - (YTHICKNESS(style) + focus_pad + focus_width) * 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;
+ GtkMisc* misc = GTK_MISC(arrow);
+
+ if (have_arrow_scaling)
+ gtk_widget_style_get(arrow, "arrow_scaling", &arrow_scaling, NULL);
+
+ extent = MIN((rect->width - misc->xpad * 2),
+ (rect->height - misc->ypad * 2)) * arrow_scaling;
+
+ xalign = direction == GTK_TEXT_DIR_LTR ? misc->xalign : 1.0 - misc->xalign;
+ xpad = misc->xpad + (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 + misc->ypad +
+ ((rect->height - extent) * misc->yalign));
+
+ arrow_rect->width = arrow_rect->height = extent;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scrollbar_button_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkScrollbarButtonFlags flags,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = (state->active) ?
+ GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GdkRectangle arrow_rect;
+ GtkStyle* style;
+ GtkWidget *scrollbar;
+ GtkArrowType arrow_type;
+ gint arrow_displacement_x, arrow_displacement_y;
+ const char* detail = (flags & MOZ_GTK_STEPPER_VERTICAL) ?
+ "vscrollbar" : "hscrollbar";
+
+ ensure_scrollbar_widget();
+
+ if (flags & MOZ_GTK_STEPPER_VERTICAL)
+ scrollbar = gVertScrollbarWidget;
+ else
+ scrollbar = gHorizScrollbarWidget;
+
+ gtk_widget_set_direction(scrollbar, direction);
+
+ /* Some theme engines (i.e., ClearLooks) check the scrollbar's allocation
+ to determine where it should paint rounded corners on the buttons.
+ We need to trick them into drawing the buttons the way we want them. */
+
+ scrollbar->allocation.x = rect->x;
+ scrollbar->allocation.y = rect->y;
+ scrollbar->allocation.width = rect->width;
+ scrollbar->allocation.height = rect->height;
+
+ if (flags & MOZ_GTK_STEPPER_VERTICAL) {
+ scrollbar->allocation.height *= 5;
+ if (flags & MOZ_GTK_STEPPER_DOWN) {
+ arrow_type = GTK_ARROW_DOWN;
+ if (flags & MOZ_GTK_STEPPER_BOTTOM)
+ scrollbar->allocation.y -= 4 * rect->height;
+ else
+ scrollbar->allocation.y -= rect->height;
+
+ } else {
+ arrow_type = GTK_ARROW_UP;
+ if (flags & MOZ_GTK_STEPPER_BOTTOM)
+ scrollbar->allocation.y -= 3 * rect->height;
+ }
+ } else {
+ scrollbar->allocation.width *= 5;
+ if (flags & MOZ_GTK_STEPPER_DOWN) {
+ arrow_type = GTK_ARROW_RIGHT;
+ if (flags & MOZ_GTK_STEPPER_BOTTOM)
+ scrollbar->allocation.x -= 4 * rect->width;
+ else
+ scrollbar->allocation.x -= rect->width;
+ } else {
+ arrow_type = GTK_ARROW_LEFT;
+ if (flags & MOZ_GTK_STEPPER_BOTTOM)
+ scrollbar->allocation.x -= 3 * rect->width;
+ }
+ }
+
+ style = scrollbar->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_box(style, drawable, state_type, shadow_type, cliprect,
+ scrollbar, detail, rect->x, rect->y,
+ rect->width, rect->height);
+
+ arrow_rect.width = rect->width / 2;
+ arrow_rect.height = rect->height / 2;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+
+ if (state_type == GTK_STATE_ACTIVE) {
+ gtk_widget_style_get(scrollbar,
+ "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_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ scrollbar, detail, arrow_type, TRUE, arrow_rect.x,
+ arrow_rect.y, arrow_rect.width, arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scrollbar_trough_paint(WidgetNodeType widget,
+ GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkScrollbar *scrollbar;
+
+ ensure_scrollbar_widget();
+
+ if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL)
+ scrollbar = GTK_SCROLLBAR(gHorizScrollbarWidget);
+ else
+ scrollbar = GTK_SCROLLBAR(gVertScrollbarWidget);
+
+ gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction);
+
+ style = GTK_WIDGET(scrollbar)->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_style_apply_default_background(style, drawable, TRUE, GTK_STATE_ACTIVE,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+
+ gtk_paint_box(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_IN, cliprect,
+ GTK_WIDGET(scrollbar), "trough", rect->x, rect->y,
+ rect->width, rect->height);
+
+ if (state->focused) {
+ gtk_paint_focus(style, drawable, GTK_STATE_ACTIVE, cliprect,
+ GTK_WIDGET(scrollbar), "trough",
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scrollbar_thumb_paint(WidgetNodeType widget,
+ GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = (state->inHover || state->active) ?
+ GTK_STATE_PRELIGHT : GTK_STATE_NORMAL;
+ GtkShadowType shadow_type = GTK_SHADOW_OUT;
+ GtkStyle* style;
+ GtkScrollbar *scrollbar;
+ GtkAdjustment *adj;
+ gboolean activate_slider;
+
+ ensure_scrollbar_widget();
+
+ if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL)
+ scrollbar = GTK_SCROLLBAR(gHorizScrollbarWidget);
+ else
+ scrollbar = GTK_SCROLLBAR(gVertScrollbarWidget);
+
+ gtk_widget_set_direction(GTK_WIDGET(scrollbar), direction);
+
+ /* Make sure to set the scrollbar range before painting so that
+ everything is drawn properly. At least the bluecurve (and
+ maybe other) themes don't draw the top or bottom black line
+ surrounding the scrollbar if the theme thinks that it's butted
+ up against the scrollbar arrows. Note the increases of the
+ clip rect below. */
+ adj = gtk_range_get_adjustment(GTK_RANGE(scrollbar));
+
+ if (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) {
+ adj->page_size = rect->width;
+ }
+ else {
+ adj->page_size = rect->height;
+ }
+
+ adj->lower = 0;
+ adj->value = state->curpos;
+ adj->upper = state->maxpos;
+ gtk_adjustment_changed(adj);
+
+ style = GTK_WIDGET(scrollbar)->style;
+
+ gtk_widget_style_get(GTK_WIDGET(scrollbar), "activate-slider",
+ &activate_slider, NULL);
+
+ if (activate_slider && state->active) {
+ shadow_type = GTK_SHADOW_IN;
+ state_type = GTK_STATE_ACTIVE;
+ }
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_slider(style, drawable, state_type, shadow_type, cliprect,
+ GTK_WIDGET(scrollbar), "slider", rect->x, rect->y,
+ rect->width, rect->height,
+ (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) ?
+ GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_spin_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+
+ ensure_spin_widget();
+ gtk_widget_set_direction(gSpinWidget, direction);
+ style = gSpinWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN, NULL,
+ gSpinWidget, "spinbutton",
+ rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_spin_updown_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ gboolean isDown, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GdkRectangle arrow_rect;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state_type == GTK_STATE_ACTIVE ?
+ GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GtkStyle* style;
+
+ ensure_spin_widget();
+ style = gSpinWidget->style;
+ gtk_widget_set_direction(gSpinWidget, direction);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_box(style, drawable, state_type, shadow_type, NULL, gSpinWidget,
+ isDown ? "spinbutton_down" : "spinbutton_up",
+ rect->x, rect->y, rect->width, rect->height);
+
+ /* hard code these values */
+ 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_paint_arrow(style, drawable, state_type, shadow_type, NULL,
+ gSpinWidget, "spinbutton",
+ isDown ? GTK_ARROW_DOWN : GTK_ARROW_UP, TRUE,
+ arrow_rect.x, arrow_rect.y,
+ arrow_rect.width, arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scale_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkOrientation flags, GtkTextDirection direction)
+{
+ gint x = 0, y = 0;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkStyle* style;
+ GtkWidget* widget;
+
+ ensure_scale_widget();
+ widget = ((flags == GTK_ORIENTATION_HORIZONTAL) ? gHScaleWidget : gVScaleWidget);
+ gtk_widget_set_direction(widget, direction);
+
+ style = widget->style;
+
+ if (flags == GTK_ORIENTATION_HORIZONTAL) {
+ x = XTHICKNESS(style);
+ y++;
+ }
+ else {
+ x++;
+ y = YTHICKNESS(style);
+ }
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_box(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_IN, cliprect,
+ widget, "trough", rect->x + x, rect->y + y,
+ rect->width - 2*x, rect->height - 2*y);
+
+ if (state->focused)
+ gtk_paint_focus(style, drawable, state_type, cliprect, widget, "trough",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scale_thumb_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkOrientation flags, GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkStyle* style;
+ GtkWidget* widget;
+ gint thumb_width, thumb_height, x, y;
+
+ ensure_scale_widget();
+ widget = ((flags == GTK_ORIENTATION_HORIZONTAL) ? gHScaleWidget : gVScaleWidget);
+ gtk_widget_set_direction(widget, direction);
+
+ style = widget->style;
+
+ /* 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;
+ }
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_slider(style, drawable, state_type, GTK_SHADOW_OUT, cliprect,
+ widget, (flags == GTK_ORIENTATION_HORIZONTAL) ? "hscale" : "vscale",
+ x, y, thumb_width, thumb_height, flags);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_gripper_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type;
+ GtkStyle* style;
+
+ ensure_handlebox_widget();
+ gtk_widget_set_direction(gHandleBoxWidget, direction);
+
+ style = gHandleBoxWidget->style;
+ shadow_type = GTK_HANDLE_BOX(gHandleBoxWidget)->shadow_type;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_box(style, drawable, state_type, shadow_type, cliprect,
+ gHandleBoxWidget, "handlebox_bin", rect->x, rect->y,
+ rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_hpaned_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state)
+{
+ GtkStateType hpaned_state = ConvertGtkState(state);
+
+ ensure_hpaned_widget();
+ gtk_paint_handle(gHPanedWidget->style, drawable, hpaned_state,
+ GTK_SHADOW_NONE, cliprect, gHPanedWidget, "paned",
+ rect->x, rect->y, rect->width, rect->height,
+ GTK_ORIENTATION_VERTICAL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_vpaned_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state)
+{
+ GtkStateType vpaned_state = ConvertGtkState(state);
+
+ ensure_vpaned_widget();
+ gtk_paint_handle(gVPanedWidget->style, drawable, vpaned_state,
+ GTK_SHADOW_NONE, cliprect, gVPanedWidget, "paned",
+ rect->x, rect->y, rect->width, rect->height,
+ GTK_ORIENTATION_HORIZONTAL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_entry_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkWidget* widget, GtkTextDirection direction)
+{
+ GtkStateType bg_state = state->disabled ?
+ GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
+ gint x, y, width = rect->width, height = rect->height;
+ GtkStyle* style;
+ gboolean interior_focus;
+ gboolean theme_honors_transparency = FALSE;
+ gint focus_width;
+ int draw_focus_outline_only = state->depressed; // NS_THEME_FOCUS_OUTLINE
+
+ gtk_widget_set_direction(widget, direction);
+
+ style = widget->style;
+
+ gtk_widget_style_get(widget,
+ "interior-focus", &interior_focus,
+ "focus-line-width", &focus_width,
+ "honors-transparent-bg-hint", &theme_honors_transparency,
+ NULL);
+
+ if (draw_focus_outline_only) {
+ // Inflate the given 'rect' with the focus outline size.
+ gint h, v;
+ moz_gtk_get_focus_outline_size(&h, &v);
+ rect->x -= h;
+ rect->width += 2 * h;
+ rect->y -= v;
+ rect->height += 2 * v;
+ width = rect->width;
+ height = rect->height;
+ }
+
+ /* gtkentry.c uses two windows, one for the entire widget and one for the
+ * text area inside it. The background of both windows is set to the "base"
+ * color of the new state in gtk_entry_state_changed, but only the inner
+ * textarea window uses gtk_paint_flat_box when exposed */
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ /* This gets us a lovely greyish disabledish look */
+ gtk_widget_set_sensitive(widget, !state->disabled);
+
+ /* GTK fills the outer widget window with the base color before drawing the widget.
+ * Some older themes rely on this behavior, but many themes nowadays use rounded
+ * corners on their widgets. While most GTK apps are blissfully unaware of this
+ * problem due to their use of the default window background, we render widgets on
+ * many kinds of backgrounds on the web.
+ * If the theme is able to cope with transparency, then we can skip pre-filling
+ * and notify the theme it will paint directly on the canvas. */
+ if (theme_honors_transparency) {
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
+ } else {
+ GdkRectangle clipped_rect;
+ gdk_rectangle_intersect(rect, cliprect, &clipped_rect);
+ if (clipped_rect.width != 0) {
+ gdk_draw_rectangle(drawable, style->base_gc[bg_state], TRUE,
+ clipped_rect.x, clipped_rect.y,
+ clipped_rect.width, clipped_rect.height);
+ }
+ g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(FALSE));
+ }
+
+ if (!draw_focus_outline_only) {
+ /* Get the position of the inner window, see _gtk_entry_get_borders */
+ x = XTHICKNESS(style);
+ y = YTHICKNESS(style);
+
+ if (!interior_focus) {
+ x += focus_width;
+ y += focus_width;
+ }
+
+ /* Simulate an expose of the inner window */
+ gtk_paint_flat_box(style, drawable, bg_state, GTK_SHADOW_NONE,
+ cliprect, widget, "entry_bg", rect->x + x,
+ rect->y + y, rect->width - 2*x, rect->height - 2*y);
+ }
+
+ /* Now paint the shadow and focus border.
+ * We do like in gtk_entry_draw_frame, we first draw the shadow, a tad
+ * smaller when focused if the focus is not interior, then the focus. */
+ x = rect->x;
+ y = rect->y;
+
+ if (state->focused && !state->disabled) {
+ /* This will get us the lit borders that focused textboxes enjoy on
+ * some themes. */
+ GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
+
+ if (!interior_focus) {
+ /* Indent the border a little bit if we have exterior focus
+ (this is what GTK does to draw native entries) */
+ x += focus_width;
+ y += focus_width;
+ width -= 2 * focus_width;
+ height -= 2 * focus_width;
+ }
+ }
+
+ if (!draw_focus_outline_only || interior_focus) {
+ gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
+ cliprect, widget, "entry", x, y, width, height);
+ }
+
+ if (state->focused && !state->disabled) {
+ if (!interior_focus) {
+ gtk_paint_focus(style, drawable, GTK_STATE_NORMAL, cliprect,
+ widget, "entry",
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ /* Now unset the focus flag. We don't want other entries to look
+ * like they're focused too! */
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_treeview_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ gint xthickness, ythickness;
+
+ GtkStyle *style;
+ GtkStateType state_type;
+
+ ensure_tree_view_widget();
+ ensure_scrolled_window_widget();
+
+ gtk_widget_set_direction(gTreeViewWidget, direction);
+ gtk_widget_set_direction(gScrolledWindowWidget, direction);
+
+ /* only handle disabled and normal states, otherwise the whole background
+ * area will be painted differently with other states */
+ state_type = state->disabled ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
+
+ /* In GTK the treeview sets the background of the window
+ * which contains the cells to the treeview base color.
+ * If we don't set it here the background color will not be correct.*/
+ gtk_widget_modify_bg(gTreeViewWidget, state_type,
+ &gTreeViewWidget->style->base[state_type]);
+
+ style = gScrolledWindowWidget->style;
+ xthickness = XTHICKNESS(style);
+ ythickness = YTHICKNESS(style);
+
+ TSOffsetStyleGCs(gTreeViewWidget->style, rect->x, rect->y);
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_flat_box(gTreeViewWidget->style, drawable, state_type,
+ GTK_SHADOW_NONE, cliprect, gTreeViewWidget, "treeview",
+ rect->x + xthickness, rect->y + ythickness,
+ rect->width - 2 * xthickness,
+ rect->height - 2 * ythickness);
+
+ gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
+ cliprect, gScrolledWindowWidget, "scrolled_window",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tree_header_cell_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean isSorted, GtkTextDirection direction)
+{
+ gtk_tree_view_column_set_sort_indicator(gMiddleTreeViewColumn,
+ isSorted);
+
+ moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL,
+ gTreeHeaderCellWidget, direction);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tree_header_sort_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect,
+ GtkWidgetState* state, GtkArrowType flags,
+ GtkTextDirection direction)
+{
+ GdkRectangle arrow_rect;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = GTK_SHADOW_IN;
+ GtkArrowType arrow_type = flags;
+ GtkStyle* style;
+
+ ensure_tree_header_cell_widget();
+ gtk_widget_set_direction(gTreeHeaderSortArrowWidget, direction);
+
+ /* 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 = gTreeHeaderSortArrowWidget->style;
+ TSOffsetStyleGCs(style, arrow_rect.x, arrow_rect.y);
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ gTreeHeaderSortArrowWidget, "arrow", arrow_type, TRUE,
+ arrow_rect.x, arrow_rect.y,
+ arrow_rect.width, arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_treeview_expander_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkExpanderStyle expander_state,
+ GtkTextDirection direction)
+{
+ GtkStyle *style;
+ GtkStateType state_type;
+
+ ensure_tree_view_widget();
+ gtk_widget_set_direction(gTreeViewWidget, direction);
+
+ style = gTreeViewWidget->style;
+
+ /* 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. */
+ state_type = state->disabled ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_expander(style, drawable, state_type, cliprect, gTreeViewWidget, "treeview",
+ rect->x + rect->width / 2, rect->y + rect->height / 2, expander_state);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_combo_box_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean ishtml, GtkTextDirection direction)
+{
+ GdkRectangle arrow_rect, real_arrow_rect;
+ gint arrow_size, separator_width;
+ gboolean wide_separators;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GtkStyle* style;
+ GtkRequisition arrow_req;
+
+ ensure_combo_box_widgets();
+
+ /* Also sets the direction on gComboBoxButtonWidget, which is then
+ * inherited by the separator and arrow */
+ moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL,
+ gComboBoxButtonWidget, direction);
+
+ calculate_button_inner_rect(gComboBoxButtonWidget,
+ rect, &arrow_rect, direction, ishtml);
+ /* 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_size_request(gComboBoxArrowWidget, &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(gComboBoxArrowWidget,
+ &arrow_rect, &real_arrow_rect, direction);
+
+ style = gComboBoxArrowWidget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_widget_size_allocate(gComboBoxWidget, rect);
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ gComboBoxArrowWidget, "arrow", GTK_ARROW_DOWN, TRUE,
+ real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width, real_arrow_rect.height);
+
+
+ /* If there is no separator in the theme, there's nothing left to do. */
+ if (!gComboBoxSeparatorWidget)
+ return MOZ_GTK_SUCCESS;
+
+ style = gComboBoxSeparatorWidget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_widget_style_get(gComboBoxSeparatorWidget,
+ "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_paint_box(style, drawable,
+ GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT,
+ cliprect, gComboBoxSeparatorWidget, "vseparator",
+ arrow_rect.x, arrow_rect.y,
+ separator_width, arrow_rect.height);
+ } else {
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x -= XTHICKNESS(style);
+ else
+ arrow_rect.x += arrow_rect.width;
+
+ gtk_paint_vline(style, drawable, GTK_STATE_NORMAL, cliprect,
+ gComboBoxSeparatorWidget, "vseparator",
+ arrow_rect.y, arrow_rect.y + arrow_rect.height,
+ arrow_rect.x);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkArrowType arrow_type, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GdkRectangle arrow_rect;
+
+ ensure_button_arrow_widget();
+ style = gButtonArrowWidget->style;
+ gtk_widget_set_direction(gButtonArrowWidget, direction);
+
+ calculate_arrow_rect(gButtonArrowWidget, rect, &arrow_rect,
+ direction);
+
+ 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;
+ }
+
+ TSOffsetStyleGCs(style, arrow_rect.x, arrow_rect.y);
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ gButtonArrowWidget, "arrow", arrow_type, TRUE,
+ arrow_rect.x, arrow_rect.y, arrow_rect.width, arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_combo_box_entry_button_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect,
+ GtkWidgetState* state,
+ gboolean input_focus,
+ GtkTextDirection direction)
+{
+ gint x_displacement, y_displacement;
+ GdkRectangle arrow_rect, real_arrow_rect;
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GtkStyle* style;
+
+ ensure_combo_box_entry_widgets();
+
+ if (input_focus) {
+ /* Some themes draw a complementary focus ring for the dropdown button
+ * when the dropdown entry has focus */
+ GTK_WIDGET_SET_FLAGS(gComboBoxEntryTextareaWidget, GTK_HAS_FOCUS);
+ }
+
+ moz_gtk_button_paint(drawable, rect, cliprect, state, GTK_RELIEF_NORMAL,
+ gComboBoxEntryButtonWidget, direction);
+
+ if (input_focus)
+ GTK_WIDGET_UNSET_FLAGS(gComboBoxEntryTextareaWidget, GTK_HAS_FOCUS);
+
+ calculate_button_inner_rect(gComboBoxEntryButtonWidget,
+ rect, &arrow_rect, direction, FALSE);
+ if (state_type == GTK_STATE_ACTIVE) {
+ gtk_widget_style_get(gComboBoxEntryButtonWidget,
+ "child-displacement-x", &x_displacement,
+ "child-displacement-y", &y_displacement,
+ NULL);
+ arrow_rect.x += x_displacement;
+ arrow_rect.y += y_displacement;
+ }
+
+ calculate_arrow_rect(gComboBoxEntryArrowWidget,
+ &arrow_rect, &real_arrow_rect, direction);
+
+ style = gComboBoxEntryArrowWidget->style;
+ TSOffsetStyleGCs(style, real_arrow_rect.x, real_arrow_rect.y);
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, cliprect,
+ gComboBoxEntryArrowWidget, "arrow", GTK_ARROW_DOWN, TRUE,
+ real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width, real_arrow_rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_container_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean isradio, GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkStyle* style;
+ GtkWidget *widget;
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ if (isradio) {
+ ensure_radiobutton_widget();
+ widget = gRadiobuttonWidget;
+ } else {
+ ensure_checkbox_widget();
+ widget = gCheckboxWidget;
+ }
+ gtk_widget_set_direction(widget, direction);
+
+ style = widget->style;
+ moz_gtk_widget_get_focus(widget, &interior_focus, &focus_width,
+ &focus_pad);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ /* The detail argument for the gtk_paint_* calls below are "checkbutton"
+ even for radio buttons, to match what gtk does. */
+
+ /* this is for drawing a prelight box */
+ if (state_type == GTK_STATE_PRELIGHT || state_type == GTK_STATE_ACTIVE) {
+ gtk_paint_flat_box(style, drawable, GTK_STATE_PRELIGHT,
+ GTK_SHADOW_ETCHED_OUT, cliprect, widget,
+ "checkbutton",
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ if (state_type != GTK_STATE_NORMAL && state_type != GTK_STATE_PRELIGHT)
+ state_type = GTK_STATE_NORMAL;
+
+ if (state->focused && !interior_focus) {
+ gtk_paint_focus(style, drawable, state_type, cliprect, widget,
+ "checkbutton",
+ rect->x, rect->y, rect->width, rect->height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toggle_label_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean isradio, GtkTextDirection direction)
+{
+ GtkStateType state_type;
+ GtkStyle *style;
+ GtkWidget *widget;
+ gboolean interior_focus;
+
+ if (!state->focused)
+ return MOZ_GTK_SUCCESS;
+
+ if (isradio) {
+ ensure_radiobutton_widget();
+ widget = gRadiobuttonWidget;
+ } else {
+ ensure_checkbox_widget();
+ widget = gCheckboxWidget;
+ }
+ gtk_widget_set_direction(widget, direction);
+
+ gtk_widget_style_get(widget, "interior-focus", &interior_focus, NULL);
+ if (!interior_focus)
+ return MOZ_GTK_SUCCESS;
+
+ state_type = ConvertGtkState(state);
+
+ style = widget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ /* Always "checkbutton" to match gtkcheckbutton.c */
+ gtk_paint_focus(style, drawable, state_type, cliprect, widget,
+ "checkbutton",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toolbar_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkShadowType shadow_type;
+
+ ensure_toolbar_widget();
+ gtk_widget_set_direction(gToolbarWidget, direction);
+
+ style = gToolbarWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_style_apply_default_background(style, drawable, TRUE,
+ GTK_STATE_NORMAL,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+
+ gtk_widget_style_get(gToolbarWidget, "shadow-type", &shadow_type, NULL);
+
+ gtk_paint_box (style, drawable, GTK_STATE_NORMAL, shadow_type,
+ cliprect, gToolbarWidget, "toolbar",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toolbar_separator_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+ 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;
+
+ ensure_toolbar_separator_widget();
+ gtk_widget_set_direction(gToolbarSeparatorWidget, direction);
+
+ style = gToolbarSeparatorWidget->style;
+
+ gtk_widget_style_get(gToolbarWidget,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ if (wide_separators) {
+ if (separator_width > rect->width)
+ separator_width = rect->width;
+
+ gtk_paint_box(style, drawable,
+ GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT,
+ cliprect, gToolbarWidget, "vseparator",
+ rect->x + (rect->width - separator_width) / 2,
+ rect->y + rect->height * start_fraction,
+ separator_width,
+ rect->height * (end_fraction - start_fraction));
+
+ } else {
+ paint_width = style->xthickness;
+
+ if (paint_width > rect->width)
+ paint_width = rect->width;
+
+ gtk_paint_vline(style, drawable,
+ GTK_STATE_NORMAL, cliprect, gToolbarSeparatorWidget,
+ "toolbar",
+ rect->y + rect->height * start_fraction,
+ rect->y + rect->height * end_fraction,
+ rect->x + (rect->width - paint_width) / 2);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tooltip_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+
+ ensure_tooltip_widget();
+ gtk_widget_set_direction(gTooltipWidget, direction);
+
+ style = gtk_rc_get_style_by_paths(gtk_settings_get_default(),
+ "gtk-tooltips", "GtkWindow",
+ GTK_TYPE_WINDOW);
+
+ style = gtk_style_attach(style, gTooltipWidget->window);
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_flat_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ cliprect, gTooltipWidget, "tooltip",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_resizer_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkStateType state_type = ConvertGtkState(state);
+
+ ensure_frame_widget();
+ gtk_widget_set_direction(gStatusbarWidget, direction);
+
+ style = gStatusbarWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_paint_resize_grip(style, drawable, state_type, cliprect, gStatusbarWidget,
+ "statusbar", (direction == GTK_TEXT_DIR_LTR) ?
+ GDK_WINDOW_EDGE_SOUTH_EAST :
+ GDK_WINDOW_EDGE_SOUTH_WEST,
+ rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_frame_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkShadowType shadow_type;
+
+ ensure_frame_widget();
+ gtk_widget_set_direction(gFrameWidget, direction);
+
+ style = gFrameWidget->style;
+
+ gtk_widget_style_get(gStatusbarWidget, "shadow-type", &shadow_type, NULL);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_shadow(style, drawable, GTK_STATE_NORMAL, shadow_type,
+ cliprect, gFrameWidget, "frame", rect->x, rect->y,
+ rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_progressbar_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+
+ ensure_progress_widget();
+ gtk_widget_set_direction(gProgressWidget, direction);
+
+ style = gProgressWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_IN,
+ cliprect, gProgressWidget, "trough", rect->x, rect->y,
+ rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_progress_chunk_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction,
+ WidgetNodeType widget)
+{
+ GtkStyle* style;
+
+ ensure_progress_widget();
+ gtk_widget_set_direction(gProgressWidget, direction);
+
+ style = gProgressWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ 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_paint_box(style, drawable, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT,
+ cliprect, gProgressWidget, "bar", rect->x, rect->y,
+ rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_tab_thickness(WidgetNodeType aNodeType)
+{
+ ensure_tab_widget();
+ if (YTHICKNESS(gTabWidget->style) < 2)
+ return 2; /* some themes don't set ythickness correctly */
+
+ return YTHICKNESS(gTabWidget->style);
+}
+
+static gint
+moz_gtk_tab_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, 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. */
+
+ GtkStyle* style;
+ GdkRectangle focusRect;
+ gboolean isBottomTab = (widget == MOZ_GTK_TAB_BOTTOM);
+
+ ensure_tab_widget();
+ gtk_widget_set_direction(gTabWidget, direction);
+
+ style = gTabWidget->style;
+ focusRect = *rect;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ if ((flags & MOZ_GTK_TAB_SELECTED) == 0) {
+ /* Only draw the tab */
+ gtk_paint_extension(style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_OUT,
+ cliprect, gTabWidget, "tab",
+ rect->x, rect->y, rect->width, rect->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_paint_box_gap(), see comment in
+ * moz_gtk_tabpanels_paint(). This box_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(widget);
+
+ /* 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 = 0;
+ else
+ gap_loffset = 0;
+ }
+
+ if (isBottomTab) {
+ /* Draw the tab */
+ focusRect.y += gap_voffset;
+ focusRect.height -= gap_voffset;
+ gtk_paint_extension(style, drawable, GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT, cliprect, gTabWidget, "tab",
+ rect->x, rect->y + gap_voffset, rect->width,
+ rect->height - gap_voffset, GTK_POS_TOP);
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_style_apply_default_background(style, drawable, TRUE,
+ GTK_STATE_NORMAL, cliprect,
+ rect->x,
+ rect->y + gap_voffset
+ - gap_height,
+ rect->width, gap_height);
+ gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ cliprect, gTabWidget, "notebook",
+ rect->x - gap_loffset,
+ rect->y + gap_voffset - 3 * gap_height,
+ rect->width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_BOTTOM,
+ gap_loffset, rect->width);
+ } else {
+ /* Draw the tab */
+ focusRect.height -= gap_voffset;
+ gtk_paint_extension(style, drawable, GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT, cliprect, gTabWidget, "tab",
+ rect->x, rect->y, rect->width,
+ rect->height - gap_voffset, GTK_POS_BOTTOM);
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_style_apply_default_background(style, drawable, TRUE,
+ GTK_STATE_NORMAL, cliprect,
+ rect->x,
+ rect->y + rect->height
+ - gap_voffset,
+ rect->width, gap_height);
+ gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ cliprect, gTabWidget, "notebook",
+ rect->x - gap_loffset,
+ rect->y + rect->height - gap_voffset,
+ rect->width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_TOP,
+ gap_loffset, rect->width);
+ }
+
+ }
+
+ if (state->focused) {
+ /* Paint the focus ring */
+ focusRect.x += XTHICKNESS(style);
+ focusRect.width -= XTHICKNESS(style) * 2;
+ focusRect.y += YTHICKNESS(style);
+ focusRect.height -= YTHICKNESS(style) * 2;
+
+ gtk_paint_focus(style, drawable,
+ /* Believe it or not, NORMAL means a selected tab and
+ ACTIVE means an unselected tab. */
+ (flags & MOZ_GTK_TAB_SELECTED) ? GTK_STATE_NORMAL
+ : GTK_STATE_ACTIVE,
+ cliprect, gTabWidget, "tab",
+ focusRect.x, focusRect.y, focusRect.width, focusRect.height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tabpanels_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ /* We have three problems here:
+ * - Most engines draw gtk_paint_box differently to gtk_paint_box_gap, the
+ * former implies there are no tabs, eg. Clearlooks.
+ * - Wanting a gap of width 0 doesn't actually guarantee a zero-width gap, eg.
+ * Clearlooks.
+ * - Our old approach of a negative X position could cause rendering errors
+ * on the box's corner, eg. themes using the Pixbuf engine.
+ */
+ GtkStyle* style;
+ GdkRectangle halfClipRect;
+
+ ensure_tab_widget();
+ gtk_widget_set_direction(gTabWidget, direction);
+
+ style = gTabWidget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ /* Our approach is as follows:
+ * - Draw the box in two passes. Pass in a clip rect to draw the left half of the
+ * box, with the gap specified to the right outside the clip rect so that it is
+ * not drawn.
+ * - The right half is drawn with the gap to the left outside the modified clip rect.
+ */
+ if (!gdk_rectangle_intersect(rect, cliprect, &halfClipRect))
+ return MOZ_GTK_SUCCESS;
+
+ halfClipRect.width = (halfClipRect.width / 2) + 1;
+ gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ &halfClipRect, gTabWidget, "notebook", rect->x, rect->y,
+ rect->width, rect->height,
+ GTK_POS_TOP, halfClipRect.width + 1, 0);
+
+ halfClipRect.x += halfClipRect.width;
+ gtk_paint_box_gap(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ &halfClipRect, gTabWidget, "notebook", rect->x, rect->y,
+ rect->width, rect->height,
+ GTK_POS_TOP, -10, 0);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tab_scroll_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkArrowType arrow_type,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkShadowType shadow_type = state->active ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
+ GtkStyle* style;
+ 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;
+
+ ensure_tab_widget();
+
+ style = gTabWidget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ arrow_type = (arrow_type == GTK_ARROW_LEFT) ?
+ GTK_ARROW_RIGHT : GTK_ARROW_LEFT;
+ }
+
+ gtk_paint_arrow(style, drawable, state_type, shadow_type, NULL,
+ gTabWidget, "notebook", arrow_type, TRUE,
+ x, y, arrow_size, arrow_size);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_bar_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkShadowType shadow_type;
+ ensure_menu_bar_widget();
+ gtk_widget_set_direction(gMenuBarWidget, direction);
+
+ gtk_widget_style_get(gMenuBarWidget, "shadow-type", &shadow_type, NULL);
+
+ style = gMenuBarWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_style_apply_default_background(style, drawable, TRUE, GTK_STATE_NORMAL,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+
+ gtk_paint_box(style, drawable, GTK_STATE_NORMAL, shadow_type,
+ cliprect, gMenuBarWidget, "menubar", rect->x, rect->y,
+ rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_popup_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ ensure_menu_popup_widget();
+ gtk_widget_set_direction(gMenuPopupWidget, direction);
+
+ style = gMenuPopupWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_style_apply_default_background(style, drawable, TRUE, GTK_STATE_NORMAL,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+ gtk_paint_box(style, drawable, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
+ cliprect, gMenuPopupWidget, "menu",
+ rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_separator_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ gboolean wide_separators;
+ gint separator_height;
+ guint horizontal_padding;
+ gint paint_height;
+
+ ensure_menu_separator_widget();
+ gtk_widget_set_direction(gMenuSeparatorWidget, direction);
+
+ style = gMenuSeparatorWidget->style;
+
+ gtk_widget_style_get(gMenuSeparatorWidget,
+ "wide-separators", &wide_separators,
+ "separator-height", &separator_height,
+ "horizontal-padding", &horizontal_padding,
+ NULL);
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ if (wide_separators) {
+ if (separator_height > rect->height)
+ separator_height = rect->height;
+
+ gtk_paint_box(style, drawable,
+ GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT,
+ cliprect, gMenuSeparatorWidget, "hseparator",
+ rect->x + horizontal_padding + style->xthickness,
+ rect->y + (rect->height - separator_height - style->ythickness) / 2,
+ rect->width - 2 * (horizontal_padding + style->xthickness),
+ separator_height);
+ } else {
+ paint_height = style->ythickness;
+ if (paint_height > rect->height)
+ paint_height = rect->height;
+
+ gtk_paint_hline(style, drawable,
+ GTK_STATE_NORMAL, cliprect, gMenuSeparatorWidget,
+ "menuitem",
+ rect->x + horizontal_padding + style->xthickness,
+ rect->x + rect->width - horizontal_padding - style->xthickness - 1,
+ rect->y + (rect->height - style->ythickness) / 2);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_item_paint(WidgetNodeType widget, GdkDrawable* drawable,
+ GdkRectangle* rect, GdkRectangle* cliprect,
+ GtkWidgetState* state, GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkShadowType shadow_type;
+ GtkWidget* item_widget;
+
+ if (state->inHover && !state->disabled) {
+ if (widget == MOZ_GTK_MENUBARITEM) {
+ ensure_menu_bar_item_widget();
+ item_widget = gMenuBarItemWidget;
+ } else {
+ ensure_menu_item_widget();
+ item_widget = gMenuItemWidget;
+ }
+ gtk_widget_set_direction(item_widget, direction);
+
+ style = item_widget->style;
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+
+ gtk_widget_style_get(item_widget, "selected-shadow-type",
+ &shadow_type, NULL);
+
+ gtk_paint_box(style, drawable, GTK_STATE_PRELIGHT, shadow_type,
+ cliprect, item_widget, "menuitem", rect->x, rect->y,
+ rect->width, rect->height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_arrow_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyle* style;
+ GtkStateType state_type = ConvertGtkState(state);
+
+ ensure_menu_item_widget();
+ gtk_widget_set_direction(gMenuItemWidget, direction);
+
+ style = gMenuItemWidget->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_paint_arrow(style, drawable, state_type,
+ (state_type == GTK_STATE_PRELIGHT) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
+ cliprect, gMenuItemWidget, "menuitem",
+ (direction == GTK_TEXT_DIR_LTR) ? GTK_ARROW_RIGHT : GTK_ARROW_LEFT,
+ TRUE, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_check_menu_item_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkWidgetState* state,
+ gboolean checked, gboolean isradio,
+ GtkTextDirection direction)
+{
+ GtkStateType state_type = ConvertGtkState(state);
+ GtkStyle* style;
+ GtkShadowType shadow_type = (checked)?GTK_SHADOW_IN:GTK_SHADOW_OUT;
+ gint offset;
+ gint indicator_size, horizontal_padding;
+ gint x, y;
+
+ moz_gtk_menu_item_paint(MOZ_GTK_MENUITEM, drawable, rect, cliprect, state,
+ direction);
+
+ ensure_check_menu_item_widget();
+ gtk_widget_set_direction(gCheckMenuItemWidget, direction);
+
+ gtk_widget_style_get (gCheckMenuItemWidget,
+ "indicator-size", &indicator_size,
+ "horizontal-padding", &horizontal_padding,
+ NULL);
+
+ if (checked || GTK_CHECK_MENU_ITEM(gCheckMenuItemWidget)->always_show_toggle) {
+ style = gCheckMenuItemWidget->style;
+
+ offset = GTK_CONTAINER(gCheckMenuItemWidget)->border_width +
+ gCheckMenuItemWidget->style->xthickness + 2;
+
+ x = (direction == GTK_TEXT_DIR_RTL) ?
+ rect->width - indicator_size - offset - horizontal_padding: rect->x + offset + horizontal_padding;
+ y = rect->y + (rect->height - indicator_size) / 2;
+
+ TSOffsetStyleGCs(style, x, y);
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gCheckMenuItemWidget),
+ checked);
+
+ if (isradio) {
+ gtk_paint_option(style, drawable, state_type, shadow_type, cliprect,
+ gCheckMenuItemWidget, "option",
+ x, y, indicator_size, indicator_size);
+ } else {
+ gtk_paint_check(style, drawable, state_type, shadow_type, cliprect,
+ gCheckMenuItemWidget, "check",
+ x, y, indicator_size, indicator_size);
+ }
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_window_paint(GdkDrawable* drawable, GdkRectangle* rect,
+ GdkRectangle* cliprect, GtkTextDirection direction)
+{
+ GtkStyle* style;
+
+ ensure_window_widget();
+ gtk_widget_set_direction(gProtoWindow, direction);
+
+ style = gProtoWindow->style;
+
+ TSOffsetStyleGCs(style, rect->x, rect->y);
+ gtk_style_apply_default_background(style, drawable, TRUE,
+ GTK_STATE_NORMAL,
+ cliprect, rect->x, rect->y,
+ rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
+ gint* right, gint* bottom, GtkTextDirection direction,
+ gboolean inhtml)
+{
+ GtkWidget* w;
+
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON:
+ {
+ GtkBorder inner_border;
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ ensure_button_widget();
+ *left = *top = *right = *bottom = GTK_CONTAINER(gButtonWidget)->border_width;
+
+ /* Don't add this padding in HTML, otherwise the buttons will
+ become too big and stuff the layout. */
+ if (!inhtml) {
+ moz_gtk_widget_get_focus(gButtonWidget, &interior_focus, &focus_width, &focus_pad);
+ moz_gtk_button_get_inner_border(gButtonWidget, &inner_border);
+ *left += focus_width + focus_pad + inner_border.left;
+ *right += focus_width + focus_pad + inner_border.right;
+ *top += focus_width + focus_pad + inner_border.top;
+ *bottom += focus_width + focus_pad + inner_border.bottom;
+ }
+
+ *left += gButtonWidget->style->xthickness;
+ *right += gButtonWidget->style->xthickness;
+ *top += gButtonWidget->style->ythickness;
+ *bottom += gButtonWidget->style->ythickness;
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_ENTRY:
+ ensure_entry_widget();
+ w = gEntryWidget;
+ break;
+ case MOZ_GTK_TREEVIEW:
+ ensure_tree_view_widget();
+ w = gTreeViewWidget;
+ break;
+ 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.
+ * */
+
+ GtkBorder inner_border;
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ ensure_tree_header_cell_widget();
+ *left = *top = *right = *bottom = GTK_CONTAINER(gTreeHeaderCellWidget)->border_width;
+
+ moz_gtk_widget_get_focus(gTreeHeaderCellWidget, &interior_focus, &focus_width, &focus_pad);
+ moz_gtk_button_get_inner_border(gTreeHeaderCellWidget, &inner_border);
+ *left += focus_width + focus_pad + inner_border.left;
+ *right += focus_width + focus_pad + inner_border.right;
+ *top += focus_width + focus_pad + inner_border.top;
+ *bottom += focus_width + focus_pad + inner_border.bottom;
+
+ *left += gTreeHeaderCellWidget->style->xthickness;
+ *right += gTreeHeaderCellWidget->style->xthickness;
+ *top += gTreeHeaderCellWidget->style->ythickness;
+ *bottom += gTreeHeaderCellWidget->style->ythickness;
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ ensure_tree_header_cell_widget();
+ w = gTreeHeaderSortArrowWidget;
+ break;
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ ensure_combo_box_entry_widgets();
+ w = gComboBoxEntryTextareaWidget;
+ break;
+ case MOZ_GTK_DROPDOWN_ARROW:
+ ensure_combo_box_entry_widgets();
+ w = gComboBoxEntryButtonWidget;
+ 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 ignored_interior_focus, wide_separators;
+ gint focus_width, focus_pad, separator_width;
+ GtkRequisition arrow_req;
+
+ ensure_combo_box_widgets();
+
+ *left = GTK_CONTAINER(gComboBoxButtonWidget)->border_width;
+
+ if (!inhtml) {
+ moz_gtk_widget_get_focus(gComboBoxButtonWidget,
+ &ignored_interior_focus,
+ &focus_width, &focus_pad);
+ *left += focus_width + focus_pad;
+ }
+
+ *top = *left + gComboBoxButtonWidget->style->ythickness;
+ *left += gComboBoxButtonWidget->style->xthickness;
+
+ *right = *left; *bottom = *top;
+
+ /* If there is no separator, don't try to count its width. */
+ separator_width = 0;
+ if (gComboBoxSeparatorWidget) {
+ gtk_widget_style_get(gComboBoxSeparatorWidget,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+
+ if (!wide_separators)
+ separator_width =
+ XTHICKNESS(gComboBoxSeparatorWidget->style);
+ }
+
+ gtk_widget_size_request(gComboBoxArrowWidget, &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:
+ ensure_tab_widget();
+ w = gTabWidget;
+ break;
+ case MOZ_GTK_PROGRESSBAR:
+ ensure_progress_widget();
+ w = gProgressWidget;
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ ensure_spin_widget();
+ w = gSpinWidget;
+ break;
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ ensure_scale_widget();
+ w = gHScaleWidget;
+ break;
+ case MOZ_GTK_SCALE_VERTICAL:
+ ensure_scale_widget();
+ w = gVScaleWidget;
+ break;
+ case MOZ_GTK_FRAME:
+ ensure_frame_widget();
+ w = gFrameWidget;
+ break;
+ case MOZ_GTK_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ {
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ /* If the focus is interior, then the label has a border of
+ (focus_width + focus_pad). */
+ if (widget == MOZ_GTK_CHECKBUTTON_LABEL) {
+ ensure_checkbox_widget();
+ moz_gtk_widget_get_focus(gCheckboxWidget, &interior_focus,
+ &focus_width, &focus_pad);
+ }
+ else {
+ ensure_radiobutton_widget();
+ moz_gtk_widget_get_focus(gRadiobuttonWidget, &interior_focus,
+ &focus_width, &focus_pad);
+ }
+
+ if (interior_focus)
+ *left = *top = *right = *bottom = (focus_width + focus_pad);
+ else
+ *left = *top = *right = *bottom = 0;
+
+ return MOZ_GTK_SUCCESS;
+ }
+
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ {
+ gboolean interior_focus;
+ gint focus_width, focus_pad;
+
+ /* If the focus is _not_ interior, then the container has a border
+ of (focus_width + focus_pad). */
+ if (widget == MOZ_GTK_CHECKBUTTON_CONTAINER) {
+ ensure_checkbox_widget();
+ moz_gtk_widget_get_focus(gCheckboxWidget, &interior_focus,
+ &focus_width, &focus_pad);
+ w = gCheckboxWidget;
+ } else {
+ ensure_radiobutton_widget();
+ moz_gtk_widget_get_focus(gRadiobuttonWidget, &interior_focus,
+ &focus_width, &focus_pad);
+ w = gRadiobuttonWidget;
+ }
+
+ *left = *top = *right = *bottom = GTK_CONTAINER(w)->border_width;
+
+ if (!interior_focus) {
+ *left += (focus_width + focus_pad);
+ *right += (focus_width + focus_pad);
+ *top += (focus_width + focus_pad);
+ *bottom += (focus_width + focus_pad);
+ }
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_MENUPOPUP:
+ ensure_menu_popup_widget();
+ w = gMenuPopupWidget;
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ // Bug 1274143 for MOZ_GTK_MENUBARITEM.
+ // Fall through to MOZ_GTK_MENUITEM for now.
+ case MOZ_GTK_MENUITEM:
+ ensure_menu_item_widget();
+ ensure_menu_bar_item_widget();
+ w = gMenuItemWidget;
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM:
+ ensure_check_menu_item_widget();
+ w = gCheckMenuItemWidget;
+ break;
+ case MOZ_GTK_TAB_TOP:
+ case MOZ_GTK_TAB_BOTTOM:
+ ensure_tab_widget();
+ w = gTabWidget;
+ break;
+ case MOZ_GTK_TOOLTIP:
+ // In GTK 2 the spacing between box is set to 4.
+ *left = *top = *right = *bottom = 4;
+ return MOZ_GTK_SUCCESS;
+ /* These widgets have no borders, since they are not containers. */
+ 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_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ 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:
+ /* These widgets have no borders.*/
+ 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:
+ *left = *top = *right = *bottom = 0;
+ return MOZ_GTK_SUCCESS;
+ default:
+ g_warning("Unsupported widget type: %d", widget);
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+
+ *right = *left = XTHICKNESS(w->style);
+ *bottom = *top = YTHICKNESS(w->style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom,
+ GtkTextDirection direction, GtkTabFlags flags,
+ WidgetNodeType widget)
+{
+ moz_gtk_get_widget_border(widget, left, top,
+ right, bottom, direction,
+ FALSE);
+
+ // Top tabs have no bottom border, bottom tabs have no top border
+ if (widget == MOZ_GTK_TAB_BOTTOM) {
+ *top = 0;
+ } else {
+ *bottom = 0;
+ }
+
+ 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;
+ ensure_combo_box_entry_widgets();
+
+ gtk_widget_size_request(gComboBoxEntryButtonWidget, &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;
+
+ ensure_tab_widget();
+ gtk_widget_style_get(gTabWidget,
+ "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:
+ ensure_combo_box_widgets();
+ widget = gComboBoxArrowWidget;
+ break;
+ default:
+ ensure_button_arrow_widget();
+ widget = gButtonArrowWidget;
+ break;
+ }
+
+ GtkRequisition requisition;
+ gtk_widget_size_request(widget, &requisition);
+ *width = requisition.width;
+ *height = requisition.height;
+}
+
+gint
+moz_gtk_get_toolbar_separator_width(gint* size)
+{
+ gboolean wide_separators;
+ gint separator_width;
+ GtkStyle* style;
+
+ ensure_toolbar_widget();
+
+ style = gToolbarWidget->style;
+
+ gtk_widget_style_get(gToolbarWidget,
+ "space-size", size,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+
+ /* Just in case... */
+ *size = MAX(*size, (wide_separators ? separator_width : style->xthickness));
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_expander_size(gint* size)
+{
+ ensure_expander_widget();
+ gtk_widget_style_get(gExpanderWidget,
+ "expander-size", size,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_treeview_expander_size(gint* size)
+{
+ ensure_tree_view_widget();
+ gtk_widget_style_get(gTreeViewWidget,
+ "expander-size", size,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_menu_separator_height(gint *size)
+{
+ gboolean wide_separators;
+ gint separator_height;
+
+ ensure_menu_separator_widget();
+
+ gtk_widget_style_get(gMenuSeparatorWidget,
+ "wide-separators", &wide_separators,
+ "separator-height", &separator_height,
+ NULL);
+
+ if (wide_separators)
+ *size = separator_height + gMenuSeparatorWidget->style->ythickness;
+ else
+ *size = gMenuSeparatorWidget->style->ythickness * 2;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void
+moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width,
+ gint* scale_height)
+{
+ moz_gtk_get_scalethumb_metrics(orient, scale_width, scale_height);
+}
+
+
+gint
+moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length, gint* thumb_height)
+{
+ GtkWidget* widget;
+
+ ensure_scale_widget();
+ widget = ((orient == GTK_ORIENTATION_HORIZONTAL) ? gHScaleWidget : gVScaleWidget);
+
+ gtk_widget_style_get (widget,
+ "slider_length", thumb_length,
+ "slider_width", thumb_height,
+ NULL);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics *metrics)
+{
+ ensure_scrollbar_widget();
+
+ gtk_widget_style_get (gHorizScrollbarWidget,
+ "slider_width", &metrics->slider_width,
+ "trough_border", &metrics->trough_border,
+ "stepper_size", &metrics->stepper_size,
+ "stepper_spacing", &metrics->stepper_spacing,
+ NULL);
+
+ metrics->min_slider_size =
+ GTK_RANGE(gHorizScrollbarWidget)->min_slider_size;
+
+ return MOZ_GTK_SUCCESS;
+}
+void
+moz_gtk_get_widget_min_size(WidgetNodeType aGtkWidgetType, int* width, int* height) {
+ MOZ_ASSERT_UNREACHABLE("get_widget_min_size not available for GTK2");
+}
+
+gint
+moz_gtk_widget_paint(WidgetNodeType widget, GdkDrawable* drawable,
+ GdkRectangle* rect, GdkRectangle* cliprect,
+ GtkWidgetState* state, gint flags,
+ GtkTextDirection direction)
+{
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON:
+ if (state->depressed) {
+ ensure_toggle_button_widget();
+ return moz_gtk_button_paint(drawable, rect, cliprect, state,
+ (GtkReliefStyle) flags,
+ gToggleButtonWidget, direction);
+ }
+ ensure_button_widget();
+ return moz_gtk_button_paint(drawable, rect, cliprect, state,
+ (GtkReliefStyle) flags, gButtonWidget,
+ direction);
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ return moz_gtk_toggle_paint(drawable, rect, cliprect, state,
+ !!(flags & MOZ_GTK_WIDGET_CHECKED),
+ !!(flags & MOZ_GTK_WIDGET_INCONSISTENT),
+ (widget == MOZ_GTK_RADIOBUTTON),
+ direction);
+ break;
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ return moz_gtk_scrollbar_button_paint(drawable, rect, cliprect, state,
+ (GtkScrollbarButtonFlags) flags,
+ direction);
+ break;
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ return moz_gtk_scrollbar_trough_paint(widget, drawable, rect,
+ cliprect, state, direction);
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ return MOZ_GTK_SUCCESS;
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ return moz_gtk_scrollbar_thumb_paint(widget, drawable, rect,
+ cliprect, state, direction);
+ break;
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ case MOZ_GTK_SCALE_VERTICAL:
+ return moz_gtk_scale_paint(drawable, rect, cliprect, state,
+ (GtkOrientation) flags, direction);
+ break;
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ return moz_gtk_scale_thumb_paint(drawable, rect, cliprect, state,
+ (GtkOrientation) flags, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON:
+ return moz_gtk_spin_paint(drawable, rect, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ return moz_gtk_spin_updown_paint(drawable, rect,
+ (widget == MOZ_GTK_SPINBUTTON_DOWN),
+ state, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ ensure_spin_widget();
+ return moz_gtk_entry_paint(drawable, rect, cliprect, state,
+ gSpinWidget, direction);
+ break;
+ case MOZ_GTK_GRIPPER:
+ return moz_gtk_gripper_paint(drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_TREEVIEW:
+ return moz_gtk_treeview_paint(drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_TREE_HEADER_CELL:
+ return moz_gtk_tree_header_cell_paint(drawable, rect, cliprect, state,
+ flags, direction);
+ break;
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ return moz_gtk_tree_header_sort_arrow_paint(drawable, rect, cliprect,
+ state,
+ (GtkArrowType) flags,
+ direction);
+ break;
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ return moz_gtk_treeview_expander_paint(drawable, rect, cliprect, state,
+ (GtkExpanderStyle) flags, direction);
+ break;
+ case MOZ_GTK_ENTRY:
+ ensure_entry_widget();
+ return moz_gtk_entry_paint(drawable, rect, cliprect, state,
+ gEntryWidget, direction);
+ break;
+ case MOZ_GTK_DROPDOWN:
+ return moz_gtk_combo_box_paint(drawable, rect, cliprect, state,
+ (gboolean) flags, direction);
+ break;
+ case MOZ_GTK_DROPDOWN_ARROW:
+ return moz_gtk_combo_box_entry_button_paint(drawable, rect, cliprect,
+ state, flags, direction);
+ break;
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ ensure_combo_box_entry_widgets();
+ return moz_gtk_entry_paint(drawable, rect, cliprect, state,
+ gComboBoxEntryTextareaWidget, direction);
+ break;
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ return moz_gtk_container_paint(drawable, rect, cliprect, state,
+ (widget == MOZ_GTK_RADIOBUTTON_CONTAINER),
+ direction);
+ break;
+ case MOZ_GTK_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ return moz_gtk_toggle_label_paint(drawable, rect, cliprect, state,
+ (widget == MOZ_GTK_RADIOBUTTON_LABEL),
+ direction);
+ break;
+ case MOZ_GTK_TOOLBAR:
+ return moz_gtk_toolbar_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ return moz_gtk_toolbar_separator_paint(drawable, rect, cliprect,
+ direction);
+ break;
+ case MOZ_GTK_TOOLTIP:
+ return moz_gtk_tooltip_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_FRAME:
+ return moz_gtk_frame_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_RESIZER:
+ return moz_gtk_resizer_paint(drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_PROGRESSBAR:
+ return moz_gtk_progressbar_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_PROGRESS_CHUNK:
+ case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
+ case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
+ return moz_gtk_progress_chunk_paint(drawable, rect, cliprect,
+ direction, widget);
+ break;
+ case MOZ_GTK_TAB_TOP:
+ case MOZ_GTK_TAB_BOTTOM:
+ return moz_gtk_tab_paint(drawable, rect, cliprect, state,
+ (GtkTabFlags) flags, direction, widget);
+ break;
+ case MOZ_GTK_TABPANELS:
+ return moz_gtk_tabpanels_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_TAB_SCROLLARROW:
+ return moz_gtk_tab_scroll_arrow_paint(drawable, rect, cliprect, state,
+ (GtkArrowType) flags, direction);
+ break;
+ case MOZ_GTK_MENUBAR:
+ return moz_gtk_menu_bar_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_MENUPOPUP:
+ return moz_gtk_menu_popup_paint(drawable, rect, cliprect, direction);
+ break;
+ case MOZ_GTK_MENUSEPARATOR:
+ return moz_gtk_menu_separator_paint(drawable, rect, cliprect,
+ direction);
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ case MOZ_GTK_MENUITEM:
+ return moz_gtk_menu_item_paint(widget, drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_MENUARROW:
+ return moz_gtk_menu_arrow_paint(drawable, rect, cliprect, state,
+ direction);
+ break;
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ return moz_gtk_arrow_paint(drawable, rect, cliprect, state,
+ (GtkArrowType) flags, direction);
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM:
+ return moz_gtk_check_menu_item_paint(drawable, rect, cliprect, state,
+ (gboolean) flags,
+ (widget == MOZ_GTK_RADIOMENUITEM),
+ direction);
+ break;
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ return moz_gtk_vpaned_paint(drawable, rect, cliprect, state);
+ break;
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ return moz_gtk_hpaned_paint(drawable, rect, cliprect, state);
+ break;
+ case MOZ_GTK_WINDOW:
+ return moz_gtk_window_paint(drawable, rect, cliprect, direction);
+ break;
+ default:
+ g_warning("Unknown widget type: %d", widget);
+ }
+
+ return MOZ_GTK_UNKNOWN_WIDGET;
+}
+
+GtkWidget* moz_gtk_get_scrollbar_widget(void)
+{
+ MOZ_ASSERT(is_initialized, "Forgot to call moz_gtk_init()");
+ ensure_scrollbar_widget();
+ return gHorizScrollbarWidget;
+}
+
+gboolean moz_gtk_has_scrollbar_buttons(void)
+{
+ gboolean backward, forward, secondary_backward, secondary_forward;
+ MOZ_ASSERT(is_initialized, "Forgot to call moz_gtk_init()");
+ ensure_scrollbar_widget();
+ gtk_widget_style_get (gHorizScrollbarWidget,
+ "has-backward-stepper", &backward,
+ "has-forward-stepper", &forward,
+ "has-secondary-backward-stepper", &secondary_backward,
+ "has-secondary-forward-stepper", &secondary_forward,
+ NULL);
+ return backward | forward | secondary_forward | secondary_forward;
+}
+
+gint
+moz_gtk_shutdown()
+{
+ GtkWidgetClass *entry_class;
+
+ if (gTooltipWidget)
+ gtk_widget_destroy(gTooltipWidget);
+ /* This will destroy all of our widgets */
+ if (gProtoWindow)
+ gtk_widget_destroy(gProtoWindow);
+
+ gProtoWindow = NULL;
+ gProtoLayout = NULL;
+ gButtonWidget = NULL;
+ gToggleButtonWidget = NULL;
+ gButtonArrowWidget = NULL;
+ gCheckboxWidget = NULL;
+ gRadiobuttonWidget = NULL;
+ gHorizScrollbarWidget = NULL;
+ gVertScrollbarWidget = NULL;
+ gSpinWidget = NULL;
+ gHScaleWidget = NULL;
+ gVScaleWidget = NULL;
+ gEntryWidget = NULL;
+ gComboBoxWidget = NULL;
+ gComboBoxButtonWidget = NULL;
+ gComboBoxSeparatorWidget = NULL;
+ gComboBoxArrowWidget = NULL;
+ gComboBoxEntryWidget = NULL;
+ gComboBoxEntryButtonWidget = NULL;
+ gComboBoxEntryArrowWidget = NULL;
+ gComboBoxEntryTextareaWidget = NULL;
+ gHandleBoxWidget = NULL;
+ gToolbarWidget = NULL;
+ gStatusbarWidget = NULL;
+ gFrameWidget = NULL;
+ gProgressWidget = NULL;
+ gTabWidget = NULL;
+ gTooltipWidget = NULL;
+ gMenuBarWidget = NULL;
+ gMenuBarItemWidget = NULL;
+ gMenuPopupWidget = NULL;
+ gMenuItemWidget = NULL;
+ gImageMenuItemWidget = NULL;
+ gCheckMenuItemWidget = NULL;
+ gTreeViewWidget = NULL;
+ gMiddleTreeViewColumn = NULL;
+ gTreeHeaderCellWidget = NULL;
+ gTreeHeaderSortArrowWidget = NULL;
+ gExpanderWidget = NULL;
+ gToolbarSeparatorWidget = NULL;
+ gMenuSeparatorWidget = NULL;
+ gHPanedWidget = NULL;
+ gVPanedWidget = NULL;
+ gScrolledWindowWidget = NULL;
+
+ entry_class = g_type_class_peek(GTK_TYPE_ENTRY);
+ g_type_class_unref(entry_class);
+
+ is_initialized = FALSE;
+
+ return MOZ_GTK_SUCCESS;
+}
diff --git a/widget/gtk/gtk3drawing.cpp b/widget/gtk/gtk3drawing.cpp
new file mode 100644
index 000000000..fb95b4cc4
--- /dev/null
+++ b/widget/gtk/gtk3drawing.cpp
@@ -0,0 +1,2843 @@
+/* -*- 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/. */
+
+/*
+ * 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 "prinrval.h"
+#include "WidgetStyleCache.h"
+
+#include <math.h>
+
+static style_prop_t style_prop_func;
+static gboolean have_arrow_scaling;
+static gboolean checkbox_check_state;
+static gboolean notebook_has_tab_gap;
+static gboolean is_initialized;
+
+#define ARROW_UP 0
+#define ARROW_DOWN G_PI
+#define ARROW_RIGHT G_PI_2
+#define ARROW_LEFT (G_PI+G_PI_2)
+
+#if !GTK_CHECK_VERSION(3,14,0)
+#define GTK_STATE_FLAG_CHECKED (1 << 11)
+#endif
+
+static gint
+moz_gtk_get_tab_thickness(GtkStyleContext *style);
+
+// 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);
+ }
+
+ 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_enable_style_props(style_prop_t styleGetProp)
+{
+ style_prop_func = styleGetProp;
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_init()
+{
+ if (is_initialized)
+ return MOZ_GTK_SUCCESS;
+
+ is_initialized = TRUE;
+ have_arrow_scaling = (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 12));
+ 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;
+
+ if (gtk_check_version(3, 12, 0) == nullptr &&
+ gtk_check_version(3, 20, 0) != nullptr)
+ {
+ // Deprecated for Gtk >= 3.20+
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_TAB_TOP);
+ gtk_style_context_get_style(style,
+ "has-tab-gap", &notebook_has_tab_gap, NULL);
+ ReleaseStyleContext(style);
+ }
+ else {
+ notebook_has_tab_gap = true;
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_checkbox_get_metrics(gint* indicator_size, gint* indicator_spacing)
+{
+ gtk_widget_style_get(GetWidget(MOZ_GTK_CHECKBUTTON_CONTAINER),
+ "indicator_size", indicator_size,
+ "indicator_spacing", indicator_spacing,
+ NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing)
+{
+ gtk_widget_style_get(GetWidget(MOZ_GTK_RADIOBUTTON_CONTAINER),
+ "indicator_size", indicator_size,
+ "indicator_spacing", indicator_spacing,
+ NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_get_focus_outline_size(GtkStyleContext* style,
+ gint* focus_h_width, gint* focus_v_width)
+{
+ GtkBorder border;
+ GtkBorder padding;
+ gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border);
+ gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
+ *focus_h_width = border.left + padding.left;
+ *focus_v_width = border.top + padding.top;
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width)
+{
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_ENTRY);
+ moz_gtk_get_focus_outline_size(style, focus_h_width, focus_v_width);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding)
+{
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_MENUITEM);
+ gtk_style_context_get_style(style,
+ "horizontal-padding", horizontal_padding,
+ nullptr);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding)
+{
+ GtkStyleContext *style = ClaimStyleContext(MOZ_GTK_CHECKMENUITEM_CONTAINER);
+ gtk_style_context_get_style(style,
+ "horizontal-padding", horizontal_padding,
+ nullptr);
+ ReleaseStyleContext(style);
+ 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 = ClaimStyleContext(MOZ_GTK_BUTTON);
+ gtk_style_context_get_style(style,
+ "default-outside-border", &default_outside_border,
+ NULL);
+ ReleaseStyleContext(style);
+
+ 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 = ClaimStyleContext(MOZ_GTK_BUTTON);
+ gtk_style_context_get_style(style,
+ "default-border", &default_border,
+ NULL);
+ ReleaseStyleContext(style);
+
+ 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 = ClaimStyleContext(MOZ_GTK_SPLITTER_HORIZONTAL);
+ } else {
+ style = ClaimStyleContext(MOZ_GTK_SPLITTER_VERTICAL);
+ }
+ gtk_style_context_get_style(style, "handle_size", size, NULL);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_window_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(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);
+
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_button_paint(cairo_t *cr, 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);
+ 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_toggle_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean selected, gboolean inconsistent,
+ gboolean isradio, GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gint indicator_size, indicator_spacing;
+ gint x, y, width, height;
+ gint focus_x, focus_y, focus_width, focus_height;
+ GtkStyleContext *style;
+
+ GtkWidget *widget = GetWidget(isradio ? MOZ_GTK_RADIOBUTTON_CONTAINER :
+ MOZ_GTK_CHECKBUTTON_CONTAINER);
+ gtk_widget_style_get(widget,
+ "indicator_size", &indicator_size,
+ "indicator_spacing", &indicator_spacing,
+ nullptr);
+
+ // XXX we should assert rect->height >= indicator_size too
+ // after bug 369581 is fixed.
+ MOZ_ASSERT(rect->width >= indicator_size,
+ "GetMinimumWidgetSize was ignored");
+
+ // Paint it center aligned in the rect.
+ x = rect->x + (rect->width - indicator_size) / 2;
+ y = rect->y + (rect->height - indicator_size) / 2;
+ width = indicator_size;
+ height = indicator_size;
+
+ focus_x = x - indicator_spacing;
+ focus_y = y - indicator_spacing;
+ focus_width = width + 2 * indicator_spacing;
+ focus_height = height + 2 * indicator_spacing;
+
+ 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 = ClaimStyleContext(isradio ? MOZ_GTK_RADIOBUTTON :
+ MOZ_GTK_CHECKBUTTON,
+ 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);
+ }
+
+ if (isradio) {
+ gtk_render_option(style, cr, x, y, width, height);
+ if (state->focused) {
+ gtk_render_focus(style, cr, focus_x, focus_y,
+ focus_width, focus_height);
+ }
+ }
+ else {
+ gtk_render_check(style, cr, x, y, width, height);
+ if (state->focused) {
+ gtk_render_focus(style, cr,
+ focus_x, focus_y, focus_width, focus_height);
+ }
+ }
+
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+calculate_button_inner_rect(GtkWidget* button, 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_STATE_FLAG_NORMAL, &border);
+ gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &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);
+
+ if (have_arrow_scaling)
+ 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;
+}
+
+void
+moz_gtk_get_widget_min_size(WidgetNodeType aGtkWidgetType, int* width,
+ int* height)
+{
+ GtkStyleContext* style = ClaimStyleContext(aGtkWidgetType);
+ 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);
+ ReleaseStyleContext(style);
+
+ *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 void
+moz_gtk_rectangle_inset(GdkRectangle* rect, GtkBorder& aBorder)
+{
+ MOZ_ASSERT(rect);
+ rect->x += aBorder.left;
+ rect->y += aBorder.top;
+ rect->width -= aBorder.left + aBorder.right;
+ rect->height -= aBorder.top + aBorder.bottom;
+}
+
+/* Subtracting margin is used to inset drawing of element which can have margins,
+ * like scrollbar, scrollbar's trough, thumb and scrollbar's button */
+static void
+moz_gtk_subtract_margin(GtkStyleContext* style, GdkRectangle* rect)
+{
+ MOZ_ASSERT(rect);
+ GtkBorder margin;
+
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ moz_gtk_rectangle_inset(rect, margin);
+}
+
+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);
+ 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.
+ moz_gtk_subtract_margin(style, &rect);
+ } 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).
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+ if (flags & MOZ_GTK_STEPPER_VERTICAL) {
+ rect.x += metrics.trough_border;
+ rect.width = metrics.slider_width;
+ } else {
+ rect.y += metrics.trough_border;
+ rect.height = metrics.slider_width;
+ }
+ }
+
+ 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;
+ if (gtk_check_version(3, 6, 0) == nullptr) {
+ moz_gtk_subtract_margin(style, &rect);
+ }
+ 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* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(widget, direction);
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_scrollbar_paint(WidgetNodeType widget,
+ cairo_t *cr, const GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(widget, direction);
+ moz_gtk_update_scrollbar_style(style, widget, direction);
+
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+
+ ReleaseStyleContext(style);
+ style = ClaimStyleContext((widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) ?
+ MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL :
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL,
+ direction);
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+ ReleaseStyleContext(style);
+
+ 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);
+
+ GdkRectangle rect = *aRect;
+ GtkStyleContext* style = ClaimStyleContext(widget, direction, state_flags);
+ moz_gtk_subtract_margin(style, &rect);
+
+ gtk_render_slider(style, cr,
+ rect.x,
+ rect.y,
+ rect.width,
+ rect.height,
+ (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL) ?
+ GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
+
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_spin_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SPINBUTTON, 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);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_spin_updown_paint(cairo_t *cr, GdkRectangle* rect,
+ gboolean isDown, GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SPINBUTTON, 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);
+
+ ReleaseStyleContext(style);
+ 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 = ClaimStyleContext(widget, 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);
+
+ ReleaseStyleContext(style);
+ 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 = ClaimStyleContext(widget, direction, state_flags);
+ gtk_render_slider(style, cr, x, y, thumb_width, thumb_height, flags);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_gripper_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_GRIPPER, 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);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_hpaned_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state)
+{
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL,
+ GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_handle(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_vpaned_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state)
+{
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL,
+ GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_handle(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_entry_draw() for reference.
+static gint
+moz_gtk_entry_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkStyleContext* style)
+{
+ gint x = rect->x, y = rect->y, width = rect->width, height = rect->height;
+ int draw_focus_outline_only = state->depressed; // NS_THEME_FOCUS_OUTLINE
+
+ 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;
+ width = rect->width;
+ height = rect->height;
+ } else {
+ gtk_render_background(style, cr, x, y, width, height);
+ }
+ gtk_render_frame(style, cr, x, y, width, height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_text_view_paint(cairo_t *cr, GdkRectangle* rect,
+ 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 =
+ ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW, direction, state_flags);
+ gtk_render_frame(style_frame, cr, rect->x, rect->y, rect->width, rect->height);
+
+ GtkBorder border, padding;
+ gtk_style_context_get_border(style_frame, state_flags, &border);
+ gtk_style_context_get_padding(style_frame, state_flags, &padding);
+ ReleaseStyleContext(style_frame);
+
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_TEXT_VIEW, direction, state_flags);
+
+ gint xthickness = border.left + padding.left;
+ gint ythickness = border.top + padding.top;
+
+ gtk_render_background(style, cr,
+ rect->x + xthickness, rect->y + ythickness,
+ rect->width - 2 * xthickness,
+ rect->height - 2 * ythickness);
+
+ ReleaseStyleContext(style);
+
+ 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 = ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW, direction);
+ gtk_style_context_get_border(style, state_flags, &border);
+ xthickness = border.left;
+ ythickness = border.top;
+ ReleaseStyleContext(style);
+
+ style_tree = ClaimStyleContext(MOZ_GTK_TREEVIEW_VIEW, direction);
+ gtk_render_background(style_tree, cr,
+ rect->x + xthickness, rect->y + ythickness,
+ rect->width - 2 * xthickness,
+ rect->height - 2 * ythickness);
+ ReleaseStyleContext(style_tree);
+
+ style = ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW, direction);
+ gtk_render_frame(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tree_header_cell_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean isSorted, GtkTextDirection direction)
+{
+ moz_gtk_button_paint(cr, rect, 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 = ClaimStyleContext(MOZ_GTK_TREE_HEADER_SORTARROW, 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);
+ ReleaseStyleContext(style);
+ 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;
+
+ /* 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 = ClaimStyleContext(MOZ_GTK_TREEVIEW_EXPANDER,
+ direction, state_flags);
+ gtk_render_expander(style, cr,
+ rect->x,
+ rect->y,
+ rect->width,
+ rect->height);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_separator_draw() for reference.
+*/
+static gint
+moz_gtk_combo_box_paint(cairo_t *cr, GdkRectangle* rect,
+ 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, rect, state, GTK_RELIEF_NORMAL,
+ comboBoxButton, direction);
+
+ calculate_button_inner_rect(comboBoxButton, rect, &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);
+
+ 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 = ClaimStyleContext(MOZ_GTK_COMBOBOX_ARROW);
+ gtk_render_arrow(style, cr, ARROW_DOWN,
+ real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width);
+ ReleaseStyleContext(style);
+
+ /* 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);
+ 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 = ClaimStyleContext(MOZ_GTK_BUTTON_ARROW,
+ direction, state_flags);
+ gtk_render_arrow(style, cr, arrow_angle,
+ arrow_rect.x, arrow_rect.y, arrow_rect.width);
+ ReleaseStyleContext(style);
+ 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);
+ 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 = ClaimStyleContext(MOZ_GTK_COMBOBOX_ENTRY_ARROW);
+ gtk_render_arrow(style, cr, ARROW_DOWN,
+ real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width);
+ ReleaseStyleContext(style);
+ 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 = ClaimStyleContext(widget_type, 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);
+ }
+
+ ReleaseStyleContext(style);
+ 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 =
+ ClaimStyleContext(isradio ? MOZ_GTK_RADIOBUTTON_CONTAINER :
+ MOZ_GTK_CHECKBUTTON_CONTAINER,
+ direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_focus(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_toolbar_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TOOLBAR, 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);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See _gtk_toolbar_paint_space_line() for reference.
+*/
+static gint
+moz_gtk_toolbar_separator_paint(cairo_t *cr, GdkRectangle* rect,
+ 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 = ClaimStyleContext(MOZ_GTK_TOOLBAR);
+ gtk_style_context_get_style(style,
+ "wide-separators", &wide_separators,
+ "separator-width", &separator_width,
+ NULL);
+ ReleaseStyleContext(style);
+
+ style = ClaimStyleContext(MOZ_GTK_TOOLBAR_SEPARATOR, 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_STATE_FLAG_NORMAL, &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);
+ }
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_tooltip_paint(cairo_t *cr, const GdkRectangle* aRect,
+ 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 = ClaimStyleContext(MOZ_GTK_TOOLTIP, 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 =
+ CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0), style);
+
+ rect.x += 6;
+ rect.y += 6;
+ rect.width -= 12;
+ rect.height -= 12;
+
+ moz_gtk_subtract_margin(boxStyle, &rect);
+ 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
+ GtkBorder padding, border;
+ gtk_style_context_get_padding(boxStyle, GTK_STATE_FLAG_NORMAL, &padding);
+ moz_gtk_rectangle_inset(&rect, padding);
+ gtk_style_context_get_border(boxStyle, GTK_STATE_FLAG_NORMAL, &border);
+ moz_gtk_rectangle_inset(&rect, border);
+
+ GtkStyleContext* labelStyle =
+ CreateStyleForWidget(gtk_label_new(nullptr), boxStyle);
+ moz_gtk_draw_styled_frame(labelStyle, cr, &rect, false);
+ g_object_unref(labelStyle);
+
+ g_object_unref(boxStyle);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_resizer_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style;
+
+ // gtk_render_handle() draws a background, so use GtkTextView and its
+ // GTK_STYLE_CLASS_VIEW to match the background with textarea elements.
+ // The resizer is drawn with shaded variants of the background color, and
+ // so a transparent background would lead to a transparent resizer.
+ style = ClaimStyleContext(MOZ_GTK_TEXT_VIEW, GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+ // TODO - we need to save/restore style when gtk 3.20 CSS node path
+ // is used
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
+
+ // 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);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_frame_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_FRAME, direction);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_progressbar_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_PROGRESS_TROUGH,
+ 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);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_progress_chunk_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction,
+ WidgetNodeType widget)
+{
+ GtkStyleContext* style;
+
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ /* Ask for MOZ_GTK_PROGRESS_TROUGH instead of MOZ_GTK_PROGRESSBAR
+ * because ClaimStyleContext() saves/restores that style */
+ style = ClaimStyleContext(MOZ_GTK_PROGRESS_TROUGH, direction);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_TROUGH);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_PROGRESSBAR);
+ } else {
+ style = ClaimStyleContext(MOZ_GTK_PROGRESS_CHUNK, 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_activity was used to render progress chunks on GTK versions
+ // before 3.13.7, see bug 1173907.
+ if (!gtk_check_version(3, 13, 7)) {
+ 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);
+ } else {
+ gtk_render_activity(style, cr, rect->x, rect->y, rect->width, rect->height);
+ }
+ ReleaseStyleContext(style);
+
+ 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_STATE_FLAG_NORMAL, &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 = ClaimStyleContext(aNodeType);
+ int thickness = moz_gtk_get_tab_thickness(style);
+ ReleaseStyleContext(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 = ClaimStyleContext(widget, 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_paint_box_gap(), see comment in
+ * moz_gtk_tabpanels_paint(). This box_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;
+ }
+
+ 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);
+
+ gtk_style_context_remove_region(style, GTK_STYLE_REGION_TAB);
+
+ 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(style, 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(style, 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);
+
+ gtk_style_context_remove_region(style, GTK_STYLE_REGION_TAB);
+
+ 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(style, 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(style, 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);
+ }
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* tab area*/
+static gint
+moz_gtk_tabpanels_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TABPANELS, 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);
+
+ ReleaseStyleContext(style);
+ 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 = ClaimStyleContext(MOZ_GTK_TAB_SCROLLARROW, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_arrow(style, cr, arrow_angle,
+ x, y, arrow_size);
+ ReleaseStyleContext(style);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_menu_bar_paint(cairo_t *cr, GdkRectangle* rect,
+ 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);
+ 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,
+ 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);
+
+ 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,
+ GtkTextDirection direction)
+{
+ GtkStyleContext* style;
+ gboolean wide_separators;
+ gint separator_height;
+ gint x, y, w;
+ GtkBorder padding;
+
+ style = ClaimStyleContext(MOZ_GTK_MENUSEPARATOR, direction);
+ gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &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);
+ ReleaseStyleContext(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;
+
+ if (state->inHover && !state->disabled) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ ClaimStyleContext(widget, direction, state_flags);
+
+ bool pre_3_6 = gtk_check_version(3, 6, 0) != nullptr;
+ if (pre_3_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 (pre_3_6) {
+ gtk_style_context_restore(style);
+ }
+ ReleaseStyleContext(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 = ClaimStyleContext(MOZ_GTK_MENUITEM,
+ direction, state_flags);
+ gtk_render_arrow(style, cr,
+ (direction == GTK_TEXT_DIR_LTR) ? ARROW_RIGHT : ARROW_LEFT,
+ rect->x, rect->y, rect->width);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_real_check_menu_item_draw_indicator() for reference.
+static gint
+moz_gtk_check_menu_item_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean checked, gboolean isradio,
+ GtkTextDirection direction)
+{
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+ GtkBorder padding;
+ 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);
+ }
+
+ style = ClaimStyleContext(isradio ? MOZ_GTK_RADIOMENUITEM_CONTAINER :
+ MOZ_GTK_CHECKMENUITEM_CONTAINER,
+ direction);
+ gtk_style_context_get_style(style,
+ "indicator-size", &indicator_size,
+ "horizontal-padding", &horizontal_padding,
+ NULL);
+ ReleaseStyleContext(style);
+
+ style = ClaimStyleContext(isradio ? MOZ_GTK_RADIOMENUITEM :
+ MOZ_GTK_CHECKMENUITEM,
+ direction, state_flags);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ gint offset = padding.left + 2;
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ x = rect->width - indicator_size - offset - horizontal_padding;
+ }
+ else {
+ x = rect->x + offset + horizontal_padding;
+ }
+ y = rect->y + (rect->height - indicator_size) / 2;
+
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ gtk_render_background(style, cr, x, y, indicator_size, indicator_size);
+ gtk_render_frame(style, cr, x, y, indicator_size, indicator_size);
+ }
+
+ if (isradio) {
+ gtk_render_option(style, cr, x, y, indicator_size, indicator_size);
+ } else {
+ gtk_render_check(style, cr, x, y, indicator_size, indicator_size);
+ }
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint
+moz_gtk_info_bar_paint(cairo_t *cr, GdkRectangle* rect,
+ GtkWidgetState* state)
+{
+ GtkStyleContext *style =
+ ClaimStyleContext(MOZ_GTK_INFO_BAR, 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);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+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_STATE_FLAG_NORMAL, &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_STATE_FLAG_NORMAL, &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_STATE_FLAG_NORMAL, &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);
+}
+
+gint
+moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
+ gint* right, gint* bottom, GtkTextDirection direction,
+ gboolean inhtml)
+{
+ GtkWidget* w;
+ GtkStyleContext* style;
+ *left = *top = *right = *bottom = 0;
+
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON:
+ {
+ style = ClaimStyleContext(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);
+
+ // XXX: Subtract 1 pixel from the border to account for the added
+ // -moz-focus-inner border (Bug 1228281).
+ *left -= 1; *top -= 1; *right -= 1; *bottom -= 1;
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_ENTRY:
+ {
+ style = ClaimStyleContext(MOZ_GTK_ENTRY);
+
+ // 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_style_padding(style, left, top, right, bottom);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TEXT_VIEW:
+ case MOZ_GTK_TREEVIEW:
+ {
+ style = ClaimStyleContext(MOZ_GTK_SCROLLED_WINDOW);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ 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 = ClaimStyleContext(MOZ_GTK_TREE_HEADER_CELL);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ w = GetWidget(MOZ_GTK_TREE_HEADER_SORTARROW);
+ break;
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
+ 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 = ClaimStyleContext(MOZ_GTK_COMBOBOX_BUTTON);
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+
+ /* 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_STATE_FLAG_NORMAL,
+ &border);
+ separator_width = border.left;
+ }
+ }
+
+ gtk_widget_get_preferred_size(GetWidget(MOZ_GTK_COMBOBOX_ARROW),
+ NULL, &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_style_border(style,
+ left, top, right, bottom);
+ moz_gtk_add_style_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 || widget == MOZ_GTK_MENUITEM ?
+ MOZ_GTK_MENUITEM : MOZ_GTK_CHECKMENUITEM_CONTAINER;
+ style = ClaimStyleContext(type);
+
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_INFO_BAR:
+ w = GetWidget(MOZ_GTK_INFO_BAR);
+ break;
+ case MOZ_GTK_TOOLTIP:
+ {
+ style = ClaimStyleContext(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 =
+ CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
+ style);
+ moz_gtk_add_margin_border_padding(boxStyle,
+ left, top, right, bottom);
+
+ GtkStyleContext* labelStyle =
+ CreateStyleForWidget(gtk_label_new(nullptr), boxStyle);
+ moz_gtk_add_margin_border_padding(labelStyle,
+ left, top, right, bottom);
+
+ g_object_unref(labelStyle);
+ g_object_unref(boxStyle);
+
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ style = ClaimStyleContext(widget);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ if (widget == MOZ_GTK_SCROLLBAR_VERTICAL) {
+ style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ }
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+ /* Top and bottom border for whole vertical scrollbar, top and bottom
+ * border for horizontal track - to correctly position thumb element */
+ *top = *bottom = metrics.trough_border;
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ break;
+
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ style = ClaimStyleContext(widget);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) {
+ style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ }
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+ *left = *right = metrics.trough_border;
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ break;
+
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ style = ClaimStyleContext(widget);
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ ReleaseStyleContext(style);
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ break;
+ /* 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_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:
+ /* These widgets have no borders.*/
+ 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 = ClaimStyleContext(widget, 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_STATE_FLAG_NORMAL, &margin);
+ *left += margin.left;
+ *right += margin.right;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ ReleaseStyleContext(style);
+ style = ClaimStyleContext(MOZ_GTK_NOTEBOOK_HEADER, direction);
+ gtk_style_context_get_margin(style, GTK_STATE_FLAG_NORMAL, &margin);
+ *left += margin.left;
+ *right += margin.right;
+ }
+ }
+
+ ReleaseStyleContext(style);
+ 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);
+ *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 = ClaimStyleContext(MOZ_GTK_TABPANELS);
+ gtk_style_context_get_style(style,
+ "scroll-arrow-hlength", &arrow_size,
+ NULL);
+ ReleaseStyleContext(style);
+
+ *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);
+ *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 = ClaimStyleContext(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_STATE_FLAG_NORMAL, &border);
+ *size = MAX(*size, (wide_separators ? separator_width : border.left));
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_expander_size(gint* size)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_EXPANDER);
+ gtk_style_context_get_style(style,
+ "expander-size", size,
+ NULL);
+ ReleaseStyleContext(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_treeview_expander_size(gint* size)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_TREEVIEW);
+ gtk_style_context_get_style(style, "expander-size", size, NULL);
+ ReleaseStyleContext(style);
+ 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 = ClaimStyleContext(MOZ_GTK_MENUSEPARATOR);
+ gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &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);
+ ReleaseStyleContext(style);
+
+ *size = padding.top + padding.bottom;
+ *size += (wide_separators) ? separator_height : 1;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void
+moz_gtk_get_entry_min_height(gint* height)
+{
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_ENTRY);
+ if (!gtk_check_version(3, 20, 0)) {
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-height", height,
+ nullptr);
+ } else {
+ *height = 0;
+ }
+
+ GtkBorder border;
+ GtkBorder padding;
+ gtk_style_context_get_border(style, GTK_STATE_FLAG_NORMAL, &border);
+ gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
+
+ *height += (border.top + border.bottom + padding.top + padding.bottom);
+ ReleaseStyleContext(style);
+}
+
+void
+moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width,
+ gint* scale_height)
+{
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL) ?
+ MOZ_GTK_SCALE_HORIZONTAL :
+ MOZ_GTK_SCALE_VERTICAL;
+
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ gint thumb_length, thumb_height, trough_border;
+ moz_gtk_get_scalethumb_metrics(orient, &thumb_length, &thumb_height);
+
+ GtkStyleContext* style = ClaimStyleContext(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;
+ }
+ ReleaseStyleContext(style);
+ } else {
+ GtkStyleContext* style = ClaimStyleContext(widget);
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-width", scale_width,
+ "min-height", scale_height,
+ nullptr);
+ ReleaseStyleContext(style);
+ }
+}
+
+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 = ClaimStyleContext(widget);
+ gtk_style_context_get_style(style,
+ "slider_length", thumb_length,
+ "slider_width", thumb_height,
+ NULL);
+ ReleaseStyleContext(style);
+ } else {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL) ?
+ MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ MOZ_GTK_SCALE_THUMB_VERTICAL;
+ GtkStyleContext* style = ClaimStyleContext(widget);
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-width", thumb_length,
+ "min-height", thumb_height,
+ nullptr);
+ ReleaseStyleContext(style);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint
+moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics *metrics)
+{
+ // For Gtk >= 3.20 scrollbar metrics are ignored
+ MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr);
+
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL);
+ gtk_style_context_get_style(style,
+ "slider_width", &metrics->slider_width,
+ "trough_border", &metrics->trough_border,
+ "stepper_size", &metrics->stepper_size,
+ "stepper_spacing", &metrics->stepper_spacing,
+ "min-slider-length", &metrics->min_slider_size,
+ nullptr);
+ ReleaseStyleContext(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* 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);
+ break;
+ 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);
+ break;
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ return moz_gtk_scrollbar_button_paint(cr, rect, state,
+ (GtkScrollbarButtonFlags) flags,
+ direction);
+ break;
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ if (flags & MOZ_GTK_TRACK_OPAQUE) {
+ GtkStyleContext* style =
+ ClaimStyleContext(MOZ_GTK_WINDOW, direction);
+ gtk_render_background(style, cr,
+ rect->x, rect->y, rect->width, rect->height);
+ ReleaseStyleContext(style);
+ }
+ if (gtk_check_version(3,20,0) == nullptr) {
+ return moz_gtk_scrollbar_paint(widget, cr, rect, state, direction);
+ } else {
+ 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);
+ }
+ break;
+ 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);
+ break;
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ case MOZ_GTK_SCALE_VERTICAL:
+ return moz_gtk_scale_paint(cr, rect, state,
+ (GtkOrientation) flags, direction);
+ break;
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ return moz_gtk_scale_thumb_paint(cr, rect, state,
+ (GtkOrientation) flags, direction);
+ break;
+ case MOZ_GTK_SPINBUTTON:
+ return moz_gtk_spin_paint(cr, rect, direction);
+ break;
+ 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);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ {
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SPINBUTTON_ENTRY,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ gint ret = moz_gtk_entry_paint(cr, rect, state, style);
+ ReleaseStyleContext(style);
+ return ret;
+ }
+ break;
+ case MOZ_GTK_GRIPPER:
+ return moz_gtk_gripper_paint(cr, rect, state,
+ direction);
+ break;
+ case MOZ_GTK_TREEVIEW:
+ return moz_gtk_treeview_paint(cr, rect, state,
+ direction);
+ break;
+ case MOZ_GTK_TREE_HEADER_CELL:
+ return moz_gtk_tree_header_cell_paint(cr, rect, state,
+ flags, direction);
+ break;
+ case MOZ_GTK_TREE_HEADER_SORTARROW:
+ return moz_gtk_tree_header_sort_arrow_paint(cr, rect,
+ state,
+ (GtkArrowType) flags,
+ direction);
+ break;
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ return moz_gtk_treeview_expander_paint(cr, rect, state,
+ (GtkExpanderStyle) flags, direction);
+ break;
+ case MOZ_GTK_ENTRY:
+ {
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_ENTRY,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ gint ret = moz_gtk_entry_paint(cr, rect, state, style);
+ ReleaseStyleContext(style);
+ return ret;
+ }
+ case MOZ_GTK_TEXT_VIEW:
+ return moz_gtk_text_view_paint(cr, rect, state, direction);
+ break;
+ case MOZ_GTK_DROPDOWN:
+ return moz_gtk_combo_box_paint(cr, rect, state, direction);
+ break;
+ case MOZ_GTK_DROPDOWN_ARROW:
+ return moz_gtk_combo_box_entry_button_paint(cr, rect,
+ state, flags, direction);
+ break;
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ {
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ gint ret = moz_gtk_entry_paint(cr, rect, state, style);
+ ReleaseStyleContext(style);
+ return ret;
+ }
+ break;
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ return moz_gtk_container_paint(cr, rect, state, widget, direction);
+ break;
+ 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);
+ break;
+ case MOZ_GTK_TOOLBAR:
+ return moz_gtk_toolbar_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ return moz_gtk_toolbar_separator_paint(cr, rect,
+ direction);
+ break;
+ case MOZ_GTK_TOOLTIP:
+ return moz_gtk_tooltip_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_FRAME:
+ return moz_gtk_frame_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_RESIZER:
+ return moz_gtk_resizer_paint(cr, rect, state,
+ direction);
+ break;
+ case MOZ_GTK_PROGRESSBAR:
+ return moz_gtk_progressbar_paint(cr, rect, direction);
+ break;
+ 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,
+ direction, widget);
+ break;
+ case MOZ_GTK_TAB_TOP:
+ case MOZ_GTK_TAB_BOTTOM:
+ return moz_gtk_tab_paint(cr, rect, state,
+ (GtkTabFlags) flags, direction, widget);
+ break;
+ case MOZ_GTK_TABPANELS:
+ return moz_gtk_tabpanels_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_TAB_SCROLLARROW:
+ return moz_gtk_tab_scroll_arrow_paint(cr, rect, state,
+ (GtkArrowType) flags, direction);
+ break;
+ case MOZ_GTK_MENUBAR:
+ return moz_gtk_menu_bar_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_MENUPOPUP:
+ return moz_gtk_menu_popup_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_MENUSEPARATOR:
+ return moz_gtk_menu_separator_paint(cr, rect,
+ direction);
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ case MOZ_GTK_MENUITEM:
+ return moz_gtk_menu_item_paint(widget, cr, rect, state, direction);
+ break;
+ case MOZ_GTK_MENUARROW:
+ return moz_gtk_menu_arrow_paint(cr, rect, state,
+ direction);
+ break;
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ return moz_gtk_arrow_paint(cr, rect, state,
+ (GtkArrowType) flags, direction);
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM:
+ return moz_gtk_check_menu_item_paint(cr, rect, state,
+ (gboolean) flags,
+ (widget == MOZ_GTK_RADIOMENUITEM),
+ direction);
+ break;
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ return moz_gtk_vpaned_paint(cr, rect, state);
+ break;
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ return moz_gtk_hpaned_paint(cr, rect, state);
+ break;
+ case MOZ_GTK_WINDOW:
+ return moz_gtk_window_paint(cr, rect, direction);
+ break;
+ case MOZ_GTK_INFO_BAR:
+ return moz_gtk_info_bar_paint(cr, rect, state);
+ break;
+ default:
+ g_warning("Unknown widget type: %d", widget);
+ }
+
+ return MOZ_GTK_UNKNOWN_WIDGET;
+}
+
+GtkWidget* moz_gtk_get_scrollbar_widget(void)
+{
+ return GetWidget(MOZ_GTK_SCROLLBAR_HORIZONTAL);
+}
+
+gboolean moz_gtk_has_scrollbar_buttons(void)
+{
+ gboolean backward, forward, secondary_backward, secondary_forward;
+ MOZ_ASSERT(is_initialized, "Forgot to call moz_gtk_init()");
+ GtkStyleContext* style = ClaimStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL);
+ 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,
+ NULL);
+ ReleaseStyleContext(style);
+
+ return backward | forward | secondary_forward | secondary_forward;
+}
+
+gint
+moz_gtk_shutdown()
+{
+ /* This will destroy all of our widgets */
+ ResetWidgetCache();
+
+ is_initialized = FALSE;
+
+ return MOZ_GTK_SUCCESS;
+}
diff --git a/widget/gtk/gtkdrawing.h b/widget/gtk/gtkdrawing.h
new file mode 100644
index 000000000..9bbfdefe9
--- /dev/null
+++ b/widget/gtk/gtkdrawing.h
@@ -0,0 +1,542 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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>
+
+#if (MOZ_WIDGET_GTK == 2)
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#endif
+
+/*** type definitions ***/
+typedef struct {
+ guint8 active;
+ guint8 focused;
+ 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;
+ gint32 curpos; /* curpos and maxpos are used for scrollbars */
+ gint32 maxpos;
+} GtkWidgetState;
+
+typedef struct {
+ gint slider_width;
+ gint trough_border;
+ gint stepper_size;
+ gint stepper_spacing;
+ gint min_slider_size;
+} MozGtkScrollbarMetrics;
+
+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;
+
+/* function type for moz_gtk_enable_style_props */
+typedef gint (*style_prop_t)(GtkStyle*, const gchar*, gint);
+
+/*** 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 ***/
+typedef enum {
+ /* 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_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. */
+ MOZ_GTK_TEXT_VIEW,
+ /* 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 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 GtkWindow */
+ 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,
+ MOZ_GTK_IMAGEMENUITEM,
+ MOZ_GTK_CHECKMENUITEM_CONTAINER,
+ MOZ_GTK_RADIOMENUITEM_CONTAINER,
+ MOZ_GTK_CHECKMENUITEM,
+ MOZ_GTK_RADIOMENUITEM,
+ 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,
+ /* 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,
+
+ MOZ_GTK_WIDGET_NODE_COUNT
+} WidgetNodeType;
+
+/*** 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();
+
+/**
+ * Enable GTK+ 1.2.9+ theme enhancements. You must provide a pointer
+ * to the GTK+ 1.2.9+ function "gtk_style_get_prop_experimental".
+ * styleGetProp: pointer to gtk_style_get_prop_experimental
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_enable_style_props(style_prop_t styleGetProp);
+
+/**
+ * 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();
+
+#if (MOZ_WIDGET_GTK == 2)
+/**
+ * Retrieves the colormap to use for drawables passed to moz_gtk_widget_paint.
+ */
+GdkColormap* moz_gtk_widget_get_colormap();
+#endif
+
+/*** Widget drawing ***/
+#if (MOZ_WIDGET_GTK == 2)
+/**
+ * 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
+ * cliprect: a clipprect rectangle for this painting operation
+ * 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, GdkDrawable* drawable,
+ GdkRectangle* rect, GdkRectangle* cliprect,
+ GtkWidgetState* state, gint flags,
+ GtkTextDirection direction);
+#else
+gint
+moz_gtk_widget_paint(WidgetNodeType widget, cairo_t *cr,
+ GdkRectangle* rect,
+ GtkWidgetState* state, gint flags,
+ GtkTextDirection direction);
+#endif
+
+
+/*** 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
+ * inhtml: boolean indicating whether this widget will be drawn as a HTML form control,
+ * in order to workaround a size issue (MOZ_GTK_BUTTON only, ignored otherwise)
+ *
+ * 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,
+ gboolean inhtml);
+
+/**
+ * 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 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);
+
+/** Get the extra size for the focus ring for outline:auto.
+ * widget: [IN] the widget to get the focus metrics for
+ * 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 desired metrics for a GtkScrollbar
+ * metrics: [IN] struct which will contain the metrics
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint
+moz_gtk_get_scrollbar_metrics(MozGtkScrollbarMetrics* metrics);
+
+/**
+ * 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
+ * size: [OUT] the minimum height
+ *
+ */
+void moz_gtk_get_entry_min_height(gint* 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);
+
+/**
+ * Retrieve an actual GTK scrollbar widget for style analysis. It will not
+ * be modified.
+ */
+GtkWidget* moz_gtk_get_scrollbar_widget(void);
+
+/**
+ * Get the YTHICKNESS of a tab (notebook extension).
+ */
+gint
+moz_gtk_get_tab_thickness(WidgetNodeType aNodeType);
+
+/**
+ * Get a boolean which indicates whether the theme draws scrollbar buttons.
+ * If TRUE, draw scrollbar buttons.
+ */
+gboolean moz_gtk_has_scrollbar_buttons(void);
+
+/**
+ * Get minimum widget size as sum of margin, padding, border and min-width,
+ * min-height.
+ */
+void moz_gtk_get_widget_min_size(WidgetNodeType aGtkWidgetType, int* width,
+ int* height);
+
+#if (MOZ_WIDGET_GTK == 2)
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
+#endif
diff --git a/widget/gtk/maiRedundantObjectFactory.c b/widget/gtk/maiRedundantObjectFactory.c
new file mode 100644
index 000000000..3db26c8bb
--- /dev/null
+++ b/widget/gtk/maiRedundantObjectFactory.c
@@ -0,0 +1,93 @@
+/* -*- 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 000000000..809af23ce
--- /dev/null
+++ b/widget/gtk/maiRedundantObjectFactory.h
@@ -0,0 +1,32 @@
+/* -*- 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 000000000..baccb6ccd
--- /dev/null
+++ b/widget/gtk/moz.build
@@ -0,0 +1,141 @@
+# -*- 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/.
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3':
+ DIRS += ['mozgtk']
+
+EXPORTS += [
+ 'mozcontainer.h',
+ 'nsGTKToolkit.h',
+ 'nsIImageToPixbuf.h',
+]
+
+EXPORTS.mozilla += [
+ 'WidgetUtilsGtk.h'
+]
+
+UNIFIED_SOURCES += [
+ 'IMContextWrapper.cpp',
+ 'mozcontainer.c',
+ 'NativeKeyBindings.cpp',
+ 'nsAppShell.cpp',
+ 'nsBidiKeyboard.cpp',
+ 'nsColorPicker.cpp',
+ 'nsFilePicker.cpp',
+ 'nsGtkKeyUtils.cpp',
+ 'nsImageToPixbuf.cpp',
+ 'nsLookAndFeel.cpp',
+ 'nsNativeThemeGTK.cpp',
+ 'nsScreenGtk.cpp',
+ 'nsScreenManagerGtk.cpp',
+ 'nsSound.cpp',
+ 'nsToolkit.cpp',
+ 'nsWidgetFactory.cpp',
+ 'WakeLockListener.cpp',
+ 'WidgetTraceEvent.cpp',
+ 'WidgetUtilsGtk.cpp',
+]
+
+SOURCES += [
+ 'nsWindow.cpp', # conflicts with X11 headers
+]
+
+if CONFIG['MOZ_X11']:
+ UNIFIED_SOURCES += [
+ 'CompositorWidgetChild.cpp',
+ 'CompositorWidgetParent.cpp',
+ 'InProcessX11CompositorWidget.cpp',
+ 'nsIdleServiceGTK.cpp',
+ 'X11CompositorWidget.cpp',
+ ]
+ EXPORTS.mozilla.widget += [
+ 'CompositorWidgetChild.h',
+ 'CompositorWidgetParent.h',
+ 'InProcessX11CompositorWidget.h',
+ 'X11CompositorWidget.h',
+ ]
+
+if CONFIG['NS_PRINTING']:
+ UNIFIED_SOURCES += [
+ 'nsCUPSShim.cpp',
+ 'nsDeviceContextSpecG.cpp',
+ 'nsPaperPS.cpp',
+ 'nsPrintDialogGTK.cpp',
+ 'nsPrintOptionsGTK.cpp',
+ 'nsPrintSettingsGTK.cpp',
+ 'nsPSPrinters.cpp',
+ ]
+
+if CONFIG['MOZ_X11']:
+ UNIFIED_SOURCES += [
+ 'nsClipboard.cpp',
+ 'nsDragService.cpp',
+ 'WindowSurfaceProvider.cpp',
+ 'WindowSurfaceX11.cpp',
+ 'WindowSurfaceX11Image.cpp',
+ 'WindowSurfaceXRender.cpp',
+ ]
+ EXPORTS.mozilla.widget += [
+ 'WindowSurfaceProvider.h',
+ ]
+
+if CONFIG['ACCESSIBILITY']:
+ UNIFIED_SOURCES += [
+ 'maiRedundantObjectFactory.c',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2':
+ UNIFIED_SOURCES += [
+ 'gtk2drawing.c',
+ ]
+else:
+ UNIFIED_SOURCES += [
+ 'gtk3drawing.cpp',
+ 'nsApplicationChooser.cpp',
+ 'WidgetStyleCache.cpp',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/layout/generic',
+ '/layout/xul',
+ '/other-licenses/atk-1.0',
+ '/widget',
+]
+
+if CONFIG['MOZ_X11']:
+ LOCAL_INCLUDES += [
+ '/widget/x11',
+ ]
+
+DEFINES['CAIRO_GFX'] = True
+
+DEFINES['MOZ_APP_NAME'] = '"%s"' % CONFIG['MOZ_APP_NAME']
+
+CFLAGS += CONFIG['MOZ_STARTUP_NOTIFICATION_CFLAGS']
+
+# 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'] == 'gtk3' and CONFIG['MOZ_TREE_CAIRO']):
+ CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
+
+CXXFLAGS += CONFIG['MOZ_STARTUP_NOTIFICATION_CFLAGS']
+
+CFLAGS += CONFIG['TK_CFLAGS']
+CXXFLAGS += CONFIG['TK_CFLAGS']
+
+if CONFIG['MOZ_ENABLE_DBUS']:
+ CXXFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS']
+
+CXXFLAGS += ['-Wno-error=shadow']
diff --git a/widget/gtk/mozcontainer.c b/widget/gtk/mozcontainer.c
new file mode 100644
index 000000000..9b596e4fb
--- /dev/null
+++ b/widget/gtk/mozcontainer.c
@@ -0,0 +1,418 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozcontainer.h"
+#include <gtk/gtk.h>
+#include <stdio.h>
+
+#ifdef ACCESSIBILITY
+#include <atk/atk.h>
+#include "maiRedundantObjectFactory.h"
+#endif
+
+/* 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_realize (GtkWidget *widget);
+static void moz_container_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+
+/* 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 */
+ };
+
+ moz_container_type = g_type_register_static (GTK_TYPE_CONTAINER,
+ "MozContainer",
+ &moz_container_info, 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 = g_object_new (MOZ_CONTAINER_TYPE, NULL);
+
+ 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_move (MozContainer *container, GtkWidget *child_widget,
+ gint x, gint y, gint width, gint height)
+{
+ MozContainerChild *child;
+ GtkAllocation new_allocation;
+
+ child = moz_container_get_child (container, child_widget);
+
+ child->x = x;
+ child->y = y;
+
+ new_allocation.x = x;
+ new_allocation.y = y;
+ new_allocation.width = width;
+ new_allocation.height = height;
+
+ /* printf("moz_container_move %p %p will allocate to %d %d %d %d\n",
+ (void *)container, (void *)child_widget,
+ new_allocation.x, new_allocation.y,
+ new_allocation.width, new_allocation.height); */
+
+ gtk_widget_size_allocate(child_widget, &new_allocation);
+}
+
+/* static methods */
+
+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);
+}
+
+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.visual = gtk_widget_get_visual (widget);
+ attributes.window_type = GDK_WINDOW_CHILD;
+
+#if (MOZ_WIDGET_GTK == 2)
+ attributes.colormap = gtk_widget_get_colormap (widget);
+ attributes_mask |= GDK_WA_COLORMAP;
+#endif
+
+ window = gdk_window_new (parent, &attributes, attributes_mask);
+ gdk_window_set_user_data (window, widget);
+#if (MOZ_WIDGET_GTK == 2)
+ /* TODO GTK3? */
+ /* set the back pixmap to None so that you don't end up with the gtk
+ default which is BlackPixel */
+ gdk_window_set_back_pixmap (window, NULL, FALSE);
+#endif
+ } else {
+ window = parent;
+ g_object_ref (window);
+ }
+
+ gtk_widget_set_window (widget, window);
+
+#if (MOZ_WIDGET_GTK == 2)
+ widget->style = gtk_style_attach (widget->style, widget->window);
+#endif
+}
+
+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));
+
+ /* printf("moz_container_size_allocate %p %d %d %d %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 = 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 = 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 = 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);
+}
+
diff --git a/widget/gtk/mozcontainer.h b/widget/gtk/mozcontainer.h
new file mode 100644
index 000000000..23e17f7b3
--- /dev/null
+++ b/widget/gtk/mozcontainer.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __MOZ_CONTAINER_H__
+#define __MOZ_CONTAINER_H__
+
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * MozContainer
+ *
+ * This class serves two purposes in the nsIWidget implementation.
+ *
+ * - It provides objects to receive signals from GTK for events on native
+ * windows.
+ *
+ * - 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_CONAINTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MOZ_CONTAINER_TYPE, MozContainerClass))
+
+typedef struct _MozContainer MozContainer;
+typedef struct _MozContainerClass MozContainerClass;
+
+struct _MozContainer
+{
+ GtkContainer container;
+ GList *children;
+};
+
+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_move (MozContainer *container,
+ GtkWidget *child_widget,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __MOZ_CONTAINER_H__ */
diff --git a/widget/gtk/mozgtk/gtk2/moz.build b/widget/gtk/mozgtk/gtk2/moz.build
new file mode 100644
index 000000000..d07fd06df
--- /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 000000000..4e9379565
--- /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 000000000..528e2e9d0
--- /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 000000000..d9fb9385d
--- /dev/null
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -0,0 +1,634 @@
+#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_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_get)
+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_monitor_at_window)
+STUB(gdk_screen_get_monitor_geometry)
+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_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(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_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_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_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_type)
+STUB(gtk_image_menu_item_new)
+STUB(gtk_image_new)
+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_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_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_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_new)
+STUB(gtk_target_list_unref)
+STUB(gtk_targets_include_image)
+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_settings)
+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_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_window_deiconify)
+STUB(gtk_window_fullscreen)
+STUB(gtk_window_get_group)
+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_geometry_hints)
+STUB(gtk_window_set_icon_name)
+STUB(gtk_window_set_modal)
+STUB(gtk_window_set_skip_taskbar_hint)
+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(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_error_trap_pop_ignored)
+STUB(gdk_event_get_source_device)
+STUB(gdk_window_get_type)
+STUB(gdk_x11_window_get_xid)
+STUB(gdk_x11_display_get_type)
+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_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_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_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_tree_view_column_get_button)
+STUB(gtk_widget_get_preferred_size)
+STUB(gtk_widget_get_state_flags)
+STUB(gtk_widget_get_style_context)
+STUB(gtk_widget_path_append_for_widget)
+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_new)
+STUB(gtk_widget_path_unref)
+STUB(gtk_widget_set_visual)
+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 000000000..1a8e21001
--- /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/nsAppShell.cpp b/widget/gtk/nsAppShell.cpp
new file mode 100644
index 000000000..5473dd883
--- /dev/null
+++ b/widget/gtk/nsAppShell.cpp
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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/HangMonitor.h"
+#include "mozilla/Unused.h"
+#include "GeckoProfiler.h"
+#include "nsIPowerManagerService.h"
+#ifdef MOZ_ENABLE_DBUS
+#include "WakeLockListener.h"
+#endif
+
+using mozilla::Unused;
+
+#define NOTIFY_TOKEN 0xFA
+
+PRLogModuleInfo *gWidgetLog = nullptr;
+PRLogModuleInfo *gWidgetFocusLog = nullptr;
+PRLogModuleInfo *gWidgetDragLog = nullptr;
+PRLogModuleInfo *gWidgetDrawLog = nullptr;
+
+static GPollFunc sPollFunc;
+
+// Wrapper function to disable hang monitoring while waiting in poll().
+static gint
+PollWrapper(GPollFD *ufds, guint nfsd, gint timeout_)
+{
+ mozilla::HangMonitor::Suspend();
+ profiler_sleep_start();
+ gint result = (*sPollFunc)(ufds, nfsd, timeout_);
+ profiler_sleep_end();
+ mozilla::HangMonitor::NotifyActivity();
+ return result;
+}
+
+#if MOZ_WIDGET_GTK == 3
+// For bug 726483.
+static decltype(GtkContainerClass::check_resize) sReal_gtk_window_check_resize;
+
+static void
+wrap_gtk_window_check_resize(GtkContainer *container)
+{
+ GdkWindow* gdk_window = gtk_widget_get_window(&container->widget);
+ if (gdk_window) {
+ g_object_ref(gdk_window);
+ }
+
+ sReal_gtk_window_check_resize(container);
+
+ if (gdk_window) {
+ g_object_unref(gdk_window);
+ }
+}
+
+// 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);
+}
+#endif
+
+/*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()
+{
+ if (mTag)
+ g_source_remove(mTag);
+ if (mPipeFDs[0])
+ close(mPipeFDs[0]);
+ if (mPipeFDs[1])
+ close(mPipeFDs[1]);
+}
+
+nsresult
+nsAppShell::Init()
+{
+ // For any versions of Glib before 2.36, g_type_init must be explicitly called
+ // to safely use the library. Failure to do so may cause various failures/crashes
+ // in any code that uses Glib, Gdk, or Gtk. In later versions of Glib, this call
+ // is a no-op.
+ g_type_init();
+
+ if (!gWidgetLog)
+ gWidgetLog = PR_NewLogModule("Widget");
+ if (!gWidgetFocusLog)
+ gWidgetFocusLog = PR_NewLogModule("WidgetFocus");
+ if (!gWidgetDragLog)
+ gWidgetDragLog = PR_NewLogModule("WidgetDrag");
+ if (!gWidgetDrawLog)
+ gWidgetDrawLog = PR_NewLogModule("WidgetDraw");
+
+#ifdef MOZ_ENABLE_DBUS
+ 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 MOZ_WIDGET_GTK == 3
+ if (!sReal_gtk_window_check_resize &&
+ gtk_check_version(3,8,0) != nullptr) { // GTK 3.0 to GTK 3.6.
+ // GtkWindow is a static class and so will leak anyway but this ref
+ // makes sure it isn't recreated.
+ gpointer gtk_plug_class = g_type_class_ref(GTK_TYPE_WINDOW);
+ auto check_resize = &GTK_CONTAINER_CLASS(gtk_plug_class)->check_resize;
+ sReal_gtk_window_check_resize = *check_resize;
+ *check_resize = wrap_gtk_window_check_resize;
+ }
+
+ 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");
+#endif
+
+ 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)
+{
+ return g_main_context_iteration(nullptr, mayWait);
+}
diff --git a/widget/gtk/nsAppShell.h b/widget/gtk/nsAppShell.h
new file mode 100644
index 000000000..afdea5074
--- /dev/null
+++ b/widget/gtk/nsAppShell.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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();
+ virtual bool ProcessNextNativeEvent(bool mayWait);
+
+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 000000000..76c231cf6
--- /dev/null
+++ b/widget/gtk/nsApplicationChooser.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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()
+{
+}
+
+nsApplicationChooser::~nsApplicationChooser()
+{
+}
+
+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));
+ 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 000000000..da16dac71
--- /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 000000000..51102d945
--- /dev/null
+++ b/widget/gtk/nsBidiKeyboard.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prlink.h"
+
+#include "nsBidiKeyboard.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()
+{
+}
+
+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;
+}
diff --git a/widget/gtk/nsBidiKeyboard.h b/widget/gtk/nsBidiKeyboard.h
new file mode 100644
index 000000000..08c02f354
--- /dev/null
+++ b/widget/gtk/nsBidiKeyboard.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __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/nsCUPSShim.cpp b/widget/gtk/nsCUPSShim.cpp
new file mode 100644
index 000000000..d05da307f
--- /dev/null
+++ b/widget/gtk/nsCUPSShim.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=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 "nsDebug.h"
+#include "nsString.h"
+#include "nsCUPSShim.h"
+#include "mozilla/ArrayUtils.h"
+#include "prlink.h"
+
+
+// List of symbols to find in libcups. Must match symAddr[] defined in Init().
+// Making this an array of arrays instead of pointers allows storing the
+// whole thing in read-only memory.
+static const char gSymName[][sizeof("cupsPrintFile")] = {
+ { "cupsAddOption" },
+ { "cupsFreeDests" },
+ { "cupsGetDest" },
+ { "cupsGetDests" },
+ { "cupsPrintFile" },
+ { "cupsTempFd" },
+};
+static const int gSymNameCt = mozilla::ArrayLength(gSymName);
+
+
+bool
+nsCUPSShim::Init()
+{
+ mCupsLib = PR_LoadLibrary("libcups.so.2");
+ if (!mCupsLib)
+ return false;
+
+ // List of symbol pointers. Must match gSymName[] defined above.
+ void **symAddr[] = {
+ (void **)&mCupsAddOption,
+ (void **)&mCupsFreeDests,
+ (void **)&mCupsGetDest,
+ (void **)&mCupsGetDests,
+ (void **)&mCupsPrintFile,
+ (void **)&mCupsTempFd,
+ };
+
+ for (int i = gSymNameCt; i--; ) {
+ *(symAddr[i]) = PR_FindSymbol(mCupsLib, gSymName[i]);
+ if (! *(symAddr[i])) {
+#ifdef DEBUG
+ nsAutoCString msg(gSymName[i]);
+ msg.AppendLiteral(" not found in CUPS library");
+ NS_WARNING(msg.get());
+#endif
+ PR_UnloadLibrary(mCupsLib);
+ mCupsLib = nullptr;
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/widget/gtk/nsCUPSShim.h b/widget/gtk/nsCUPSShim.h
new file mode 100644
index 000000000..3e7d96f3a
--- /dev/null
+++ b/widget/gtk/nsCUPSShim.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=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 nsCUPSShim_h___
+#define nsCUPSShim_h___
+
+
+/* Various CUPS data types. We don't #include cups headers to avoid
+ * requiring CUPS to be installed on the build host (and to avoid having
+ * to test for CUPS in configure).
+ */
+typedef struct /**** Printer Options ****/
+{
+ char *name; /* Name of option */
+ char *value; /* Value of option */
+} cups_option_t;
+
+typedef struct /**** Destination ****/
+{
+ char *name, /* Printer or class name */
+ *instance; /* Local instance name or nullptr */
+ int is_default; /* Is this printer the default? */
+ int num_options; /* Number of options */
+ cups_option_t *options; /* Options */
+} cups_dest_t;
+
+typedef cups_dest_t* (*CupsGetDestType)(const char *printer,
+ const char *instance,
+ int num_dests,
+ cups_dest_t *dests);
+typedef int (*CupsGetDestsType)(cups_dest_t **dests);
+typedef int (*CupsFreeDestsType)(int num_dests,
+ cups_dest_t *dests);
+typedef int (*CupsPrintFileType)(const char *printer,
+ const char *filename,
+ const char *title,
+ int num_options,
+ cups_option_t *options);
+typedef int (*CupsTempFdType)(char *filename,
+ int length);
+typedef int (*CupsAddOptionType)(const char *name,
+ const char *value,
+ int num_options,
+ cups_option_t **options);
+
+struct PRLibrary;
+
+/* Note: this class relies on static initialization. */
+class nsCUPSShim {
+ public:
+ /**
+ * Initialize this object. Attempt to load the CUPS shared
+ * library and find function pointers for the supported
+ * functions (see below).
+ * @return false if the shared library could not be loaded, or if
+ * any of the functions could not be found.
+ * true for successful initialization.
+ */
+ bool Init();
+
+ /**
+ * @return true if the object was initialized successfully.
+ * false otherwise.
+ */
+ bool IsInitialized() { return nullptr != mCupsLib; }
+
+ /* Function pointers for supported functions. These are only
+ * valid after successful initialization.
+ */
+ CupsAddOptionType mCupsAddOption;
+ CupsFreeDestsType mCupsFreeDests;
+ CupsGetDestType mCupsGetDest;
+ CupsGetDestsType mCupsGetDests;
+ CupsPrintFileType mCupsPrintFile;
+ CupsTempFdType mCupsTempFd;
+
+ private:
+ PRLibrary *mCupsLib;
+};
+
+
+
+#endif /* nsCUPSShim_h___ */
diff --git a/widget/gtk/nsClipboard.cpp b/widget/gtk/nsClipboard.cpp
new file mode 100644
index 000000000..053ae970e
--- /dev/null
+++ b/widget/gtk/nsClipboard.cpp
@@ -0,0 +1,1046 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsXPIDLString.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsIServiceManager.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+
+#include "imgIContainer.h"
+
+#include <gtk/gtk.h>
+
+// For manipulation of the X event queue
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include "X11UndefineNone.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "nsIUnicodeDecoder.h"
+
+using mozilla::dom::EncodingUtils;
+using namespace mozilla;
+
+// 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 void
+ConvertHTMLtoUCS2 (guchar *data,
+ int32_t dataLength,
+ char16_t **unicodeData,
+ int32_t &outUnicodeLen);
+
+static void
+GetHTMLCharset (guchar * data, int32_t dataLength, nsCString& str);
+
+
+// Our own versions of gtk_clipboard_wait_for_contents and
+// gtk_clipboard_wait_for_text, which don't run the event loop while
+// waiting for the data. This prevents a lot of problems related to
+// dispatching events at unexpected times.
+
+static GtkSelectionData *
+wait_for_contents (GtkClipboard *clipboard, GdkAtom target);
+
+static gchar *
+wait_for_text (GtkClipboard *clipboard);
+
+static GdkFilterReturn
+selection_request_filter (GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data);
+
+nsClipboard::nsClipboard()
+{
+}
+
+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)
+
+nsresult
+nsClipboard::Init(void)
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os)
+ return NS_ERROR_FAILURE;
+
+ os->AddObserver(this, "quit-application", false);
+
+ // A custom event filter to workaround attempting to dereference a null
+ // selection requestor in GTK3 versions before 3.11.3. See bug 1178799.
+#if (MOZ_WIDGET_GTK == 3) && defined(MOZ_X11)
+ if (gtk_check_version(3, 11, 3))
+ gdk_window_add_filter(nullptr, selection_request_filter, nullptr);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+{
+ if (strcmp(aTopic, "quit-application") == 0) {
+ // application is going to quit, save clipboard content
+ Store();
+ gdk_window_remove_filter(nullptr, selection_request_filter, nullptr);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsClipboard::Store(void)
+{
+ // Ask the clipboard manager to store the current clipboard content
+ if (mGlobalTransferable) {
+ GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_store(clipboard);
+ }
+ return NS_OK;
+}
+
+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;
+ }
+
+ // Clear out the clipboard in order to set the new data
+ EmptyClipboard(aWhichClipboard);
+
+ // List of suported targets
+ GtkTargetList *list = gtk_target_list_new(nullptr, 0);
+
+ // Get the types of supported flavors
+ nsCOMPtr<nsIArray> flavors;
+
+ nsresult rv =
+ aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));
+ if (!flavors || NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ // Add all the flavors to this widget's supported type.
+ bool imagesAdded = false;
+ uint32_t count;
+ flavors->GetLength(&count);
+ for (uint32_t i=0; i < count; i++) {
+ nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavors, i);
+
+ if (flavor) {
+ nsXPIDLCString flavorStr;
+ flavor->ToString(getter_Copies(flavorStr));
+
+ // special case text/unicode since we can handle all of
+ // the string types
+ if (!strcmp(flavorStr, kUnicodeMime)) {
+ gtk_target_list_add(list, gdk_atom_intern("UTF8_STRING", FALSE), 0, 0);
+ gtk_target_list_add(list, gdk_atom_intern("COMPOUND_TEXT", FALSE), 0, 0);
+ gtk_target_list_add(list, gdk_atom_intern("TEXT", FALSE), 0, 0);
+ gtk_target_list_add(list, GDK_SELECTION_TYPE_STRING, 0, 0);
+ continue;
+ }
+
+ if (flavorStr.EqualsLiteral(kNativeImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ // don't bother adding image targets twice
+ if (!imagesAdded) {
+ // accept any writable image type
+ 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, 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);
+
+ // Set getcallback and request to store data after an application exit
+ if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
+ clipboard_get_cb, clipboard_clear_cb, this))
+ {
+ // 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 {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_table_free(gtkTargets, numTargets);
+ gtk_target_list_unref(list);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
+{
+ if (!aTransferable)
+ return NS_ERROR_FAILURE;
+
+ GtkClipboard *clipboard;
+ clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ guchar *data = nullptr;
+ gint length = 0;
+ bool foundData = false;
+ nsAutoCString foundFlavor;
+
+ // Get a list of flavors this transferable can import
+ nsCOMPtr<nsIArray> flavors;
+ nsresult rv;
+ rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors));
+ if (!flavors || NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t count;
+ flavors->GetLength(&count);
+ for (uint32_t i=0; i < count; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavors, i);
+
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // Special case text/unicode since we can convert any
+ // string into text/unicode
+ if (!strcmp(flavorStr, kUnicodeMime)) {
+ gchar* new_text = wait_for_text(clipboard);
+ if (new_text) {
+ // Convert utf-8 into our unicode format.
+ NS_ConvertUTF8toUTF16 ucs2string(new_text);
+ data = (guchar *)ToNewUnicode(ucs2string);
+ length = ucs2string.Length() * 2;
+ g_free(new_text);
+ foundData = true;
+ foundFlavor = kUnicodeMime;
+ break;
+ }
+ // If the type was text/unicode and we couldn't get
+ // text off the clipboard, run the next loop
+ // iteration.
+ continue;
+ }
+
+ // For images, we must wrap the data in an nsIInputStream then return instead of break,
+ // because that code below won't help us.
+ if (!strcmp(flavorStr, kJPEGImageMime) ||
+ !strcmp(flavorStr, kJPGImageMime) ||
+ !strcmp(flavorStr, kPNGImageMime) ||
+ !strcmp(flavorStr, kGIFImageMime)) {
+ // Emulate support for image/jpg
+ if (!strcmp(flavorStr, kJPGImageMime)) {
+ flavorStr.Assign(kJPEGImageMime);
+ }
+
+ GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
+
+ GtkSelectionData *selectionData = wait_for_contents(clipboard, atom);
+ if (!selectionData)
+ continue;
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream),
+ (const char*)gtk_selection_data_get_data(selectionData),
+ gtk_selection_data_get_length(selectionData),
+ NS_ASSIGNMENT_COPY);
+ aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
+ gtk_selection_data_free(selectionData);
+ return NS_OK;
+ }
+
+ // Get the atom for this type and try to request it off
+ // the clipboard.
+ GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
+ GtkSelectionData *selectionData;
+ selectionData = wait_for_contents(clipboard, atom);
+ if (selectionData) {
+ const guchar *clipboardData = gtk_selection_data_get_data(selectionData);
+ length = gtk_selection_data_get_length(selectionData);
+ // Special case text/html since we can convert into UCS2
+ if (!strcmp(flavorStr, kHTMLMime)) {
+ char16_t* htmlBody= nullptr;
+ int32_t htmlBodyLen = 0;
+ // Convert text/html into our unicode format
+ ConvertHTMLtoUCS2(const_cast<guchar*>(clipboardData), length,
+ &htmlBody, htmlBodyLen);
+ // Try next data format?
+ if (!htmlBodyLen)
+ continue;
+ data = (guchar *)htmlBody;
+ length = htmlBodyLen * 2;
+ } else {
+ data = (guchar *)moz_xmalloc(length);
+ if (!data)
+ break;
+ memcpy(data, clipboardData, length);
+ }
+ gtk_selection_data_free(selectionData);
+ foundData = true;
+ foundFlavor = flavorStr;
+ break;
+ }
+ }
+ }
+
+ if (foundData) {
+ nsCOMPtr<nsISupports> wrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor.get(),
+ data, length,
+ getter_AddRefs(wrapper));
+ aTransferable->SetTransferData(foundFlavor.get(),
+ wrapper, length);
+ }
+
+ if (data)
+ free(data);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(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;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
+ int32_t aWhichClipboard, bool *_retval)
+{
+ if (!aFlavorList || !_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = false;
+
+ GtkSelectionData *selection_data =
+ GetTargets(GetSelectionAtom(aWhichClipboard));
+ if (!selection_data)
+ return NS_OK;
+
+ gint n_targets = 0;
+ GdkAtom *targets = nullptr;
+
+ if (!gtk_selection_data_get_targets(selection_data,
+ &targets, &n_targets) ||
+ !n_targets)
+ return NS_OK;
+
+ // Walk through the provided types and try to match it to a
+ // provided type.
+ for (uint32_t i = 0; i < aLength && !*_retval; i++) {
+ // We special case text/unicode here.
+ if (!strcmp(aFlavorList[i], kUnicodeMime) &&
+ gtk_selection_data_targets_include_text(selection_data)) {
+ *_retval = true;
+ break;
+ }
+
+ for (int32_t j = 0; j < n_targets; j++) {
+ gchar *atom_name = gdk_atom_name(targets[j]);
+ if (!atom_name)
+ continue;
+
+ if (!strcmp(atom_name, aFlavorList[i]))
+ *_retval = true;
+
+ // X clipboard supports image/jpeg, but we want to emulate support
+ // for image/jpg as well
+ if (!strcmp(aFlavorList[i], kJPGImageMime) && !strcmp(atom_name, kJPEGImageMime))
+ *_retval = true;
+
+ g_free(atom_name);
+
+ if (*_retval)
+ break;
+ }
+ }
+ gtk_selection_data_free(selection_data);
+ g_free(targets);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool *_retval)
+{
+ *_retval = true; // yeah, unix supports the selection clipboard
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool* _retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+/* static */
+GdkAtom
+nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard == kGlobalClipboard)
+ return GDK_SELECTION_CLIPBOARD;
+
+ return GDK_SELECTION_PRIMARY;
+}
+
+/* static */
+GtkSelectionData *
+nsClipboard::GetTargets(GdkAtom aWhichClipboard)
+{
+ GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard);
+ return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE));
+}
+
+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
+
+ nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
+ if (!trans) {
+ // We have nothing to serve
+#ifdef DEBUG_CLIPBOARD
+ printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
+ whichClipboard == kSelectionClipboard ? "Selection" : "Global");
+#endif
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> item;
+ uint32_t len;
+
+ GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
+
+ // Check to see if the selection data includes any of the string
+ // types that we support.
+ if (selectionTarget == gdk_atom_intern ("STRING", FALSE) ||
+ selectionTarget == gdk_atom_intern ("TEXT", FALSE) ||
+ selectionTarget == gdk_atom_intern ("COMPOUND_TEXT", FALSE) ||
+ selectionTarget == gdk_atom_intern ("UTF8_STRING", FALSE)) {
+ // 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),
+ &len);
+ if (!item || NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString)
+ return;
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+ char *utf8string = ToNewUTF8String(ucs2string);
+ if (!utf8string)
+ return;
+
+ gtk_selection_data_set_text (aSelectionData, utf8string,
+ strlen(utf8string));
+
+ free(utf8string);
+ return;
+ }
+
+ // Check to see if the selection data is an image type
+ if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
+ // Look through our transfer data for the image
+ static const char* const imageMimeTypes[] = {
+ kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime };
+ nsCOMPtr<nsISupports> imageItem;
+ nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive;
+ for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) {
+ rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem), &len);
+ ptrPrimitive = do_QueryInterface(imageItem);
+ }
+ if (!ptrPrimitive)
+ return;
+
+ nsCOMPtr<nsISupports> primitiveData;
+ ptrPrimitive->GetData(getter_AddRefs(primitiveData));
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
+ if (!image) // Not getting an image for an image mime type!?
+ return;
+
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
+ if (!pixbuf)
+ return;
+
+ gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
+ g_object_unref(pixbuf);
+ return;
+ }
+
+ // Try to match up the selection data target to something our
+ // transferable provides.
+ gchar *target_name = gdk_atom_name(selectionTarget);
+ if (!target_name)
+ return;
+
+ rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len);
+ // nothing found?
+ if (!item || NS_FAILED(rv)) {
+ g_free(target_name);
+ return;
+ }
+
+ void *primitive_data = nullptr;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(target_name, item,
+ &primitive_data, len);
+
+ if (primitive_data) {
+ // Check to see if the selection data is text/html
+ if (selectionTarget == gdk_atom_intern (kHTMLMime, FALSE)) {
+ /*
+ * "text/html" can be encoded UCS2. It is recommended that
+ * documents transmitted as UCS2 always begin with a ZERO-WIDTH
+ * NON-BREAKING SPACE character (hexadecimal FEFF, also called
+ * Byte Order Mark (BOM)). Adding BOM can help other app to
+ * detect mozilla use UCS2 encoding when copy-paste.
+ */
+ guchar *buffer = (guchar *)
+ moz_xmalloc((len * sizeof(guchar)) + sizeof(char16_t));
+ if (!buffer)
+ return;
+ char16_t prefix = 0xFEFF;
+ memcpy(buffer, &prefix, sizeof(prefix));
+ memcpy(buffer + sizeof(prefix), primitive_data, len);
+ free((guchar *)primitive_data);
+ primitive_data = (guchar *)buffer;
+ len += sizeof(prefix);
+ }
+
+ gtk_selection_data_set(aSelectionData, selectionTarget,
+ 8, /* 8 bits in a unit */
+ (const guchar *)primitive_data, len);
+ free(primitive_data);
+ }
+
+ g_free(target_name);
+
+}
+
+void
+nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard)
+{
+ int32_t whichClipboard;
+
+ // which clipboard?
+ if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
+ whichClipboard = kSelectionClipboard;
+ else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
+ whichClipboard = kGlobalClipboard;
+ else
+ return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+
+ EmptyClipboard(whichClipboard);
+}
+
+void
+clipboard_get_cb(GtkClipboard *aGtkClipboard,
+ GtkSelectionData *aSelectionData,
+ guint info,
+ gpointer user_data)
+{
+ nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
+ aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
+}
+
+void
+clipboard_clear_cb(GtkClipboard *aGtkClipboard,
+ gpointer user_data)
+{
+ 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
+ */
+void ConvertHTMLtoUCS2(guchar * data, int32_t dataLength,
+ char16_t** unicodeData, int32_t& outUnicodeLen)
+{
+ nsAutoCString charset;
+ GetHTMLCharset(data, dataLength, charset);// get charset of HTML
+ if (charset.EqualsLiteral("UTF-16")) {//current mozilla
+ outUnicodeLen = (dataLength / 2) - 1;
+ *unicodeData = reinterpret_cast<char16_t*>
+ (moz_xmalloc((outUnicodeLen + sizeof('\0')) *
+ sizeof(char16_t)));
+ if (*unicodeData) {
+ memcpy(*unicodeData, data + sizeof(char16_t),
+ outUnicodeLen * sizeof(char16_t));
+ (*unicodeData)[outUnicodeLen] = '\0';
+ }
+ } else if (charset.EqualsLiteral("UNKNOWN")) {
+ outUnicodeLen = 0;
+ return;
+ } else {
+ // app which use "text/html" to copy&paste
+ nsCOMPtr<nsIUnicodeDecoder> decoder;
+ // get the decoder
+ nsAutoCString encoding;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(charset,
+ encoding)) {
+#ifdef DEBUG_CLIPBOARD
+ g_print(" get unicode decoder error\n");
+#endif
+ outUnicodeLen = 0;
+ return;
+ }
+ decoder = EncodingUtils::DecoderForEncoding(encoding);
+ // converting
+ nsresult rv = decoder->GetMaxLength((const char *)data, dataLength,
+ &outUnicodeLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ outUnicodeLen = 0;
+ return;
+ }
+
+ // |outUnicodeLen| is number of chars
+ if (outUnicodeLen) {
+ *unicodeData = reinterpret_cast<char16_t*>
+ (moz_xmalloc((outUnicodeLen + sizeof('\0')) *
+ sizeof(char16_t)));
+ if (*unicodeData) {
+ int32_t numberTmp = dataLength;
+ decoder->Convert((const char *)data, &numberTmp,
+ *unicodeData, &outUnicodeLen);
+#ifdef DEBUG_CLIPBOARD
+ if (numberTmp != dataLength)
+ printf("didn't consume all the bytes\n");
+#endif
+ // null terminate. Convert() doesn't do it for us
+ (*unicodeData)[outUnicodeLen] = '\0';
+ }
+ } // if valid length
+ }
+}
+
+/*
+ * 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
+ */
+void GetHTMLCharset(guchar * 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");
+ return;
+ }
+ // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
+ const nsDependentCString htmlStr((const char *)data, dataLength);
+ nsACString::const_iterator start, end;
+ htmlStr.BeginReading(start);
+ htmlStr.EndReading(end);
+ nsACString::const_iterator valueStart(start), valueEnd(start);
+
+ if (CaseInsensitiveFindInReadable(
+ NS_LITERAL_CSTRING("CONTENT=\"text/html;"),
+ start, end)) {
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (CaseInsensitiveFindInReadable(
+ NS_LITERAL_CSTRING("charset="),
+ 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);
+#ifdef DEBUG_CLIPBOARD
+ printf("Charset of HTML = %s\n", charsetUpperStr.get());
+#endif
+ return;
+ }
+ str.AssignLiteral("UNKNOWN");
+}
+
+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 True;
+ }
+ }
+ }
+
+ return False;
+}
+
+// Idle timeout for receiving selection and property notify events (microsec)
+static const int kClipboardTimeout = 500000;
+
+static gchar* CopyRetrievedData(const gchar *aData)
+{
+ return g_strdup(aData);
+}
+
+static GtkSelectionData* CopyRetrievedData(GtkSelectionData *aData)
+{
+ // A negative length indicates that retrieving the data failed.
+ return gtk_selection_data_get_length(aData) >= 0 ?
+ gtk_selection_data_copy(aData) : nullptr;
+}
+
+class RetrievalContext {
+ ~RetrievalContext()
+ {
+ MOZ_ASSERT(!mData, "Wait() wasn't called");
+ }
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(RetrievalContext)
+ enum State { INITIAL, COMPLETED, TIMED_OUT };
+
+ RetrievalContext() : mState(INITIAL), mData(nullptr) {}
+
+ /**
+ * Call this when data has been retrieved.
+ */
+ template <class T> void Complete(T *aData)
+ {
+ if (mState == INITIAL) {
+ mState = COMPLETED;
+ mData = CopyRetrievedData(aData);
+ } else {
+ // Already timed out
+ MOZ_ASSERT(mState == TIMED_OUT);
+ }
+ }
+
+ /**
+ * 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).
+ */
+ void *Wait();
+
+protected:
+ State mState;
+ void* mData;
+};
+
+void *
+RetrievalContext::Wait()
+{
+ if (mState == COMPLETED) { // the request completed synchronously
+ void *data = mData;
+ mData = nullptr;
+ return data;
+ }
+
+ GdkDisplay *gdkDisplay = gdk_display_get_default();
+ if (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 select_result;
+
+ int cnumber = ConnectionNumber(xDisplay);
+ fd_set select_set;
+ FD_ZERO(&select_set);
+ FD_SET(cnumber, &select_set);
+ ++cnumber;
+ 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) {
+ void *data = mData;
+ mData = nullptr;
+ return data;
+ }
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = std::max<int32_t>(0,
+ kClipboardTimeout - (now - start).ToMicroseconds());
+ select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
+ } while (select_result == 1 ||
+ (select_result == -1 && errno == EINTR));
+ }
+#ifdef DEBUG_CLIPBOARD
+ printf("exceeded clipboard timeout\n");
+#endif
+ mState = TIMED_OUT;
+ return nullptr;
+}
+
+static void
+clipboard_contents_received(GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ RetrievalContext *context = static_cast<RetrievalContext*>(data);
+ context->Complete(selection_data);
+ context->Release();
+}
+
+static GtkSelectionData *
+wait_for_contents(GtkClipboard *clipboard, GdkAtom target)
+{
+ RefPtr<RetrievalContext> context = new RetrievalContext();
+ // Balanced by Release in clipboard_contents_received
+ context.get()->AddRef();
+ gtk_clipboard_request_contents(clipboard, target,
+ clipboard_contents_received,
+ context.get());
+ return static_cast<GtkSelectionData*>(context->Wait());
+}
+
+static void
+clipboard_text_received(GtkClipboard *clipboard,
+ const gchar *text,
+ gpointer data)
+{
+ RetrievalContext *context = static_cast<RetrievalContext*>(data);
+ context->Complete(text);
+ context->Release();
+}
+
+static gchar *
+wait_for_text(GtkClipboard *clipboard)
+{
+ RefPtr<RetrievalContext> context = new RetrievalContext();
+ // Balanced by Release in clipboard_text_received
+ context.get()->AddRef();
+ gtk_clipboard_request_text(clipboard, clipboard_text_received, context.get());
+ return static_cast<gchar*>(context->Wait());
+}
+
+static GdkFilterReturn
+selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
+{
+ XEvent *xevent = static_cast<XEvent*>(gdk_xevent);
+ if (xevent->xany.type == SelectionRequest) {
+ if (xevent->xselectionrequest.requestor == X11None)
+ return GDK_FILTER_REMOVE;
+
+ GdkDisplay *display = gdk_x11_lookup_xdisplay(
+ xevent->xselectionrequest.display);
+ if (!display)
+ return GDK_FILTER_REMOVE;
+
+ GdkWindow *window = gdk_x11_window_foreign_new_for_display(display,
+ xevent->xselectionrequest.requestor);
+ if (!window)
+ return GDK_FILTER_REMOVE;
+
+ g_object_unref(window);
+ }
+ return GDK_FILTER_CONTINUE;
+}
diff --git a/widget/gtk/nsClipboard.h b/widget/gtk/nsClipboard.h
new file mode 100644
index 000000000..70c866a01
--- /dev/null
+++ b/widget/gtk/nsClipboard.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsClipboard_h_
+#define __nsClipboard_h_
+
+#include "nsIClipboard.h"
+#include "nsIObserver.h"
+#include <gtk/gtk.h>
+
+class nsClipboard : public nsIClipboard,
+ public nsIObserver
+{
+public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSICLIPBOARD
+ NS_DECL_NSIOBSERVER
+
+ // 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();
+
+ // Utility methods
+ static GdkAtom GetSelectionAtom (int32_t aWhichClipboard);
+ static GtkSelectionData *GetTargets (GdkAtom aWhichClipboard);
+
+ // Save global clipboard content to gtk
+ nsresult Store (void);
+
+ // Get our hands on the correct transferable, given a specific
+ // clipboard
+ nsITransferable *GetTransferable (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;
+
+};
+
+#endif /* __nsClipboard_h_ */
diff --git a/widget/gtk/nsColorPicker.cpp b/widget/gtk/nsColorPicker.cpp
new file mode 100644
index 000000000..93ab8bb9a
--- /dev/null
+++ b/widget/gtk/nsColorPicker.cpp
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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) && GTK_CHECK_VERSION(3,4,0)
+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;
+
+ nsXPIDLCString title;
+ title.Adopt(ToNewUTF8String(mTitle));
+ GtkWindow *parent_window = GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER) && GTK_CHECK_VERSION(3,4,0)
+ GtkWidget* color_chooser = gtk_color_chooser_dialog_new(title, parent_window);
+
+ if (parent_window) {
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(color_chooser), 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);
+
+ 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);
+ }
+
+ 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) && GTK_CHECK_VERSION(3,4,0)
+/* 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) && GTK_CHECK_VERSION(3,4,0)
+ 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 000000000..107e6f058
--- /dev/null
+++ b/widget/gtk/nsColorPicker.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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() {};
+
+private:
+ ~nsColorPicker() {};
+
+ 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) && GTK_CHECK_VERSION(3,4,0)
+ 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 000000000..0bd87bd85
--- /dev/null
+++ b/widget/gtk/nsDeviceContextSpecG.cpp
@@ -0,0 +1,498 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "plstr.h"
+#include "prenv.h" /* for PR_GetEnv */
+
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsStringEnumerator.h"
+#include "nsIServiceManager.h"
+#include "nsThreadUtils.h"
+
+#include "nsPSPrinters.h"
+#include "nsPaperPS.h" /* Paper size list */
+
+#include "nsPrintSettingsGTK.h"
+
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Preferences.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+using namespace mozilla;
+
+using mozilla::gfx::IntSize;
+using mozilla::gfx::PrintTarget;
+using mozilla::gfx::PrintTargetPDF;
+using mozilla::gfx::PrintTargetPS;
+
+static PRLogModuleInfo *
+GetDeviceContextSpecGTKLog()
+{
+ static PRLogModuleInfo *sLog;
+ if (!sLog)
+ sLog = PR_NewLogModule("DeviceContextSpecGTK");
+ return sLog;
+}
+/* Macro to make lines shorter */
+#define DO_PR_DEBUG_LOG(x) MOZ_LOG(GetDeviceContextSpecGTKLog(), mozilla::LogLevel::Debug, x)
+
+//----------------------------------------------------------------------------------
+// The printer data is shared between the PrinterEnumerator and the nsDeviceContextSpecGTK
+// The PrinterEnumerator creates the printer info
+// but the nsDeviceContextSpecGTK cleans it up
+// If it gets created (via the Page Setup Dialog) but the user never prints anything
+// then it will never be delete, so this class takes care of that.
+class GlobalPrinters {
+public:
+ static GlobalPrinters* GetInstance() { return &mGlobalPrinters; }
+ ~GlobalPrinters() { FreeGlobalPrinters(); }
+
+ void FreeGlobalPrinters();
+ nsresult InitializeGlobalPrinters();
+
+ bool PrintersAreAllocated() { return mGlobalPrinterList != nullptr; }
+ uint32_t GetNumPrinters()
+ { return mGlobalPrinterList ? mGlobalPrinterList->Length() : 0; }
+ nsString* GetStringAt(int32_t aInx) { return &mGlobalPrinterList->ElementAt(aInx); }
+ void GetDefaultPrinterName(char16_t **aDefaultPrinterName);
+
+protected:
+ GlobalPrinters() {}
+
+ static GlobalPrinters mGlobalPrinters;
+ static nsTArray<nsString>* mGlobalPrinterList;
+};
+
+//---------------
+// static members
+GlobalPrinters GlobalPrinters::mGlobalPrinters;
+nsTArray<nsString>* GlobalPrinters::mGlobalPrinterList = nullptr;
+//---------------
+
+nsDeviceContextSpecGTK::nsDeviceContextSpecGTK()
+ : mGtkPrintSettings(nullptr)
+ , mGtkPageSetup(nullptr)
+{
+ DO_PR_DEBUG_LOG(("nsDeviceContextSpecGTK::nsDeviceContextSpecGTK()\n"));
+}
+
+nsDeviceContextSpecGTK::~nsDeviceContextSpecGTK()
+{
+ DO_PR_DEBUG_LOG(("nsDeviceContextSpecGTK::~nsDeviceContextSpecGTK()\n"));
+
+ if (mGtkPageSetup) {
+ g_object_unref(mGtkPageSetup);
+ }
+
+ if (mGtkPrintSettings) {
+ g_object_unref(mGtkPrintSettings);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecGTK,
+ nsIDeviceContextSpec)
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecGTK::MakePrintTarget()
+{
+ double width, height;
+ mPrintSettings->GetEffectivePageSize(&width, &height);
+
+ // convert twips to points
+ width /= TWIPS_PER_POINT_FLOAT;
+ height /= TWIPS_PER_POINT_FLOAT;
+
+ DO_PR_DEBUG_LOG(("\"%s\", %f, %f\n", mPath, width, height));
+ 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);
+ 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);
+
+ // Determine the real format with some GTK magic
+ if (format == nsIPrintSettings::kOutputFormatNative) {
+ if (mIsPPreview) {
+ // There is nothing to detect on Print Preview, use PS.
+ format = nsIPrintSettings::kOutputFormatPS;
+ } else {
+ return nullptr;
+ }
+ }
+
+ IntSize size = IntSize::Truncate(width, height);
+
+ if (format == nsIPrintSettings::kOutputFormatPDF) {
+ return PrintTargetPDF::CreateOrNull(stream, size);
+ }
+
+ int32_t orientation;
+ mPrintSettings->GetOrientation(&orientation);
+ return PrintTargetPS::CreateOrNull(stream,
+ size,
+ orientation == nsIPrintSettings::kPortraitOrientation
+ ? PrintTargetPS::PORTRAIT
+ : PrintTargetPS::LANDSCAPE);
+}
+
+/** -------------------------------------------------------
+ * Initialize the nsDeviceContextSpecGTK
+ * @update dc 2/15/98
+ * @update syd 3/2/99
+ */
+NS_IMETHODIMP nsDeviceContextSpecGTK::Init(nsIWidget *aWidget,
+ nsIPrintSettings* aPS,
+ bool aIsPrintPreview)
+{
+ DO_PR_DEBUG_LOG(("nsDeviceContextSpecGTK::Init(aPS=%p)\n", aPS));
+
+ 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;
+
+ mIsPPreview = aIsPrintPreview;
+
+ // 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);
+
+ 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
+#if (MOZ_WIDGET_GTK == 3)
+print_callback(GtkPrintJob *aJob, gpointer aData, const GError *aError) {
+#else
+print_callback(GtkPrintJob *aJob, gpointer aData, GError *aError) {
+#endif
+ g_object_unref(aJob);
+ ((nsIFile*) aData)->Remove(false);
+}
+
+static void
+ns_release_macro(gpointer aData) {
+ nsIFile* spoolFile = (nsIFile*) aData;
+ NS_RELEASE(spoolFile);
+}
+
+/* static */
+gboolean nsDeviceContextSpecGTK::PrinterEnumerator(GtkPrinter *aPrinter,
+ gpointer aData) {
+ nsDeviceContextSpecGTK *spec = (nsDeviceContextSpecGTK*)aData;
+
+ // Find the printer whose name matches the one inside the settings.
+ nsXPIDLString printerName;
+ nsresult rv =
+ spec->mPrintSettings->GetPrinterName(getter_Copies(printerName));
+ if (NS_SUCCEEDED(rv) && printerName) {
+ 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(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;
+
+ NS_ADDREF(mSpoolFile.get());
+ gtk_print_job_send(job, print_callback, mSpoolFile, ns_release_macro);
+}
+
+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)
+{
+ mTitle.Truncate();
+ AppendUTF16toUTF8(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.
+
+ GtkPrinter* printer = mPrintSettings->GetGtkPrinter();
+ if (printer) {
+ // 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(this, &nsDeviceContextSpecGTK::EnumeratePrinters));
+ }
+ } else {
+ // Handle print-to-file ourselves for the benefit of embedders
+ nsXPIDLString targetPath;
+ nsCOMPtr<nsIFile> destFile;
+ mPrintSettings->GetToFileName(getter_Copies(targetPath));
+
+ nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(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);
+
+ // 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));
+ }
+ return NS_OK;
+}
+
+// Printer Enumerator
+nsPrinterEnumeratorGTK::nsPrinterEnumeratorGTK()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsPrinterEnumeratorGTK, nsIPrinterEnumerator)
+
+NS_IMETHODIMP nsPrinterEnumeratorGTK::GetPrinterNameList(nsIStringEnumerator **aPrinterNameList)
+{
+ NS_ENSURE_ARG_POINTER(aPrinterNameList);
+ *aPrinterNameList = nullptr;
+
+ nsresult rv = GlobalPrinters::GetInstance()->InitializeGlobalPrinters();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint32_t numPrinters = GlobalPrinters::GetInstance()->GetNumPrinters();
+ nsTArray<nsString> *printers = new nsTArray<nsString>(numPrinters);
+ if (!printers) {
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t count = 0;
+ while( count < numPrinters )
+ {
+ printers->AppendElement(*GlobalPrinters::GetInstance()->GetStringAt(count++));
+ }
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+
+ return NS_NewAdoptingStringEnumerator(aPrinterNameList, printers);
+}
+
+NS_IMETHODIMP nsPrinterEnumeratorGTK::GetDefaultPrinterName(char16_t **aDefaultPrinterName)
+{
+ DO_PR_DEBUG_LOG(("nsPrinterEnumeratorGTK::GetDefaultPrinterName()\n"));
+ NS_ENSURE_ARG_POINTER(aDefaultPrinterName);
+
+ GlobalPrinters::GetInstance()->GetDefaultPrinterName(aDefaultPrinterName);
+
+ DO_PR_DEBUG_LOG(("GetDefaultPrinterName(): default printer='%s'.\n", NS_ConvertUTF16toUTF8(*aDefaultPrinterName).get()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrinterEnumeratorGTK::InitPrintSettingsFromPrinter(const char16_t *aPrinterName, nsIPrintSettings *aPrintSettings)
+{
+ DO_PR_DEBUG_LOG(("nsPrinterEnumeratorGTK::InitPrintSettingsFromPrinter()"));
+
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ /* Set filename */
+ nsAutoCString filename;
+ const char *path;
+
+ if (!(path = PR_GetEnv("PWD")))
+ path = PR_GetEnv("HOME");
+
+ if (path)
+ filename = nsPrintfCString("%s/mozilla.pdf", path);
+ else
+ filename.AssignLiteral("mozilla.pdf");
+
+ DO_PR_DEBUG_LOG(("Setting default filename to '%s'\n", filename.get()));
+ aPrintSettings->SetToFileName(NS_ConvertUTF8toUTF16(filename).get());
+
+ aPrintSettings->SetIsInitializedFromPrinter(true);
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+nsresult GlobalPrinters::InitializeGlobalPrinters ()
+{
+ if (PrintersAreAllocated()) {
+ return NS_OK;
+ }
+
+ mGlobalPrinterList = new nsTArray<nsString>();
+
+ nsPSPrinterList psMgr;
+ if (psMgr.Enabled()) {
+ /* Get the list of PostScript-module printers */
+ // XXX: this function is the only user of GetPrinterList
+ // So it may be interesting to convert the nsCStrings
+ // in this function, we would save one loop here
+ nsTArray<nsCString> printerList;
+ psMgr.GetPrinterList(printerList);
+ for (uint32_t i = 0; i < printerList.Length(); i++)
+ {
+ mGlobalPrinterList->AppendElement(NS_ConvertUTF8toUTF16(printerList[i]));
+ }
+ }
+
+ /* If there are no printers available after all checks, return an error */
+ if (!mGlobalPrinterList->Length())
+ {
+ /* Make sure we do not cache an empty printer list */
+ FreeGlobalPrinters();
+
+ return NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+void GlobalPrinters::FreeGlobalPrinters()
+{
+ if (mGlobalPrinterList) {
+ delete mGlobalPrinterList;
+ mGlobalPrinterList = nullptr;
+ }
+}
+
+void
+GlobalPrinters::GetDefaultPrinterName(char16_t **aDefaultPrinterName)
+{
+ *aDefaultPrinterName = nullptr;
+
+ bool allocate = !GlobalPrinters::GetInstance()->PrintersAreAllocated();
+
+ if (allocate) {
+ nsresult rv = GlobalPrinters::GetInstance()->InitializeGlobalPrinters();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ NS_ASSERTION(GlobalPrinters::GetInstance()->PrintersAreAllocated(), "no GlobalPrinters");
+
+ if (GlobalPrinters::GetInstance()->GetNumPrinters() == 0)
+ return;
+
+ *aDefaultPrinterName = ToNewUnicode(*GlobalPrinters::GetInstance()->GetStringAt(0));
+
+ if (allocate) {
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+ }
+}
+
diff --git a/widget/gtk/nsDeviceContextSpecG.h b/widget/gtk/nsDeviceContextSpecG.h
new file mode 100644
index 000000000..921c6275c
--- /dev/null
+++ b/widget/gtk/nsDeviceContextSpecG.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 nsDeviceContextSpecGTK_h___
+#define nsDeviceContextSpecGTK_h___
+
+#include "nsIDeviceContextSpec.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrinterEnumerator.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
+
+ virtual 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 */
+ bool mIsPPreview : 1; /* If true, is print preview */
+ char mPath[PATH_MAX]; /* If toPrinter = false, dest file */
+ char mPrinter[256]; /* Printer name */
+ GtkPrintSettings* mGtkPrintSettings;
+ GtkPageSetup* mGtkPageSetup;
+
+ nsCString mSpoolName;
+ nsCOMPtr<nsIFile> mSpoolFile;
+ nsCString mTitle;
+
+private:
+ void EnumeratePrinters();
+ void StartPrintJob();
+ static gboolean PrinterEnumerator(GtkPrinter *aPrinter, gpointer aData);
+};
+
+//-------------------------------------------------------------------------
+// Printer Enumerator
+//-------------------------------------------------------------------------
+class nsPrinterEnumeratorGTK final : public nsIPrinterEnumerator
+{
+ ~nsPrinterEnumeratorGTK() {}
+public:
+ nsPrinterEnumeratorGTK();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTERENUMERATOR
+};
+
+#endif /* !nsDeviceContextSpecGTK_h___ */
diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp
new file mode 100644
index 000000000..15b4eeffa
--- /dev/null
+++ b/widget/gtk/nsDragService.cpp
@@ -0,0 +1,2109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 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 "nsIServiceManager.h"
+#include "nsXPCOM.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 "gfxXlibSurface.h"
+#include "gfxContext.h"
+#include "nsImageToPixbuf.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsISelection.h"
+#include "nsViewManager.h"
+#include "nsIFrame.h"
+#include "nsGtkUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatform.h"
+#include "nsScreenGtk.h"
+#include "nsArrayUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// 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
+};
+
+static PRLogModuleInfo *sDragLm = nullptr;
+
+// 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 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)
+{
+ // 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
+#if (MOZ_WIDGET_GTK == 2)
+ mHiddenWidget = gtk_window_new(GTK_WINDOW_POPUP);
+#else
+ // Using an offscreen window works around bug 983843.
+ mHiddenWidget = gtk_offscreen_window_new();
+#endif
+ // 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
+ if (!sDragLm)
+ sDragLm = PR_NewLogModule("nsDragService");
+ 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)
+
+/* static */ nsDragService*
+nsDragService::GetInstance()
+{
+ static const nsIID iid = NS_DRAGSERVICE_CID;
+ nsCOMPtr<nsIDragService> dragService = do_GetService(iid);
+ return static_cast<nsDragService*>(dragService.get());
+ // We rely on XPCOM keeping a reference to the service.
+}
+
+// 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 {
+ NS_NOTREACHED("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 = nsScreenGtk::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(nsIDOMDocument *aDocument)
+{
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
+ if (!doc)
+ return nullptr;
+
+ nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
+ 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(nsIDOMNode *aDOMNode,
+ nsIArray * aArrayTransferables,
+ nsIScriptableRegion * aRegion,
+ 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, aArrayTransferables,
+ aRegion, aActionType,
+ aContentPolicyType);
+}
+
+// nsBaseDragService
+nsresult
+nsDragService::InvokeDragSessionImpl(nsIArray* aArrayTransferables,
+ nsIScriptableRegion* 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;
+
+ // stored temporarily until the drag-begin signal has been received
+ mSourceRegion = aRegion;
+
+ // 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));
+
+#if (MOZ_WIDGET_GTK == 3)
+ // 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);
+#endif
+
+ // start our drag.
+ GdkDragContext *context = gtk_drag_begin(mHiddenWidget,
+ sourceList,
+ action,
+ 1,
+ &event);
+
+ mSourceRegion = nullptr;
+
+ 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;
+
+#if (MOZ_WIDGET_GTK == 2)
+ GdkColormap* alphaColormap = gdk_screen_get_rgba_colormap(screen);
+ if (!alphaColormap)
+ return false;
+
+ GdkPixmap* pixmap = gdk_pixmap_new(nullptr, dragRect.width, dragRect.height,
+ gdk_colormap_get_visual(alphaColormap)->depth);
+ if (!pixmap)
+ return false;
+
+ gdk_drawable_set_colormap(GDK_DRAWABLE(pixmap), alphaColormap);
+
+ // Make a DrawTarget wrapped around the pixmap to render on
+ RefPtr<DrawTarget> dt =
+ nsWindow::GetDrawTargetForGdkDrawable(GDK_DRAWABLE(pixmap),
+ IntSize(dragRect.width,
+ dragRect.height));
+ if (!dt)
+ return false;
+
+ // Clear it...
+ dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height));
+
+ // ...and paint the drag image with translucency
+ 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));
+
+ // The drag transaction addrefs the pixmap, so we can just unref it from us here
+ gtk_drag_set_icon_pixmap(aContext, alphaColormap, pixmap, nullptr,
+ aXOffset, aYOffset);
+ g_object_unref(pixmap);
+ return true;
+#else
+#ifdef cairo_image_surface_create
+#error "Looks like we're including Mozilla's cairo instead of system cairo"
+#endif
+ // Prior to GTK 3.9.12, cairo surfaces passed into gtk_drag_set_icon_surface
+ // had their shape information derived from the alpha channel and used with
+ // the X SHAPE extension instead of being displayed as an ARGB window.
+ // See bug 1249604.
+ if (gtk_check_version(3, 9, 12))
+ return false;
+
+ // 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 = nsScreenGtk::GetGtkMonitorScaleFactor();
+ sCairoSurfaceSetDeviceScalePtr(surf, scale, scale);
+ }
+
+ gtk_drag_set_icon_surface(aContext, surf);
+ cairo_surface_destroy(surf);
+ return true;
+#endif
+}
+
+NS_IMETHODIMP
+nsDragService::StartDragSession()
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::StartDragSession"));
+ return nsBaseDragService::StartDragSession();
+}
+
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag)
+{
+ 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;
+
+ return nsBaseDragService::EndDragSession(aDoneDrag);
+}
+
+// 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). Flavors are nsISupportsStrings
+ // so that they can be seen from JS.
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(
+ getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // count the number of flavors
+ uint32_t cnt;
+ flavorList->GetLength(&cnt);
+ unsigned int i;
+
+ // 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 (i = 0; i < cnt; ++i) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ MOZ_LOG(sDragLm,
+ LogLevel::Debug,
+ ("flavor is %s\n", (const char *)flavorStr));
+ // get the item with the right index
+ nsCOMPtr<nsITransferable> item =
+ do_QueryElementAt(mSourceDataItems, aItemIndex);
+ if (!item)
+ continue;
+
+ nsCOMPtr<nsISupports> data;
+ uint32_t tmpDataLen = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("trying to get transfer data for %s\n",
+ (const char *)flavorStr));
+ rv = item->GetTransferData(flavorStr,
+ getter_AddRefs(data),
+ &tmpDataLen);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("failed.\n"));
+ continue;
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("succeeded.\n"));
+ rv = aTransferable->SetTransferData(flavorStr,data,tmpDataLen);
+ 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 ( i = 0; i < cnt; ++i ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, i);
+ if (currentFlavor) {
+ // find our gtk flavor
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ GdkAtom gdkFlavor = gdk_atom_intern(flavorStr, FALSE);
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("looking for data in type %s, gdk flavor %ld\n",
+ static_cast<const char*>(flavorStr), 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 ( strcmp(flavorStr, kFileMime) == 0 ) {
+ 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, file,
+ convertedTextLen);
+ 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 ( strcmp(flavorStr, kUnicodeMime) == 0 ) {
+ 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);
+ 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 (strcmp(flavorStr, kURLMime) == 0) {
+ 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 (strcmp(flavorStr, kCustomTypesMime) != 0) {
+ // 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,
+ genericDataWrapper,
+ mTargetDragDataLen);
+ // we found one, get out of this loop!
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound and converted!\n"));
+ break;
+ }
+ } // if (currentFlavor)
+ } // foreach flavor
+
+ 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) {
+ nsCOMPtr <nsIArray> flavorList;
+ currItem->FlavorsTransferableCanExport(
+ getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t numFlavors;
+ flavorList->GetLength( &numFlavors );
+ for ( uint32_t flavorIndex = 0;
+ flavorIndex < numFlavors ;
+ ++flavorIndex ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("checking %s against %s\n",
+ (const char *)flavorStr, aDataFlavor));
+ if (strcmp(flavorStr, aDataFlavor) == 0) {
+ 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;
+ for (tmp = gdk_drag_context_list_targets(mTargetDragContext);
+ 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);
+ }
+ 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);
+}
+
+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);
+ if (len > 0 && data) {
+ mTargetDragDataLen = len;
+ mTargetDragData = g_malloc(mTargetDragDataLen);
+ memcpy(mTargetDragData, data, mTargetDragDataLen);
+ }
+ else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Failed to get data. selection data len was %d\n",
+ mTargetDragDataLen));
+ }
+}
+
+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 (gtk_drag_get_source_widget(mTargetDragContext) == nullptr)
+ return retval;
+
+ GList *tmp;
+
+ // walk the list of context targets and see if one of them is a list
+ // of items.
+ for (tmp = gdk_drag_context_list_targets(mTargetDragContext);
+ 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;
+ }
+ 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 %d\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();
+ 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();
+ }
+ 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) {
+ nsCOMPtr <nsIArray> flavorList;
+ currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t numFlavors;
+ flavorList->GetLength( &numFlavors );
+ for (uint32_t flavorIndex = 0;
+ flavorIndex < numFlavors ;
+ ++flavorIndex ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // check if text/x-moz-url is supported.
+ // If so, advertise
+ // text/uri-list.
+ if (strcmp(flavorStr, kURLMime) == 0) {
+ 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);
+ }
+ }
+ } // foreach flavor in item
+ } // if valid flavor list
+ } // if item is a transferable
+ } else if (numDragItems == 1) {
+ nsCOMPtr<nsITransferable> currItem =
+ do_QueryElementAt(mSourceDataItems, 0);
+ if (currItem) {
+ nsCOMPtr <nsIArray> flavorList;
+ currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t numFlavors;
+ flavorList->GetLength( &numFlavors );
+ for (uint32_t flavorIndex = 0;
+ flavorIndex < numFlavors ;
+ ++flavorIndex ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ GtkTargetEntry *target =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ target->target = g_strdup(flavorStr);
+ 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 (strcmp(flavorStr, kFileMime) == 0) {
+ 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 (strcmp(flavorStr, kUnicodeMime) == 0) {
+ 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 (strcmp(flavorStr, kURLMime) == 0) {
+ 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);
+ }
+ }
+ } // foreach flavor in item
+ } // if valid flavor list
+ } // if item is a transferable
+ } // if it is a single item drag
+
+ // 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);
+ }
+ return targetList;
+}
+
+void
+nsDragService::SourceEndDragSession(GdkDragContext *aContext,
+ gint aResult)
+{
+ // this just releases the list of data items that we provide
+ mSourceDataItems = nullptr;
+
+ 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 = nsScreenGtk::GetGtkMonitorScaleFactor();
+ gdk_display_get_pointer(display, nullptr, &x, &y, nullptr);
+ SetDragEndPoint(LayoutDeviceIntPoint(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;
+
+ if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET) {
+ mUserCancelled = true;
+ }
+ }
+
+ if (mDataTransfer) {
+ mDataTransfer->SetDropEffectInt(dropEffect);
+ }
+
+ // Schedule the appropriate drag end dom events.
+ Schedule(eDragTaskSourceEnd, nullptr, nullptr, LayoutDeviceIntPoint(), 0);
+}
+
+static void
+CreateUriList(nsIArray *items, gchar **text, gint *length)
+{
+ uint32_t i, count;
+ GString *uriList = g_string_new(nullptr);
+
+ items->GetLength(&count);
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsITransferable> item;
+ item = do_QueryElementAt(items, i);
+
+ if (item) {
+ uint32_t tmpDataLen = 0;
+ void *tmpData = nullptr;
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(kURLMime,
+ getter_AddRefs(data),
+ &tmpDataLen);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsPrimitiveHelpers::CreateDataFromPrimitive(kURLMime,
+ data,
+ &tmpData,
+ tmpDataLen);
+ char* plainTextData = nullptr;
+ char16_t* castedUnicode = reinterpret_cast<char16_t*>
+ (tmpData);
+ uint32_t plainTextLen = 0;
+ UTF16ToNewUTF8(castedUnicode,
+ tmpDataLen / 2,
+ &plainTextData,
+ &plainTextLen);
+ if (plainTextData) {
+ uint32_t j;
+
+ // text/x-moz-url is of form url + "\n" + title.
+ // We just want the url.
+ for (j = 0; j < plainTextLen; j++)
+ if (plainTextData[j] == '\n' ||
+ plainTextData[j] == '\r') {
+ plainTextData[j] = '\0';
+ break;
+ }
+ g_string_append(uriList, plainTextData);
+ g_string_append(uriList, "\r\n");
+ // this wasn't allocated with glib
+ free(plainTextData);
+ }
+ if (tmpData) {
+ // this wasn't allocated with glib
+ free(tmpData);
+ }
+ } else {
+ // There is no uri available. If there is a file available,
+ // create a uri from the file.
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(kFileMime,
+ getter_AddRefs(data),
+ &tmpDataLen);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(data);
+ if (!file) {
+ // Sometimes the file is wrapped in a
+ // nsISupportsInterfacePointer. See bug 1310193 for
+ // removing this distinction.
+ nsCOMPtr<nsISupportsInterfacePointer> ptr =
+ do_QueryInterface(data);
+ if (ptr) {
+ ptr->GetData(getter_AddRefs(data));
+ file = do_QueryInterface(data);
+ }
+ }
+
+ if (file) {
+ nsCOMPtr<nsIURI> fileURI;
+ NS_NewFileURI(getter_AddRefs(fileURI), file);
+ if (fileURI) {
+ nsAutoCString uristring;
+ fileURI->GetSpec(uristring);
+ g_string_append(uriList, uristring.get());
+ g_string_append(uriList, "\r\n");
+ }
+ }
+ }
+ }
+ }
+ }
+ *text = uriList->str;
+ *length = uriList->len + 1;
+ g_string_free(uriList, FALSE); // don't free the data
+}
+
+
+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);
+ nsXPIDLCString mimeFlavor;
+ gchar *typeName = 0;
+ 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));
+ // make a copy since |nsXPIDLCString| won't use |g_free|...
+ mimeFlavor.Adopt(strdup(typeName));
+ 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;
+ }
+
+ 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 = mimeFlavor;
+ if (strcmp(mimeFlavor, kTextMime) == 0 ||
+ strcmp(mimeFlavor, gTextPlainUTF8Type) == 0) {
+ 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 (strcmp(mimeFlavor, gMozUrlType) == 0) {
+ actualFlavor = kURLMime;
+ needToDoConversionToPlainText = true;
+ }
+ // if someone was asking for text/uri-list we need to convert to
+ // plain text.
+ else if (strcmp(mimeFlavor, gTextUriListType) == 0) {
+ actualFlavor = gTextUriListType;
+ needToDoConversionToPlainText = true;
+ }
+ else
+ actualFlavor = mimeFlavor;
+
+ uint32_t tmpDataLen = 0;
+ void *tmpData = nullptr;
+ nsresult rv;
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(actualFlavor,
+ getter_AddRefs(data),
+ &tmpDataLen);
+ if (NS_SUCCEEDED(rv)) {
+ nsPrimitiveHelpers::CreateDataFromPrimitive (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 (strcmp(mimeFlavor, gTextUriListType) == 0) {
+ // fall back for text/uri-list
+ gchar *uriList;
+ gint length;
+ CreateUriList(mSourceDataItems, &uriList, &length);
+ gtk_selection_data_set(aSelectionData, target,
+ 8, (guchar *)uriList, length);
+ g_free(uriList);
+ return;
+ }
+ }
+ }
+}
+
+void nsDragService::SetDragIcon(GdkDragContext* aContext)
+{
+ if (!mHasImage && !mSelection)
+ return;
+
+ LayoutDeviceIntRect dragRect;
+ nsPresContext* pc;
+ RefPtr<SourceSurface> surface;
+ DrawDrag(mSourceNode, mSourceRegion, 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->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,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime)
+{
+ if (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,
+ 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, LayoutDeviceIntPoint(), 0)) {
+ NS_WARNING("Drag leave after drop");
+ }
+}
+
+gboolean
+nsDragService::ScheduleDropEvent(nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime)
+{
+ if (!Schedule(eDragTaskDrop, aWindow,
+ aDragContext, 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,
+ 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;
+ 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);
+ }
+ }
+
+ // 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 = mPendingWindow.forget();
+ mTargetWindowPoint = mPendingWindowPoint;
+
+ if (task == eDragTaskLeave || task == eDragTaskSourceEnd) {
+ if (task == eDragTaskSourceEnd) {
+ // Dispatch drag end events.
+ EndDragSession(true);
+ }
+
+ // 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.steal(mPendingDragContext);
+ mTargetTime = mPendingTime;
+
+ // 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;
+ } else {
+ // Reply to tell the source whether we can drop and what
+ // action would be taken.
+ ReplyToDragMotion(mTargetDragContext);
+ }
+ }
+ }
+
+ 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.
+ 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);
+ }
+
+ // We're done with the drag context.
+ mTargetWidget = nullptr;
+ mTargetDragContext = nullptr;
+
+ // 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_drag_context_get_actions(mTargetDragContext);
+
+ // 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;
+ }
+ return NS_OK;
+}
+
+void
+nsDragService::DispatchMotionEvents()
+{
+ mCanDrop = false;
+
+ FireDragEventAtSource(eDrag);
+
+ 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;
+}
diff --git a/widget/gtk/nsDragService.h b/widget/gtk/nsDragService.h
new file mode 100644
index 000000000..90c113106
--- /dev/null
+++ b/widget/gtk/nsDragService.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 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 "nsAutoRef.h"
+#include <gtk/gtk.h>
+
+class nsWindow;
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+}
+}
+
+#ifndef HAVE_NSGOBJECTREFTRAITS
+#define HAVE_NSGOBJECTREFTRAITS
+template <class T>
+class nsGObjectRefTraits : public nsPointerRefTraits<T> {
+public:
+ static void Release(T *aPtr) { g_object_unref(aPtr); }
+ static void AddRef(T *aPtr) { g_object_ref(aPtr); }
+};
+#endif
+
+#ifndef HAVE_NSAUTOREFTRAITS_GTKWIDGET
+#define HAVE_NSAUTOREFTRAITS_GTKWIDGET
+template <>
+class nsAutoRefTraits<GtkWidget> : public nsGObjectRefTraits<GtkWidget> { };
+#endif
+
+#ifndef HAVE_NSAUTOREFTRAITS_GDKDRAGCONTEXT
+#define HAVE_NSAUTOREFTRAITS_GDKDRAGCONTEXT
+template <>
+class nsAutoRefTraits<GdkDragContext> :
+ public nsGObjectRefTraits<GdkDragContext> { };
+#endif
+
+/**
+ * Native GTK DragService wrapper
+ */
+
+class nsDragService final : public nsBaseDragService,
+ public nsIObserver
+{
+public:
+ nsDragService();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIOBSERVER
+
+ // nsBaseDragService
+ virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType) override;
+ // nsIDragService
+ NS_IMETHOD InvokeDragSession (nsIDOMNode *aDOMNode,
+ nsIArray * anArrayTransferables,
+ nsIScriptableRegion * aRegion,
+ uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType) override;
+ NS_IMETHOD StartDragSession() override;
+ NS_IMETHOD EndDragSession(bool aDoneDrag) 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 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,
+ mozilla::LayoutDeviceIntPoint aWindowPoint,
+ guint aTime);
+ void ScheduleLeaveEvent();
+ gboolean ScheduleDropEvent(nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ 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);
+
+ // 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;
+ nsCountedRef<GdkDragContext> mPendingDragContext;
+ 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.
+ nsCountedRef<GtkWidget> mTargetWidget;
+ nsCountedRef<GdkDragContext> mTargetDragContext;
+ // mTargetDragContextForRemote is set while waiting for a reply from
+ // a child process.
+ nsCountedRef<GdkDragContext> mTargetDragContextForRemote;
+ 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;
+
+ nsCOMPtr<nsIScriptableRegion> mSourceRegion;
+
+ // 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,
+ mozilla::LayoutDeviceIntPoint aWindowPoint, guint aTime);
+
+ // Callback for g_idle_add_full() to run mScheduledTask.
+ static gboolean TaskDispatchCallback(gpointer data);
+ gboolean RunScheduledTask();
+ void UpdateDragAction();
+ void DispatchMotionEvents();
+ void ReplyToDragMotion(GdkDragContext* aDragContext);
+ gboolean DispatchDropEvent();
+};
+
+#endif // nsDragService_h__
+
diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
new file mode 100644
index 000000000..172cb4444
--- /dev/null
+++ b/widget/gtk/nsFilePicker.cpp
@@ -0,0 +1,610 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+
+#include "nsGtkUtils.h"
+#include "nsIFileURL.h"
+#include "nsIURI.h"
+#include "nsIWidget.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsMemory.h"
+#include "nsEnumeratorUtils.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "mozcontainer.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)
+#if (MOZ_WIDGET_GTK == 3)
+ , mFileChooserDelegate(nullptr)
+#endif
+{
+}
+
+nsFilePicker::~nsFilePicker()
+{
+}
+
+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(GtkWidget *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);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+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;
+
+ nsXPIDLCString title;
+ title.Adopt(ToNewUTF8String(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 = (action == GTK_FILE_CHOOSER_ACTION_SAVE) ?
+ GTK_STOCK_SAVE : GTK_STOCK_OPEN;
+ }
+
+ GtkWidget *file_chooser =
+ gtk_file_chooser_dialog_new(title, parent_widget, action,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ accept_button, GTK_RESPONSE_ACCEPT,
+ nullptr);
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(file_chooser),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL,
+ -1);
+ if (mAllowURLs) {
+ gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser), FALSE);
+ }
+
+ 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);
+ }
+
+ GtkWindow *window = GTK_WINDOW(file_chooser);
+ gtk_window_set_modal(window, TRUE);
+ if (parent_widget) {
+ gtk_window_set_destroy_with_parent(window, 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);
+
+#if (MOZ_WIDGET_GTK == 3)
+ // 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
+ 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)
+ g_object_ref(mFileChooserDelegate);
+#endif
+
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser),
+ directory.get());
+ }
+ }
+
+ 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);
+ g_signal_connect(file_chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(file_chooser);
+
+ return NS_OK;
+}
+
+/* static */ void
+nsFilePicker::OnResponse(GtkWidget* 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(GtkWidget* 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.
+ gtk_widget_destroy(file_chooser);
+
+#if (MOZ_WIDGET_GTK == 3)
+ 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;
+ }
+#endif
+
+ if (mCallback) {
+ mCallback->Done(result);
+ mCallback = nullptr;
+ } else {
+ mResult = result;
+ }
+ NS_RELEASE_THIS();
+}
diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h
new file mode 100644
index 000000000..2b5042098
--- /dev/null
+++ b/widget/gtk/nsFilePicker.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 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;
+ NS_IMETHOD Show(int16_t *aReturn) override;
+
+ // nsBaseFilePicker
+ virtual void InitNative(nsIWidget *aParent,
+ const nsAString& aTitle) override;
+
+ static void Shutdown();
+
+protected:
+ virtual ~nsFilePicker();
+
+ void ReadValuesFromFileChooser(GtkWidget *file_chooser);
+
+ static void OnResponse(GtkWidget* dialog, gint response_id,
+ gpointer user_data);
+ static void OnDestroy(GtkWidget* dialog, gpointer user_data);
+ void Done(GtkWidget* dialog, 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;
+
+#if (MOZ_WIDGET_GTK == 3)
+ GtkFileChooserWidget *mFileChooserDelegate;
+#endif
+};
+
+#endif
diff --git a/widget/gtk/nsGTKToolkit.h b/widget/gtk/nsGTKToolkit.h
new file mode 100644
index 000000000..ae0d55b63
--- /dev/null
+++ b/widget/gtk/nsGTKToolkit.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 000000000..b7df7f65e
--- /dev/null
+++ b/widget/gtk/nsGtkCursors.h
@@ -0,0 +1,405 @@
+/* -*- 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 000000000..ce55cf18e
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.cpp
@@ -0,0 +1,1483 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include "nsGtkKeyUtils.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <algorithm>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#if (MOZ_WIDGET_GTK == 3)
+#include <gdk/gdkkeysyms-compat.h>
+#endif
+#include <X11/XKBlib.h>
+#include "WidgetUtils.h"
+#include "keysym2ucs.h"
+#include "nsContentUtils.h"
+#include "nsGtkUtils.h"
+#include "nsIBidiKeyboard.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+
+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;
+KeymapWrapper::RepeatState KeymapWrapper::sRepeatState =
+ KeymapWrapper::NOT_PRESSED;
+
+static const char* GetBoolName(bool aBool)
+{
+ return aBool ? "TRUE" : "FALSE";
+}
+
+/* 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)
+{
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Constructor, mGdkKeymap=%p",
+ this, mGdkKeymap));
+
+ g_object_ref(mGdkKeymap);
+ g_signal_connect(mGdkKeymap, "keys-changed",
+ (GCallback)OnKeysChanged, this);
+ g_signal_connect(mGdkKeymap, "direction-changed",
+ (GCallback)OnDirectionChanged, this);
+
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ 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 (GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ InitBySystemSettings();
+
+ 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::InitBySystemSettings()
+{
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, mGdkKeymap=%p",
+ this, mGdkKeymap));
+
+ 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%X), 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);
+}
+
+KeymapWrapper::~KeymapWrapper()
+{
+ gdk_window_remove_filter(nullptr, FilterEvents, this);
+ g_signal_handlers_disconnect_by_func(mGdkKeymap,
+ FuncToGpointer(OnKeysChanged), this);
+ g_signal_handlers_disconnect_by_func(mGdkKeymap,
+ FuncToGpointer(OnDirectionChanged), this);
+ 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;
+ } else if (sLastRepeatableHardwareKeyCode == xEvent->xkey.keycode) {
+ sRepeatState = REPEATING;
+ } 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;
+ }
+ sLastRepeatableHardwareKeyCode = xEvent->xkey.keycode;
+ 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;
+ 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::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.
+ sInstance->mInitialized = false;
+ ResetBidiKeyboard();
+}
+
+// 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 */ void
+KeymapWrapper::InitInputEvent(WidgetInputEvent& aInputEvent,
+ guint aModifierState)
+{
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ aInputEvent.mModifiers = 0;
+ // DOM Meta key should be TRUE only on Mac. We need to discuss this
+ // issue later.
+ if (keymapWrapper->AreModifiersActive(SHIFT, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_SHIFT;
+ }
+ if (keymapWrapper->AreModifiersActive(CTRL, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_CONTROL;
+ }
+ if (keymapWrapper->AreModifiersActive(ALT, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_ALT;
+ }
+ if (keymapWrapper->AreModifiersActive(META, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_META;
+ }
+ if (keymapWrapper->AreModifiersActive(SUPER, aModifierState) ||
+ keymapWrapper->AreModifiersActive(HYPER, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_OS;
+ }
+ if (keymapWrapper->AreModifiersActive(LEVEL3, aModifierState) ||
+ keymapWrapper->AreModifiersActive(LEVEL5, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_ALTGRAPH;
+ }
+ if (keymapWrapper->AreModifiersActive(CAPS_LOCK, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_CAPSLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(NUM_LOCK, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_NUMLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(SCROLL_LOCK, aModifierState)) {
+ aInputEvent.mModifiers |= MODIFIER_SCROLLLOCK;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Debug,
+ ("%p InitInputEvent, aModifierState=0x%08X, "
+ "aInputEvent.mModifiers=0x%04X (Shift: %s, Control: %s, Alt: %s, "
+ "Meta: %s, OS: %s, AltGr: %s, "
+ "CapsLock: %s, NumLock: %s, ScrollLock: %s)",
+ keymapWrapper, aModifierState, 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.buttons = 0;
+ if (aModifierState & GDK_BUTTON1_MASK) {
+ mouseEvent.buttons |= WidgetMouseEvent::eLeftButtonFlag;
+ }
+ if (aModifierState & GDK_BUTTON3_MASK) {
+ mouseEvent.buttons |= WidgetMouseEvent::eRightButtonFlag;
+ }
+ if (aModifierState & GDK_BUTTON2_MASK) {
+ mouseEvent.buttons |= WidgetMouseEvent::eMiddleButtonFlag;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Debug,
+ ("%p InitInputEvent, aInputEvent has buttons, "
+ "aInputEvent.buttons=0x%04X (Left: %s, Right: %s, Middle: %s, "
+ "4th (BACK): %s, 5th (FORWARD): %s)",
+ keymapWrapper, mouseEvent.buttons,
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eLeftButtonFlag),
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eRightButtonFlag),
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::eMiddleButtonFlag),
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::e4thButtonFlag),
+ GetBoolName(mouseEvent.buttons & WidgetMouseEvent::e5thButtonFlag)));
+}
+
+/* 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) {
+ // Always use unshifted keycode for the non-printable key.
+ // XXX It might be better to decide DOM keycode from all keyvals of
+ // the hardware keycode. However, I think that it's too excessive.
+ guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent);
+ uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyvalWithoutModifier);
+ if (!DOMKeyCode) {
+ // If the unshifted keyval couldn't be mapped to a DOM keycode,
+ // we should fallback to legacy logic, so, we should recompute with
+ // the keyval with aGdkKeyEvent.
+ DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval);
+ }
+ return DOMKeyCode;
+ }
+
+ // 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 (unmodifiedChar > 0x7F) {
+ 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 (shiftedChar > 0x7F) {
+ 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.
+ // Note that it's important not to use alternative keyboard layout for ASCII
+ // alphabet inputabble keyboard layout because the keycode for the key with
+ // alternative keyboard layout may conflict with another key on current
+ // keyboard layout.
+ if (!keymapWrapper->IsLatinGroup(aGdkKeyEvent->group)) {
+ gint minGroup = keymapWrapper->GetFirstLatinGroup();
+ if (minGroup >= 0) {
+ uint32_t 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);
+ }
+ uint32_t 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 unmodified character is in ASCII range, use it. Otherwise, use
+ // shifted character.
+ if (!unmodifiedChar && !shiftedChar) {
+ return 0;
+ }
+ return WidgetUtils::ComputeKeyCodeFromChar(
+ unmodifiedChar ? unmodifiedChar : shiftedChar);
+}
+
+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 */ void
+KeymapWrapper::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent)
+{
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ aKeyEvent.mCodeNameIndex = ComputeDOMCodeNameIndex(aGdkKeyEvent);
+ MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ aKeyEvent.mKeyNameIndex =
+ 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);
+ }
+ }
+ aKeyEvent.mKeyCode = ComputeDOMKeyCode(aGdkKeyEvent);
+
+ 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 = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+ 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 = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+ 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 = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+ break;
+
+ default:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ break;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitKeyEvent, modifierState=0x%08X "
+ "aGdkKeyEvent={ type=%s, keyval=%s(0x%X), state=0x%08X, "
+ "hardware_keycode=0x%08X, is_modifier=%s } "
+ "aKeyEvent={ message=%s, isShift=%s, isControl=%s, "
+ "isAlt=%s, isMeta=%s }",
+ keymapWrapper, modifierState,
+ ((aGdkKeyEvent->type == GDK_KEY_PRESS) ?
+ "GDK_KEY_PRESS" : "GDK_KEY_RELEASE"),
+ gdk_keyval_name(aGdkKeyEvent->keyval),
+ aGdkKeyEvent->keyval, aGdkKeyEvent->state,
+ aGdkKeyEvent->hardware_keycode,
+ GetBoolName(aGdkKeyEvent->is_modifier),
+ ((aKeyEvent.mMessage == eKeyDown) ? "eKeyDown" :
+ (aKeyEvent.mMessage == eKeyPress) ? "eKeyPress" : "eKeyUp"),
+ GetBoolName(aKeyEvent.IsShift()), GetBoolName(aKeyEvent.IsControl()),
+ GetBoolName(aKeyEvent.IsAlt()), GetBoolName(aKeyEvent.IsMeta())));
+
+ // 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;
+}
+
+/* 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)
+{
+ 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 000000000..67528d817
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.h
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsGdkKeyUtils_h__
+#define __nsGdkKeyUtils_h__
+
+#include "nsTArray.h"
+#include "mozilla/EventForwards.h"
+
+#include <gdk/gdk.h>
+#include <X11/XKBlib.h>
+
+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.
+ */
+ 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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.
+ */
+ static void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ 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);
+
+ /**
+ * 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 InitBySystemSettings();
+
+ /**
+ * 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);
+
+ /**
+ * 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;
+ 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * See the document of WillDispatchKeyboardEvent().
+ */
+ void WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __nsGdkKeyUtils_h__ */
diff --git a/widget/gtk/nsGtkUtils.h b/widget/gtk/nsGtkUtils.h
new file mode 100644
index 000000000..cb41ddaf7
--- /dev/null
+++ b/widget/gtk/nsGtkUtils.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 000000000..0faa1c6e8
--- /dev/null
+++ b/widget/gtk/nsIImageToPixbuf.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 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/nsIdleServiceGTK.cpp b/widget/gtk/nsIdleServiceGTK.cpp
new file mode 100644
index 000000000..6b6832c82
--- /dev/null
+++ b/widget/gtk/nsIdleServiceGTK.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <gtk/gtk.h>
+
+#include "nsIdleServiceGTK.h"
+#include "nsIServiceManager.h"
+#include "nsDebug.h"
+#include "prlink.h"
+#include "mozilla/Logging.h"
+
+using mozilla::LogLevel;
+
+static PRLogModuleInfo* sIdleLog = nullptr;
+
+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;
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceGTK, nsIdleService)
+
+static void Initialize()
+{
+ if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ return;
+
+ // This will leak - See comments in ~nsIdleServiceGTK().
+ 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;
+}
+
+nsIdleServiceGTK::nsIdleServiceGTK()
+ : mXssInfo(nullptr)
+{
+ if (!sIdleLog)
+ sIdleLog = PR_NewLogModule("nsIIdleService");
+
+ Initialize();
+}
+
+nsIdleServiceGTK::~nsIdleServiceGTK()
+{
+ 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
+nsIdleServiceGTK::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
+nsIdleServiceGTK::UsePollMode()
+{
+ return sInitialized;
+}
diff --git a/widget/gtk/nsIdleServiceGTK.h b/widget/gtk/nsIdleServiceGTK.h
new file mode 100644
index 000000000..01ae9268e
--- /dev/null
+++ b/widget/gtk/nsIdleServiceGTK.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIdleServiceGTK_h__
+#define nsIdleServiceGTK_h__
+
+#include "nsIdleService.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 nsIdleServiceGTK : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsIdleServiceGTK> GetInstance()
+ {
+ RefPtr<nsIdleServiceGTK> idleService =
+ nsIdleService::GetInstance().downcast<nsIdleServiceGTK>();
+ if (!idleService) {
+ idleService = new nsIdleServiceGTK();
+ }
+
+ return idleService.forget();
+ }
+
+private:
+ ~nsIdleServiceGTK();
+ XScreenSaverInfo* mXssInfo;
+
+protected:
+ nsIdleServiceGTK();
+ virtual bool UsePollMode() override;
+};
+
+#endif // nsIdleServiceGTK_h__
diff --git a/widget/gtk/nsImageToPixbuf.cpp b/widget/gtk/nsImageToPixbuf.cpp
new file mode 100644
index 000000000..e06605b2b
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.cpp
@@ -0,0 +1,120 @@
+/* vim:set sw=4 sts=4 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);
+
+ // 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 000000000..9c026048a
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.h
@@ -0,0 +1,46 @@
+/* vim:set sw=4 sts=4 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;
+}
+}
+
+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() {}
+};
+
+
+// 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 000000000..53430dfbb
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -0,0 +1,1465 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// 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 "nsScreenGtk.h"
+
+#include "gtkdrawing.h"
+#include "nsStyleConsts.h"
+#include "gfxFontConstants.h"
+#include "WidgetUtils.h"
+
+#include <dlfcn.h>
+
+#include "mozilla/gfx/2D.h"
+
+#if MOZ_WIDGET_GTK != 2
+#include <cairo-gobject.h>
+#include "WidgetStyleCache.h"
+#include "prenv.h"
+#endif
+
+using mozilla::LookAndFeel;
+
+#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()
+ : nsXPLookAndFeel(),
+#if (MOZ_WIDGET_GTK == 2)
+ mStyle(nullptr),
+#else
+ mBackgroundStyle(nullptr),
+ mButtonStyle(nullptr),
+#endif
+ mDefaultFontCached(false), mButtonFontCached(false),
+ mFieldFontCached(false), mMenuFontCached(false)
+{
+ Init();
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+#if (MOZ_WIDGET_GTK == 2)
+ g_object_unref(mStyle);
+#else
+ g_object_unref(mBackgroundStyle);
+ g_object_unref(mButtonStyle);
+#endif
+}
+
+#if MOZ_WIDGET_GTK != 2
+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, GTK_STATE_FLAG_NORMAL, &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;
+}
+#endif
+
+nsresult
+nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor)
+{
+#if (MOZ_WIDGET_GTK == 3)
+ GdkRGBA gdk_color;
+#endif
+ 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.
+#if (MOZ_WIDGET_GTK == 2)
+ case eColorID_WindowBackground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_WindowForeground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_WidgetBackground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_WidgetForeground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_WidgetSelectBackground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_SELECTED]);
+ break;
+ case eColorID_WidgetSelectForeground:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_SELECTED]);
+ break;
+#else
+ case eColorID_WindowBackground:
+ case eColorID_WidgetBackground:
+ case eColorID_TextBackground:
+ case eColorID_activecaption: // active window caption background
+ case eColorID_appworkspace: // MDI background color
+ case eColorID_background: // desktop background
+ case eColorID_window:
+ case eColorID_windowframe:
+ case eColorID__moz_dialog:
+ case eColorID__moz_combobox:
+ aColor = sMozWindowBackground;
+ break;
+ case eColorID_WindowForeground:
+ case eColorID_WidgetForeground:
+ case eColorID_TextForeground:
+ case eColorID_captiontext: // text in active window caption, size box, and scrollbar arrow box (!)
+ case eColorID_windowtext:
+ case eColorID__moz_dialogtext:
+ aColor = sMozWindowText;
+ break;
+ case eColorID_WidgetSelectBackground:
+ case eColorID_TextSelectBackground:
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ case eColorID__moz_dragtargetzone:
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ case eColorID_highlight: // preference selected item,
+ aColor = sTextSelectedBackground;
+ break;
+ case eColorID_WidgetSelectForeground:
+ case eColorID_TextSelectForeground:
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ case eColorID_highlighttext:
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aColor = sTextSelectedText;
+ break;
+#endif
+ case eColorID_Widget3DHighlight:
+ aColor = NS_RGB(0xa0,0xa0,0xa0);
+ break;
+ case eColorID_Widget3DShadow:
+ aColor = NS_RGB(0x40,0x40,0x40);
+ break;
+#if (MOZ_WIDGET_GTK == 2)
+ case eColorID_TextBackground:
+ // not used?
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_TextForeground:
+ // not used?
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_TextSelectBackground:
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ // still used
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_SELECTED]);
+ break;
+ case eColorID_TextSelectForeground:
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ // still used
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_SELECTED]);
+ break;
+#endif
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ break;
+
+#if (MOZ_WIDGET_GTK == 2)
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case eColorID_activeborder:
+ // active window border
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_activecaption:
+ // active window caption background
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_appworkspace:
+ // MDI background color
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_background:
+ // desktop background
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_captiontext:
+ // text in active window caption, size box, and scrollbar arrow box (!)
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_graytext:
+ // disabled text in windows, menus, etc.
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_INSENSITIVE]);
+ break;
+ case eColorID_highlight:
+ // background of selected item
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_SELECTED]);
+ break;
+ case eColorID_highlighttext:
+ // text of selected item
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_SELECTED]);
+ break;
+ case eColorID_inactiveborder:
+ // inactive window border
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID_inactivecaption:
+ // inactive window caption
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_INSENSITIVE]);
+ break;
+ case eColorID_inactivecaptiontext:
+ // text in inactive window caption
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_INSENSITIVE]);
+ break;
+#else
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case eColorID_activeborder:
+ // active window border
+ gtk_style_context_get_border_color(mBackgroundStyle,
+ GTK_STATE_FLAG_NORMAL, &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+ case eColorID_inactiveborder:
+ // inactive window border
+ gtk_style_context_get_border_color(mBackgroundStyle,
+ GTK_STATE_FLAG_INSENSITIVE,
+ &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+ case eColorID_graytext: // disabled text in windows, menus, etc.
+ case eColorID_inactivecaptiontext: // text in inactive window caption
+ aColor = sMenuTextInactive;
+ break;
+ case eColorID_inactivecaption:
+ // inactive window caption
+ gtk_style_context_get_background_color(mBackgroundStyle,
+ GTK_STATE_FLAG_INSENSITIVE,
+ &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+#endif
+ case eColorID_infobackground:
+ // tooltip background color
+ aColor = sInfoBackground;
+ break;
+ case eColorID_infotext:
+ // tooltip text color
+ aColor = sInfoText;
+ break;
+ case eColorID_menu:
+ // menu background
+ aColor = sMenuBackground;
+ break;
+ case eColorID_menutext:
+ // menu text
+ aColor = sMenuText;
+ break;
+ case eColorID_scrollbar:
+ // scrollbar gray area
+#if (MOZ_WIDGET_GTK == 2)
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_ACTIVE]);
+#else
+ aColor = sMozScrollbar;
+#endif
+ break;
+
+ case eColorID_threedlightshadow:
+ // 3-D highlighted inner edge color
+ // always same as background in GTK code
+ case eColorID_threedface:
+ case eColorID_buttonface:
+ // 3-D face color
+#if (MOZ_WIDGET_GTK == 3)
+ aColor = sMozWindowBackground;
+#else
+ aColor = sButtonBackground;
+#endif
+ break;
+
+ case eColorID_buttontext:
+ // text on push buttons
+ aColor = sButtonText;
+ break;
+
+ case eColorID_buttonhighlight:
+ // 3-D highlighted edge color
+ case eColorID_threedhighlight:
+ // 3-D highlighted outer edge color
+ aColor = sFrameOuterLightBorder;
+ break;
+
+ case eColorID_buttonshadow:
+ // 3-D shadow edge color
+ case eColorID_threedshadow:
+ // 3-D shadow inner edge color
+ aColor = sFrameInnerDarkBorder;
+ break;
+
+#if (MOZ_WIDGET_GTK == 2)
+ case eColorID_threeddarkshadow:
+ // 3-D shadow outer edge color
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->black);
+ break;
+
+ case eColorID_window:
+ case eColorID_windowframe:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+
+ case eColorID_windowtext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
+ break;
+
+ case eColorID__moz_eventreerow:
+ case eColorID__moz_field:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_NORMAL]);
+ break;
+ case eColorID__moz_fieldtext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_NORMAL]);
+ break;
+ case eColorID__moz_dialog:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID__moz_dialogtext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_NORMAL]);
+ break;
+ case eColorID__moz_dragtargetzone:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_SELECTED]);
+ break;
+ case eColorID__moz_buttondefault:
+ // default button border color
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->black);
+ break;
+ case eColorID__moz_buttonhoverface:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->bg[GTK_STATE_PRELIGHT]);
+ break;
+ case eColorID__moz_buttonhovertext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->fg[GTK_STATE_PRELIGHT]);
+ break;
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->base[GTK_STATE_ACTIVE]);
+ break;
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aColor = GDK_COLOR_TO_NS_RGB(mStyle->text[GTK_STATE_ACTIVE]);
+ break;
+#else
+ case eColorID_threeddarkshadow:
+ // Hardcode to black
+ aColor = NS_RGB(0x00,0x00,0x00);
+ break;
+
+ case eColorID__moz_eventreerow:
+ case eColorID__moz_field:
+ aColor = sMozFieldBackground;
+ break;
+ case eColorID__moz_fieldtext:
+ aColor = sMozFieldText;
+ break;
+ case eColorID__moz_buttondefault:
+ // default button border color
+ gtk_style_context_get_border_color(mButtonStyle,
+ GTK_STATE_FLAG_NORMAL, &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+ case eColorID__moz_buttonhoverface:
+ gtk_style_context_get_background_color(mButtonStyle,
+ GTK_STATE_FLAG_PRELIGHT,
+ &gdk_color);
+ aColor = GDK_RGBA_TO_NS_RGBA(gdk_color);
+ break;
+ case eColorID__moz_buttonhovertext:
+ aColor = sButtonHoverText;
+ break;
+#endif
+ case eColorID__moz_menuhover:
+ aColor = sMenuHover;
+ break;
+ case eColorID__moz_menuhovertext:
+ aColor = sMenuHoverText;
+ break;
+ case eColorID__moz_oddtreerow:
+ aColor = sOddCellBackground;
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ aColor = sNativeHyperLinkText;
+ break;
+ case eColorID__moz_comboboxtext:
+ aColor = sComboBoxText;
+ break;
+#if (MOZ_WIDGET_GTK == 2)
+ case eColorID__moz_combobox:
+ aColor = sComboBoxBackground;
+ break;
+#endif
+ case eColorID__moz_menubartext:
+ aColor = sMenuBarText;
+ break;
+ case eColorID__moz_menubarhovertext:
+ aColor = sMenuBarHoverText;
+ break;
+ case eColorID__moz_gtk_info_bar_text:
+#if (MOZ_WIDGET_GTK == 3)
+ aColor = sInfoBarText;
+#else
+ aColor = sInfoText;
+#endif
+ break;
+ default:
+ /* default color is BLACK */
+ aColor = 0;
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return res;
+}
+
+#if (MOZ_WIDGET_GTK == 2)
+static void darken_gdk_color(GdkColor *src, GdkColor *dest)
+{
+ gdouble red;
+ gdouble green;
+ gdouble blue;
+
+ red = (gdouble) src->red / 65535.0;
+ green = (gdouble) src->green / 65535.0;
+ blue = (gdouble) src->blue / 65535.0;
+
+ red *= 0.93;
+ green *= 0.93;
+ blue *= 0.93;
+
+ dest->red = red * 65535.0;
+ dest->green = green * 65535.0;
+ dest->blue = blue * 65535.0;
+}
+#endif
+
+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::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ nsresult res = NS_OK;
+
+ // Set these before they can get overrided in the nsXPLookAndFeel.
+ switch (aID) {
+ case eIntID_ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ return NS_OK;
+ case eIntID_ScrollButtonMiddleMouseButtonAction:
+ aResult = 1;
+ return NS_OK;
+ case eIntID_ScrollButtonRightMouseButtonAction:
+ aResult = 2;
+ return NS_OK;
+ default:
+ break;
+ }
+
+ res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ {
+ GtkSettings *settings;
+ gint blink_time;
+ gboolean blink;
+
+ settings = gtk_settings_get_default ();
+ g_object_get (settings,
+ "gtk-cursor-blink-time", &blink_time,
+ "gtk-cursor-blink", &blink,
+ nullptr);
+
+ if (blink)
+ aResult = (int32_t) blink_time;
+ else
+ aResult = 0;
+ break;
+ }
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ {
+ GtkWidget *entry;
+ GtkSettings *settings;
+ gboolean select_on_focus;
+
+ entry = gtk_entry_new();
+ g_object_ref_sink(entry);
+ settings = gtk_widget_get_settings(entry);
+ g_object_get(settings,
+ "gtk-entry-select-on-focus",
+ &select_on_focus,
+ nullptr);
+
+ if(select_on_focus)
+ aResult = 1;
+ else
+ aResult = 0;
+
+ gtk_widget_destroy(entry);
+ g_object_unref(entry);
+ }
+ break;
+ case eIntID_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 eIntID_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 eIntID_TooltipDelay:
+ {
+ aResult = 500;
+ break;
+ }
+ case eIntID_MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+ case eIntID_SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case eIntID_DragThresholdX:
+ case eIntID_DragThresholdY:
+ {
+ GtkWidget* box = gtk_hbox_new(FALSE, 5);
+ gint threshold = 0;
+ g_object_get(gtk_widget_get_settings(box),
+ "gtk-dnd-drag-threshold", &threshold,
+ nullptr);
+ g_object_ref_sink(box);
+
+ aResult = threshold;
+ }
+ break;
+ case eIntID_ScrollArrowStyle:
+ moz_gtk_init();
+ aResult =
+ ConvertGTKStepperStyleToMozillaScrollArrowStyle(moz_gtk_get_scrollbar_widget());
+ break;
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case eIntID_TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case eIntID_TreeScrollDelay:
+ aResult = 100;
+ break;
+ case eIntID_TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case eIntID_DWMCompositor:
+ case eIntID_WindowsClassic:
+ case eIntID_WindowsDefaultTheme:
+ case eIntID_WindowsThemeIdentifier:
+ case eIntID_OperatingSystemVersionIdentifier:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_TouchEnabled:
+#if MOZ_WIDGET_GTK == 3
+ aResult = mozilla::widget::WidgetUtils::IsTouchDeviceSupportPresent();
+ break;
+#else
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+#endif
+ break;
+ case eIntID_MacGraphiteTheme:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_AlertNotificationOrigin:
+ aResult = NS_ALERT_TOP;
+ break;
+ case eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ break;
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+ case eIntID_MenuBarDrag:
+ aResult = sMenuSupportsDrag;
+ break;
+ case eIntID_ScrollbarButtonAutoRepeatBehavior:
+ aResult = 1;
+ break;
+ case eIntID_SwipeAnimationEnabled:
+ aResult = 0;
+ break;
+ case eIntID_ColorPickerAvailable:
+ aResult = 1;
+ break;
+ case eIntID_ContextMenuOffsetVertical:
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+nsresult
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = NS_OK;
+ res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case eFloatID_CaretAspectRatio:
+ aResult = sCaretRatio;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+static void
+GetSystemFontInfo(GtkWidget *aWidget,
+ nsString *aFontName,
+ gfxFontStyle *aFontStyle)
+{
+ GtkSettings *settings = gtk_widget_get_settings(aWidget);
+
+ aFontStyle->style = NS_FONT_STYLE_NORMAL;
+
+ gchar *fontname;
+ g_object_get(settings, "gtk-font-name", &fontname, nullptr);
+
+ PangoFontDescription *desc;
+ desc = pango_font_description_from_string(fontname);
+
+ aFontStyle->systemFont = true;
+
+ g_free(fontname);
+
+ NS_NAMED_LITERAL_STRING(quote, "\"");
+ NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
+ *aFontName = quote + family + quote;
+
+ aFontStyle->weight = pango_font_description_get_weight(desc);
+
+ // FIXME: Set aFontStyle->stretch correctly!
+ aFontStyle->stretch = NS_FONT_STRETCH_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::GetDPI()) / POINTS_PER_INCH_FLOAT;
+ }
+
+ // Scale fonts up on HiDPI displays.
+ // This would be done automatically with cairo, but we manually manage
+ // the display scale for platform consistency.
+ size *= nsScreenGtk::GetGtkMonitorScaleFactor();
+
+ // |size| is now pixels
+
+ aFontStyle->size = size;
+
+ pango_font_description_free(desc);
+}
+
+static void
+GetSystemFontInfo(LookAndFeel::FontID aID,
+ nsString *aFontName,
+ gfxFontStyle *aFontStyle)
+{
+ if (aID == LookAndFeel::eFont_Widget) {
+ GtkWidget *label = gtk_label_new("M");
+ GtkWidget *parent = gtk_fixed_new();
+ GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
+
+ gtk_container_add(GTK_CONTAINER(parent), label);
+ gtk_container_add(GTK_CONTAINER(window), parent);
+
+ gtk_widget_ensure_style(label);
+ GetSystemFontInfo(label, aFontName, aFontStyle);
+ gtk_widget_destroy(window); // no unref, windows are different
+
+ } else if (aID == LookAndFeel::eFont_Button) {
+ GtkWidget *label = gtk_label_new("M");
+ GtkWidget *parent = gtk_fixed_new();
+ GtkWidget *button = gtk_button_new();
+ GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
+
+ gtk_container_add(GTK_CONTAINER(button), label);
+ gtk_container_add(GTK_CONTAINER(parent), button);
+ gtk_container_add(GTK_CONTAINER(window), parent);
+
+ gtk_widget_ensure_style(label);
+ GetSystemFontInfo(label, aFontName, aFontStyle);
+ gtk_widget_destroy(window); // no unref, windows are different
+
+ } else if (aID == LookAndFeel::eFont_Field) {
+ GtkWidget *entry = gtk_entry_new();
+ GtkWidget *parent = gtk_fixed_new();
+ GtkWidget *window = gtk_window_new(GTK_WINDOW_POPUP);
+
+ gtk_container_add(GTK_CONTAINER(parent), entry);
+ gtk_container_add(GTK_CONTAINER(window), parent);
+
+ gtk_widget_ensure_style(entry);
+ GetSystemFontInfo(entry, aFontName, aFontStyle);
+ gtk_widget_destroy(window); // no unref, windows are different
+
+ } else {
+ MOZ_ASSERT(aID == LookAndFeel::eFont_Menu, "unexpected font ID");
+ GtkWidget *accel_label = gtk_accel_label_new("M");
+ GtkWidget *menuitem = gtk_menu_item_new();
+ GtkWidget *menu = gtk_menu_new();
+ g_object_ref_sink(menu);
+
+ gtk_container_add(GTK_CONTAINER(menuitem), accel_label);
+ gtk_menu_shell_append((GtkMenuShell *)GTK_MENU(menu), menuitem);
+
+ gtk_widget_ensure_style(accel_label);
+ GetSystemFontInfo(accel_label, aFontName, aFontStyle);
+ g_object_unref(menu);
+ }
+}
+
+bool
+nsLookAndFeel::GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ nsString *cachedFontName = nullptr;
+ gfxFontStyle *cachedFontStyle = nullptr;
+ bool *isCached = nullptr;
+
+ switch (aID) {
+ case eFont_Menu: // css2
+ case eFont_PullDownMenu: // css3
+ cachedFontName = &mMenuFontName;
+ cachedFontStyle = &mMenuFontStyle;
+ isCached = &mMenuFontCached;
+ aID = eFont_Menu;
+ break;
+
+ case eFont_Field: // css3
+ case eFont_List: // css3
+ cachedFontName = &mFieldFontName;
+ cachedFontStyle = &mFieldFontStyle;
+ isCached = &mFieldFontCached;
+ aID = eFont_Field;
+ break;
+
+ case eFont_Button: // css3
+ cachedFontName = &mButtonFontName;
+ cachedFontStyle = &mButtonFontStyle;
+ isCached = &mButtonFontCached;
+ break;
+
+ case eFont_Caption: // css2
+ case eFont_Icon: // css2
+ case eFont_MessageBox: // css2
+ case eFont_SmallCaption: // css2
+ case eFont_StatusBar: // css2
+ case eFont_Window: // css3
+ case eFont_Document: // css3
+ case eFont_Workspace: // css3
+ case eFont_Desktop: // css3
+ case eFont_Info: // css3
+ case eFont_Dialog: // css3
+ case eFont_Tooltips: // moz
+ case eFont_Widget: // moz
+ cachedFontName = &mDefaultFontName;
+ cachedFontStyle = &mDefaultFontStyle;
+ isCached = &mDefaultFontCached;
+ aID = eFont_Widget;
+ break;
+ }
+
+ if (!*isCached) {
+ GetSystemFontInfo(aID, cachedFontName, cachedFontStyle);
+ *isCached = true;
+ }
+
+ aFontName = *cachedFontName;
+ aFontStyle = *cachedFontStyle;
+ return true;
+}
+
+#if (MOZ_WIDGET_GTK == 3)
+static GtkStyleContext*
+create_context(GtkWidgetPath *path)
+{
+ GtkStyleContext *style = gtk_style_context_new();
+ gtk_style_context_set_path(style, path);
+ return(style);
+}
+#endif
+
+void
+nsLookAndFeel::Init()
+{
+ GdkColor colorValue;
+ GdkColor *colorValuePtr;
+
+#if (MOZ_WIDGET_GTK == 2)
+ NS_ASSERTION(!mStyle, "already initialized");
+ // GtkInvisibles come with a refcount that is not floating
+ // (since their initialization code calls g_object_ref_sink) and
+ // their destroy code releases that reference (which means they
+ // have to be explicitly destroyed, since calling unref enough
+ // to cause destruction would lead to *another* unref).
+ // However, this combination means that it's actually still ok
+ // to use the normal pattern, which is to g_object_ref_sink
+ // after construction, and then destroy *and* unref when we're
+ // done. (Though we could skip the g_object_ref_sink and the
+ // corresponding g_object_unref, but that's particular to
+ // GtkInvisibles and GtkWindows.)
+ GtkWidget *widget = gtk_invisible_new();
+ g_object_ref_sink(widget); // effectively g_object_ref (see above)
+
+ gtk_widget_ensure_style(widget);
+ mStyle = gtk_style_copy(gtk_widget_get_style(widget));
+
+ gtk_widget_destroy(widget);
+ g_object_unref(widget);
+
+ // tooltip foreground and background
+ GtkStyle *style = gtk_rc_get_style_by_paths(gtk_settings_get_default(),
+ "gtk-tooltips", "GtkWindow",
+ GTK_TYPE_WINDOW);
+ if (style) {
+ sInfoBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
+ sInfoText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ }
+
+ // menu foreground & menu background
+ GtkWidget *accel_label = gtk_accel_label_new("M");
+ GtkWidget *menuitem = gtk_menu_item_new();
+ GtkWidget *menu = gtk_menu_new();
+
+ g_object_ref_sink(menu);
+
+ gtk_container_add(GTK_CONTAINER(menuitem), accel_label);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ gtk_widget_set_style(accel_label, nullptr);
+ gtk_widget_set_style(menu, nullptr);
+ gtk_widget_realize(menu);
+ gtk_widget_realize(accel_label);
+
+ style = gtk_widget_get_style(accel_label);
+ if (style) {
+ sMenuText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ }
+
+ style = gtk_widget_get_style(menu);
+ if (style) {
+ sMenuBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
+ }
+
+ style = gtk_widget_get_style(menuitem);
+ if (style) {
+ sMenuHover = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_PRELIGHT]);
+ sMenuHoverText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_PRELIGHT]);
+ }
+
+ g_object_unref(menu);
+#else
+ GdkRGBA color;
+ GtkStyleContext *style;
+
+ // 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_for_screen(gdk_screen_get_default());
+
+ // Disable dark theme because it interacts poorly with widget styling in
+ // web content (see bug 1216658).
+ // To avoid triggering reload of theme settings unnecessarily, only set the
+ // setting when necessary.
+ const gchar* dark_setting = "gtk-application-prefer-dark-theme";
+ gboolean dark;
+ g_object_get(settings, dark_setting, &dark, nullptr);
+
+ if (dark && !PR_GetEnv("MOZ_ALLOW_GTK_DARK_THEME")) {
+ g_object_set(settings, dark_setting, FALSE, nullptr);
+ }
+
+ GtkWidgetPath *path = gtk_widget_path_new();
+ gtk_widget_path_append_type(path, GTK_TYPE_WINDOW);
+
+ mBackgroundStyle = create_context(path);
+ gtk_style_context_add_class(mBackgroundStyle, GTK_STYLE_CLASS_BACKGROUND);
+
+ mButtonStyle = create_context(path);
+ gtk_style_context_add_class(mButtonStyle, GTK_STYLE_CLASS_BUTTON);
+
+ // Scrollbar colors
+ style = create_context(path);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_SCROLLBAR);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_TROUGH);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozScrollbar = GDK_RGBA_TO_NS_RGBA(color);
+ g_object_unref(style);
+
+ // Window colors
+ style = create_context(path);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozWindowBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozWindowText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_restore(style);
+ g_object_unref(style);
+
+ // tooltip foreground and background
+ style = ClaimStyleContext(MOZ_GTK_TOOLTIP);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sInfoBackground = GDK_RGBA_TO_NS_RGBA(color);
+ {
+ GtkStyleContext* boxStyle =
+ CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
+ style);
+ GtkStyleContext* labelStyle =
+ CreateStyleForWidget(gtk_label_new(nullptr), boxStyle);
+ gtk_style_context_get_color(labelStyle, GTK_STATE_FLAG_NORMAL, &color);
+ g_object_unref(labelStyle);
+ g_object_unref(boxStyle);
+ }
+ sInfoText = GDK_RGBA_TO_NS_RGBA(color);
+ ReleaseStyleContext(style);
+
+ // menu foreground & menu background
+ GtkWidget *accel_label = gtk_accel_label_new("M");
+ GtkWidget *menuitem = gtk_menu_item_new();
+ GtkWidget *menu = gtk_menu_new();
+
+ g_object_ref_sink(menu);
+
+ gtk_container_add(GTK_CONTAINER(menuitem), accel_label);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ style = gtk_widget_get_style_context(accel_label);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMenuText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_INSENSITIVE, &color);
+ sMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = gtk_widget_get_style_context(menu);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMenuBackground = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = gtk_widget_get_style_context(menuitem);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ sMenuHover = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ sMenuHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ g_object_unref(menu);
+#endif
+
+ // button styles
+ GtkWidget *parent = gtk_fixed_new();
+ GtkWidget *button = gtk_button_new();
+ GtkWidget *label = gtk_label_new("M");
+#if (MOZ_WIDGET_GTK == 2)
+ GtkWidget *combobox = gtk_combo_box_new();
+ GtkWidget *comboboxLabel = gtk_label_new("M");
+ gtk_container_add(GTK_CONTAINER(combobox), comboboxLabel);
+#else
+ GtkWidget *combobox = gtk_combo_box_new_with_entry();
+ GtkWidget *comboboxLabel = gtk_bin_get_child(GTK_BIN(combobox));
+#endif
+ 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(button), label);
+ gtk_container_add(GTK_CONTAINER(parent), button);
+ gtk_container_add(GTK_CONTAINER(parent), treeView);
+ gtk_container_add(GTK_CONTAINER(parent), linkButton);
+ gtk_container_add(GTK_CONTAINER(parent), combobox);
+ 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);
+
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_widget_set_style(button, nullptr);
+ gtk_widget_set_style(label, nullptr);
+ gtk_widget_set_style(treeView, nullptr);
+ gtk_widget_set_style(linkButton, nullptr);
+ gtk_widget_set_style(combobox, nullptr);
+ gtk_widget_set_style(comboboxLabel, nullptr);
+ gtk_widget_set_style(menuBar, nullptr);
+ gtk_widget_set_style(entry, nullptr);
+
+ gtk_widget_realize(button);
+ gtk_widget_realize(label);
+ gtk_widget_realize(treeView);
+ gtk_widget_realize(linkButton);
+ gtk_widget_realize(combobox);
+ gtk_widget_realize(comboboxLabel);
+ gtk_widget_realize(menuBar);
+ gtk_widget_realize(entry);
+
+ style = gtk_widget_get_style(label);
+ if (style) {
+ sButtonText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ }
+
+ style = gtk_widget_get_style(comboboxLabel);
+ if (style) {
+ sComboBoxText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ }
+ style = gtk_widget_get_style(combobox);
+ if (style) {
+ sComboBoxBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
+ }
+
+ style = gtk_widget_get_style(menuBar);
+ if (style) {
+ sMenuBarText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_NORMAL]);
+ sMenuBarHoverText = GDK_COLOR_TO_NS_RGB(style->fg[GTK_STATE_SELECTED]);
+ }
+
+ // 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
+ colorValuePtr = nullptr;
+ gtk_widget_style_get(treeView,
+ "odd-row-color", &colorValuePtr,
+ nullptr);
+
+ if (colorValuePtr) {
+ colorValue = *colorValuePtr;
+ } else {
+ gtk_widget_style_get(treeView,
+ "even-row-color", &colorValuePtr,
+ nullptr);
+ if (colorValuePtr)
+ darken_gdk_color(colorValuePtr, &colorValue);
+ else
+ darken_gdk_color(&treeView->style->base[GTK_STATE_NORMAL], &colorValue);
+ }
+
+ sOddCellBackground = GDK_COLOR_TO_NS_RGB(colorValue);
+ if (colorValuePtr)
+ gdk_color_free(colorValuePtr);
+
+ style = gtk_widget_get_style(button);
+ if (style) {
+ sButtonBackground = GDK_COLOR_TO_NS_RGB(style->bg[GTK_STATE_NORMAL]);
+ sFrameOuterLightBorder =
+ GDK_COLOR_TO_NS_RGB(style->light[GTK_STATE_NORMAL]);
+ sFrameInnerDarkBorder =
+ GDK_COLOR_TO_NS_RGB(style->dark[GTK_STATE_NORMAL]);
+ }
+#else
+ // Text colors
+ style = gtk_widget_get_style_context(textView);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_VIEW);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozFieldBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMozFieldText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Selected text and background
+ gtk_style_context_get_background_color(style,
+ static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_SELECTED),
+ &color);
+ sTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style,
+ static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_SELECTED),
+ &color);
+ sTextSelectedText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_restore(style);
+
+ // Button text, background, border
+ style = gtk_widget_get_style_context(label);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sButtonText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ sButtonHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Combobox text color
+ style = gtk_widget_get_style_context(comboboxLabel);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sComboBoxText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Menubar text and hover text colors
+ style = gtk_widget_get_style_context(menuBarItem);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ sMenuBarText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ sMenuBarHoverText = 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 = gtk_widget_get_style_context(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);
+ sOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_restore(style);
+
+ gtk_widget_path_free(path);
+
+ // 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 = ClaimStyleContext(MOZ_GTK_FRAME_BORDER);
+ bool themeUsesColors =
+ GetBorderColors(style, &sFrameOuterLightBorder, &sFrameInnerDarkBorder);
+ ReleaseStyleContext(style);
+ if (!themeUsesColors) {
+ style = ClaimStyleContext(MOZ_GTK_FRAME);
+ GetBorderColors(style, &sFrameOuterLightBorder, &sFrameInnerDarkBorder);
+ ReleaseStyleContext(style);
+ }
+
+ // 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);
+ sInfoBarText = GDK_RGBA_TO_NS_RGBA(color);
+#endif
+ // 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);
+ }
+ }
+ sMenuSupportsDrag = supports_menubar_drag;
+
+ colorValuePtr = nullptr;
+ gtk_widget_style_get(linkButton, "link-color", &colorValuePtr, nullptr);
+ if (colorValuePtr) {
+ colorValue = *colorValuePtr; // we can't pass deref pointers to GDK_COLOR_TO_NS_RGB
+ sNativeHyperLinkText = GDK_COLOR_TO_NS_RGB(colorValue);
+ gdk_color_free(colorValuePtr);
+ } else {
+ sNativeHyperLinkText = NS_RGB(0x00,0x00,0xEE);
+ }
+
+ // invisible character styles
+ guint value;
+ g_object_get (entry, "invisible-char", &value, nullptr);
+ sInvisibleCharacter = char16_t(value);
+
+ // caret styles
+ gtk_widget_style_get(entry,
+ "cursor-aspect-ratio", &sCaretRatio,
+ nullptr);
+
+ gtk_widget_destroy(window);
+}
+
+// virtual
+char16_t
+nsLookAndFeel::GetPasswordCharacterImpl()
+{
+ return sInvisibleCharacter;
+}
+
+void
+nsLookAndFeel::RefreshImpl()
+{
+ nsXPLookAndFeel::RefreshImpl();
+
+ mDefaultFontCached = false;
+ mButtonFontCached = false;
+ mFieldFontCached = false;
+ mMenuFontCached = false;
+
+#if (MOZ_WIDGET_GTK == 2)
+ g_object_unref(mStyle);
+ mStyle = nullptr;
+#else
+ g_object_unref(mBackgroundStyle);
+ g_object_unref(mButtonStyle);
+
+ mBackgroundStyle = nullptr;
+ mButtonStyle = nullptr;
+#endif
+
+ Init();
+}
+
+bool
+nsLookAndFeel::GetEchoPasswordImpl() {
+ return false;
+}
diff --git a/widget/gtk/nsLookAndFeel.h b/widget/gtk/nsLookAndFeel.h
new file mode 100644
index 000000000..9058250b9
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "nsXPLookAndFeel.h"
+#include "nsCOMPtr.h"
+#include "gfxFont.h"
+
+struct _GtkStyle;
+
+class nsLookAndFeel: public nsXPLookAndFeel {
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel);
+
+ virtual void RefreshImpl();
+ virtual char16_t GetPasswordCharacterImpl();
+ virtual bool GetEchoPasswordImpl();
+
+protected:
+#if (MOZ_WIDGET_GTK == 2)
+ struct _GtkStyle *mStyle;
+#else
+ struct _GtkStyleContext *mBackgroundStyle;
+ struct _GtkStyleContext *mButtonStyle;
+#endif
+
+ // Cached fonts
+ bool mDefaultFontCached;
+ bool mButtonFontCached;
+ bool mFieldFontCached;
+ bool mMenuFontCached;
+ nsString mDefaultFontName;
+ nsString mButtonFontName;
+ nsString mFieldFontName;
+ nsString mMenuFontName;
+ gfxFontStyle mDefaultFontStyle;
+ gfxFontStyle mButtonFontStyle;
+ gfxFontStyle mFieldFontStyle;
+ gfxFontStyle mMenuFontStyle;
+
+ // Cached colors
+ nscolor sInfoBackground;
+ nscolor sInfoText;
+ nscolor sMenuBackground;
+ nscolor sMenuBarText;
+ nscolor sMenuBarHoverText;
+ nscolor sMenuText;
+ nscolor sMenuTextInactive;
+ nscolor sMenuHover;
+ nscolor sMenuHoverText;
+ nscolor sButtonText;
+ nscolor sButtonHoverText;
+ nscolor sButtonBackground;
+ nscolor sFrameOuterLightBorder;
+ nscolor sFrameInnerDarkBorder;
+ nscolor sOddCellBackground;
+ nscolor sNativeHyperLinkText;
+ nscolor sComboBoxText;
+ nscolor sComboBoxBackground;
+ nscolor sMozFieldText;
+ nscolor sMozFieldBackground;
+ nscolor sMozWindowText;
+ nscolor sMozWindowBackground;
+ nscolor sTextSelectedText;
+ nscolor sTextSelectedBackground;
+ nscolor sMozScrollbar;
+#if (MOZ_WIDGET_GTK == 3)
+ nscolor sInfoBarText;
+#endif
+ char16_t sInvisibleCharacter;
+ float sCaretRatio;
+ bool sMenuSupportsDrag;
+
+ void Init();
+};
+
+#endif
diff --git a/widget/gtk/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp
new file mode 100644
index 000000000..89b8ab7dc
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -0,0 +1,1994 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsThemeConstants.h"
+#include "gtkdrawing.h"
+#include "nsScreenGtk.h"
+
+#include "gfx2DGlue.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsIFrame.h"
+#include "nsIPresShell.h"
+#include "nsIContent.h"
+#include "nsViewManager.h"
+#include "nsNameSpaceManager.h"
+#include "nsGfxCIID.h"
+#include "nsTransform2D.h"
+#include "nsMenuFrame.h"
+#include "prlink.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsRenderingContext.h"
+#include "nsGkAtoms.h"
+#include "nsAttrValueInlines.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"
+
+#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;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
+ nsIObserver)
+
+static int gLastGdkError;
+
+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);
+
+ memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
+ memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
+}
+
+nsNativeThemeGTK::~nsNativeThemeGTK() {
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ moz_gtk_shutdown();
+ } else {
+ NS_NOTREACHED("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+void
+nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame)
+{
+ nsIPresShell *shell = GetPresShell(aFrame);
+ if (!shell)
+ return;
+
+ nsViewManager* vm = shell->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(uint8_t* aDisabledVector, uint8_t aWidgetType) {
+ return (aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7))) != 0;
+}
+
+static void SetWidgetTypeDisabled(uint8_t* aDisabledVector, uint8_t aWidgetType) {
+ aDisabledVector[aWidgetType >> 3] |= (1 << (aWidgetType & 7));
+}
+
+static inline uint16_t
+GetWidgetStateKey(uint8_t aWidgetType, GtkWidgetState *aWidgetState)
+{
+ return (aWidgetState->active |
+ aWidgetState->focused << 1 |
+ aWidgetState->inHover << 2 |
+ aWidgetState->disabled << 3 |
+ aWidgetState->isDefault << 4 |
+ aWidgetType << 5);
+}
+
+static bool IsWidgetStateSafe(uint8_t* aSafeVector,
+ uint8_t aWidgetType,
+ GtkWidgetState *aWidgetState)
+{
+ uint8_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
+ return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
+}
+
+static void SetWidgetStateSafe(uint8_t *aSafeVector,
+ uint8_t aWidgetType,
+ GtkWidgetState *aWidgetState)
+{
+ uint8_t key = GetWidgetStateKey(aWidgetType, 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,
+ uint8_t aWidgetType)
+{
+ return ((aCurpos == 0 && (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT))
+ || (aCurpos == aMaxpos && (aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT)));
+}
+
+bool
+nsNativeThemeGTK::GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState,
+ gint* aWidgetFlags)
+{
+ if (aState) {
+ // For XUL checkboxes and radio buttons, the state of the parent
+ // determines our state.
+ nsIFrame *stateFrame = aFrame;
+ if (aFrame && ((aWidgetFlags && (aWidgetType == NS_THEME_CHECKBOX ||
+ aWidgetType == NS_THEME_RADIO)) ||
+ aWidgetType == NS_THEME_CHECKBOX_LABEL ||
+ aWidgetType == NS_THEME_RADIO_LABEL)) {
+
+ nsIAtom* atom = nullptr;
+ if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
+ if (aWidgetType == NS_THEME_CHECKBOX_LABEL ||
+ aWidgetType == NS_THEME_RADIO_LABEL) {
+ // 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 = (aWidgetType == NS_THEME_CHECKBOX ||
+ aWidgetType == NS_THEME_CHECKBOX_LABEL) ? nsGkAtoms::checked
+ : nsGkAtoms::selected;
+ }
+ *aWidgetFlags = CheckBooleanAttr(aFrame, atom);
+ }
+ } else {
+ if (aWidgetFlags) {
+ nsCOMPtr<nsIDOMHTMLInputElement> inputElt(do_QueryInterface(aFrame->GetContent()));
+ *aWidgetFlags = 0;
+ if (inputElt) {
+ bool isHTMLChecked;
+ inputElt->GetChecked(&isHTMLChecked);
+ if (isHTMLChecked)
+ *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
+ }
+
+ if (GetIndeterminate(aFrame))
+ *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT;
+ }
+ }
+ } else if (aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
+ aWidgetType == NS_THEME_TREEHEADERSORTARROW ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_DOWN) {
+ // The state of an arrow comes from its parent.
+ stateFrame = aFrame = aFrame->GetParent();
+ }
+
+ EventStates eventState = GetContentState(stateFrame, aWidgetType);
+
+ 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
+ aState->depressed = FALSE;
+
+ if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
+ 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 (aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_TOOLBARBUTTON ||
+ aWidgetType == NS_THEME_DUALBUTTON ||
+ aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
+ aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_MENULIST_BUTTON) {
+ aState->active &= aState->inHover;
+ }
+
+ 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 (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
+ aWidgetType == NS_THEME_MENULIST_TEXTFIELD ||
+ aWidgetType == NS_THEME_SPINNER_TEXTFIELD ||
+ aWidgetType == NS_THEME_RADIO_CONTAINER ||
+ aWidgetType == NS_THEME_RADIO_LABEL) {
+ aState->focused = IsFocused(aFrame);
+ } else if (aWidgetType == NS_THEME_RADIO ||
+ aWidgetType == NS_THEME_CHECKBOX) {
+ // In XUL, checkboxes and radios shouldn't have focus rings, their labels do
+ aState->focused = FALSE;
+ }
+
+ if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL ||
+ aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) {
+ // 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 (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) {
+ // 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, aWidgetType)) {
+ 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 (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP < 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 (aWidgetType == NS_THEME_MENUITEM ||
+ aWidgetType == NS_THEME_CHECKMENUITEM ||
+ aWidgetType == NS_THEME_RADIOMENUITEM ||
+ aWidgetType == NS_THEME_MENUSEPARATOR ||
+ aWidgetType == NS_THEME_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 (aWidgetType == NS_THEME_CHECKMENUITEM ||
+ aWidgetType == NS_THEME_RADIOMENUITEM) {
+ *aWidgetFlags = 0;
+ if (aFrame && aFrame->GetContent()) {
+ *aWidgetFlags = aFrame->GetContent()->
+ 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 (aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_TOOLBARBUTTON ||
+ aWidgetType == NS_THEME_DUALBUTTON ||
+ aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
+ aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_MENULIST_BUTTON) {
+ 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 (aWidgetType == NS_THEME_MENULIST_BUTTON && aWidgetFlags) {
+ *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused);
+ }
+ }
+ }
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_RELIEF_NORMAL;
+ aGtkWidgetType = MOZ_GTK_BUTTON;
+ break;
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_DUALBUTTON:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_RELIEF_NONE;
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON;
+ break;
+ case NS_THEME_FOCUS_OUTLINE:
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+ break;
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ aGtkWidgetType = (aWidgetType == NS_THEME_RADIO) ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON;
+ break;
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_VERTICAL;
+ if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_HORIZONTAL;
+ if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL;
+ break;
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ break;
+ case NS_THEME_SPINNER:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON;
+ break;
+ case NS_THEME_SPINNER_UPBUTTON:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP;
+ break;
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN;
+ break;
+ case NS_THEME_SPINNER_TEXTFIELD:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY;
+ break;
+ case NS_THEME_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 NS_THEME_RANGE_THUMB:
+ {
+ 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 NS_THEME_SCALE_HORIZONTAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
+ break;
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
+ break;
+ case NS_THEME_SCALE_VERTICAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
+ break;
+ case NS_THEME_SEPARATOR:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR;
+ break;
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
+ break;
+ case NS_THEME_TOOLBARGRIPPER:
+ aGtkWidgetType = MOZ_GTK_GRIPPER;
+ break;
+ case NS_THEME_RESIZER:
+ aGtkWidgetType = MOZ_GTK_RESIZER;
+ break;
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+ break;
+ case NS_THEME_TEXTFIELD_MULTILINE:
+#if (MOZ_WIDGET_GTK == 3)
+ aGtkWidgetType = MOZ_GTK_TEXT_VIEW;
+#else
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+#endif
+ break;
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW;
+ break;
+ case NS_THEME_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 NS_THEME_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 NS_THEME_TREETWISTY:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_EXPANDER_COLLAPSED;
+ break;
+ case NS_THEME_TREETWISTYOPEN:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags)
+ *aWidgetFlags = GTK_EXPANDER_EXPANDED;
+ break;
+ case NS_THEME_MENULIST:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN;
+ if (aWidgetFlags)
+ *aWidgetFlags = IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML);
+ break;
+ case NS_THEME_MENULIST_TEXT:
+ return false; // nothing to do, but prevents the bg from being drawn
+ case NS_THEME_MENULIST_TEXTFIELD:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN_ENTRY;
+ break;
+ case NS_THEME_MENULIST_BUTTON:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW;
+ break;
+ case NS_THEME_TOOLBARBUTTON_DROPDOWN:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_NEXT:
+ case NS_THEME_BUTTON_ARROW_PREVIOUS:
+ aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW;
+ if (aWidgetFlags) {
+ *aWidgetFlags = GTK_ARROW_DOWN;
+
+ if (aWidgetType == NS_THEME_BUTTON_ARROW_UP)
+ *aWidgetFlags = GTK_ARROW_UP;
+ else if (aWidgetType == NS_THEME_BUTTON_ARROW_NEXT)
+ *aWidgetFlags = GTK_ARROW_RIGHT;
+ else if (aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
+ *aWidgetFlags = GTK_ARROW_LEFT;
+ }
+ break;
+ case NS_THEME_CHECKBOX_CONTAINER:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER;
+ break;
+ case NS_THEME_RADIO_CONTAINER:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER;
+ break;
+ case NS_THEME_CHECKBOX_LABEL:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL;
+ break;
+ case NS_THEME_RADIO_LABEL:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL;
+ break;
+ case NS_THEME_TOOLBAR:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR;
+ break;
+ case NS_THEME_TOOLTIP:
+ aGtkWidgetType = MOZ_GTK_TOOLTIP;
+ break;
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ aGtkWidgetType = MOZ_GTK_FRAME;
+ break;
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
+ break;
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ EventStates eventStates = GetContentState(stateFrame, aWidgetType);
+
+ aGtkWidgetType = IsIndeterminateProgress(stateFrame, eventStates)
+ ? IsVerticalProgress(stateFrame)
+ ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK;
+ }
+ break;
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ if (aWidgetFlags)
+ *aWidgetFlags = aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK ?
+ GTK_ARROW_LEFT : GTK_ARROW_RIGHT;
+ aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW;
+ break;
+ case NS_THEME_TABPANELS:
+ aGtkWidgetType = MOZ_GTK_TABPANELS;
+ break;
+ case NS_THEME_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 NS_THEME_SPLITTER:
+ if (IsHorizontal(aFrame))
+ aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL;
+ else
+ aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL;
+ break;
+ case NS_THEME_MENUBAR:
+ aGtkWidgetType = MOZ_GTK_MENUBAR;
+ break;
+ case NS_THEME_MENUPOPUP:
+ aGtkWidgetType = MOZ_GTK_MENUPOPUP;
+ break;
+ case NS_THEME_MENUITEM:
+ {
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame && menuFrame->IsOnMenuBar()) {
+ aGtkWidgetType = MOZ_GTK_MENUBARITEM;
+ break;
+ }
+ }
+ aGtkWidgetType = MOZ_GTK_MENUITEM;
+ break;
+ case NS_THEME_MENUSEPARATOR:
+ aGtkWidgetType = MOZ_GTK_MENUSEPARATOR;
+ break;
+ case NS_THEME_MENUARROW:
+ aGtkWidgetType = MOZ_GTK_MENUARROW;
+ break;
+ case NS_THEME_CHECKMENUITEM:
+ aGtkWidgetType = MOZ_GTK_CHECKMENUITEM;
+ break;
+ case NS_THEME_RADIOMENUITEM:
+ aGtkWidgetType = MOZ_GTK_RADIOMENUITEM;
+ break;
+ case NS_THEME_WINDOW:
+ case NS_THEME_DIALOG:
+ aGtkWidgetType = MOZ_GTK_WINDOW;
+ break;
+ case NS_THEME_GTK_INFO_BAR:
+ aGtkWidgetType = MOZ_GTK_INFO_BAR;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+#if (MOZ_WIDGET_GTK == 2)
+class ThemeRenderer : public gfxGdkNativeRenderer {
+public:
+ ThemeRenderer(GtkWidgetState aState, WidgetNodeType aGTKWidgetType,
+ gint aFlags, GtkTextDirection aDirection,
+ const GdkRectangle& aGDKRect, const GdkRectangle& aGDKClip)
+ : mState(aState), mGTKWidgetType(aGTKWidgetType), mFlags(aFlags),
+ mDirection(aDirection), mGDKRect(aGDKRect), mGDKClip(aGDKClip) {}
+ nsresult DrawWithGDK(GdkDrawable * drawable, gint offsetX, gint offsetY,
+ GdkRectangle * clipRects, uint32_t numClipRects);
+private:
+ GtkWidgetState mState;
+ WidgetNodeType mGTKWidgetType;
+ gint mFlags;
+ GtkTextDirection mDirection;
+ const GdkRectangle& mGDKRect;
+ const GdkRectangle& mGDKClip;
+};
+
+nsresult
+ThemeRenderer::DrawWithGDK(GdkDrawable * drawable, gint offsetX,
+ gint offsetY, GdkRectangle * clipRects, uint32_t numClipRects)
+{
+ GdkRectangle gdk_rect = mGDKRect;
+ gdk_rect.x += offsetX;
+ gdk_rect.y += offsetY;
+
+ GdkRectangle gdk_clip = mGDKClip;
+ gdk_clip.x += offsetX;
+ gdk_clip.y += offsetY;
+
+ GdkRectangle surfaceRect;
+ surfaceRect.x = 0;
+ surfaceRect.y = 0;
+ gdk_drawable_get_size(drawable, &surfaceRect.width, &surfaceRect.height);
+ gdk_rectangle_intersect(&gdk_clip, &surfaceRect, &gdk_clip);
+
+ NS_ASSERTION(numClipRects == 0, "We don't support clipping!!!");
+ moz_gtk_widget_paint(mGTKWidgetType, drawable, &gdk_rect, &gdk_clip,
+ &mState, mFlags, mDirection);
+
+ return NS_OK;
+}
+#else
+class SystemCairoClipper : public ClipExporter {
+public:
+ explicit SystemCairoClipper(cairo_t* aContext) : mContext(aContext)
+ {
+ }
+
+ void
+ BeginClip(const Matrix& aTransform) override
+ {
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(aTransform, mat);
+ cairo_set_matrix(mContext, &mat);
+
+ cairo_new_path(mContext);
+ }
+
+ void
+ MoveTo(const Point &aPoint) override
+ {
+ cairo_move_to(mContext, aPoint.x, aPoint.y);
+ mCurrentPoint = aPoint;
+ }
+
+ void
+ LineTo(const Point &aPoint) override
+ {
+ cairo_line_to(mContext, aPoint.x, aPoint.y);
+ mCurrentPoint = aPoint;
+ }
+
+ void
+ BezierTo(const Point &aCP1, const Point &aCP2, const Point &aCP3) override
+ {
+ cairo_curve_to(mContext, aCP1.x, aCP1.y, aCP2.x, aCP2.y, aCP3.x, aCP3.y);
+ 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, CP1.y, CP2.x, CP2.y, CP3.x, CP3.y);
+ 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);
+ }
+
+ void
+ EndClip() override
+ {
+ cairo_clip(mContext);
+ }
+
+ Point
+ CurrentPoint() const override
+ {
+ return mCurrentPoint;
+ }
+
+private:
+ cairo_t* mContext;
+ Point mCurrentPoint;
+};
+
+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)
+{
+ Point drawOffset;
+ Matrix transform;
+ if (!aSnapped) {
+ // If we are not snapped, we depend on the DT for translation.
+ drawOffset = aDrawOrigin;
+ transform = aDrawTarget->GetTransform().PreTranslate(aDrawOrigin);
+ } else {
+ // Otherwise, we only need to take the device offset into account.
+ drawOffset = aDrawOrigin - aContext->GetDeviceOffset();
+ transform = Matrix::Translation(drawOffset);
+ }
+
+ if (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);
+
+#ifndef MOZ_TREE_CAIRO
+ // Directly use the Cairo draw target to render the widget if using system Cairo everywhere.
+ BorrowedCairoContext borrowCairo(aDrawTarget);
+ if (borrowCairo.mCairo) {
+ cairo_set_matrix(borrowCairo.mCairo, &mat);
+
+ cairo_new_path(borrowCairo.mCairo);
+ cairo_rectangle(borrowCairo.mCairo, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(borrowCairo.mCairo);
+
+ moz_gtk_widget_paint(aGTKWidgetType, borrowCairo.mCairo, &aGDKRect, &aState, aFlags, aDirection);
+
+ borrowCairo.Finish();
+ return;
+ }
+#endif
+
+ // 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 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 (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);
+ 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) {
+ 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) {
+ if (!aSnapped || aTransparency != nsITheme::eOpaque) {
+ // The widget either needs to be masked or has transparency, so use the slower drawing path.
+ aDrawTarget->DrawSurface(dataSurface,
+ Rect(aSnapped ? drawOffset - aDrawTarget->GetTransform().GetTranslation() : drawOffset,
+ Size(aDrawSize)),
+ Rect(0, 0, aDrawSize.width, aDrawSize.height));
+ } else {
+ // The widget is a simple opaque rectangle, so just copy it out.
+ aDrawTarget->CopySurface(dataSurface,
+ IntRect(0, 0, aDrawSize.width, aDrawSize.height),
+ TruncatedToInt(drawOffset));
+ }
+
+ cairo_destroy(cr);
+ }
+
+ if (surf) {
+ cairo_surface_destroy(surf);
+ }
+ }
+ }
+}
+#endif
+
+bool
+nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType,
+ 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 (aWidgetType) {
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ aExtra->top = aExtra->bottom = 1;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ aExtra->left = aExtra->right = 1;
+ break;
+
+ // Include the indicator spacing (the padding around the control).
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ {
+ gint indicator_size, indicator_spacing;
+
+ if (aWidgetType == NS_THEME_CHECKBOX) {
+ moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
+ } else {
+ moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
+ }
+
+ aExtra->top = indicator_spacing;
+ aExtra->right = indicator_spacing;
+ aExtra->bottom = indicator_spacing;
+ aExtra->left = indicator_spacing;
+ break;
+ }
+ case NS_THEME_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 NS_THEME_FOCUS_OUTLINE:
+ {
+ moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top);
+ aExtra->right = aExtra->left;
+ aExtra->bottom = aExtra->top;
+ break;
+ }
+ case NS_THEME_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 = nsScreenGtk::GetGtkMonitorScaleFactor();
+ aExtra->top *= scale;
+ aExtra->right *= scale;
+ aExtra->bottom *= scale;
+ aExtra->left *= scale;
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ GtkWidgetState state;
+ WidgetNodeType gtkWidgetType;
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ gint flags;
+ if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, &state,
+ &flags))
+ return NS_OK;
+
+ gfxContext* ctx = aContext->ThebesContext();
+ nsPresContext *presContext = aFrame->PresContext();
+
+ gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
+ gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
+ gint scaleFactor = nsScreenGtk::GetGtkMonitorScaleFactor();
+
+ // 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, aWidgetType, &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, aWidgetType),
+ "Trying to render an unsafe widget!");
+
+ bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
+ if (!safeState) {
+ gLastGdkError = 0;
+ gdk_error_trap_push ();
+ }
+
+ Transparency transparency = GetWidgetTransparency(aFrame, aWidgetType);
+
+ // gdk rectangles are wrt the drawing rect.
+ GdkRectangle gdk_rect = {-drawingRect.x/scaleFactor,
+ -drawingRect.y/scaleFactor,
+ widgetRect.width/scaleFactor,
+ widgetRect.height/scaleFactor};
+
+ // translate everything so (0,0) is the top left of the drawingRect
+ gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft();
+
+#if (MOZ_WIDGET_GTK == 2)
+ gfxContextAutoSaveRestore autoSR(ctx);
+ gfxMatrix matrix;
+ if (!snapped) { // else rects are in device coords
+ matrix = ctx->CurrentMatrix();
+ }
+ matrix.Translate(origin);
+ matrix.Scale(scaleFactor, scaleFactor); // Draw in GDK coords
+ ctx->SetMatrix(matrix);
+
+ // The gdk_clip is just advisory here, meaning "you don't
+ // need to draw outside this rect if you don't feel like it!"
+ GdkRectangle gdk_clip = {0, 0, drawingRect.width, drawingRect.height};
+
+ ThemeRenderer renderer(state, gtkWidgetType, flags, direction,
+ gdk_rect, gdk_clip);
+
+ // Some themes (e.g. Clearlooks) just don't clip properly to any
+ // clip rect we provide, so we cannot advertise support for clipping within
+ // the widget bounds.
+ uint32_t rendererFlags = 0;
+ if (transparency == eOpaque) {
+ rendererFlags |= gfxGdkNativeRenderer::DRAW_IS_OPAQUE;
+ }
+
+ // GtkStyles (used by the widget drawing backend) are created for a
+ // particular colormap/visual.
+ GdkColormap* colormap = moz_gtk_widget_get_colormap();
+
+ renderer.Draw(ctx, drawingRect.Size(), rendererFlags, colormap);
+#else
+ DrawThemeWithCairo(ctx, aContext->GetDrawTarget(),
+ state, gtkWidgetType, flags, direction, scaleFactor,
+ snapped, ToPoint(origin), drawingRect.Size(),
+ gdk_rect, transparency);
+#endif
+
+ if (!safeState) {
+ 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",
+ aWidgetType, gLastGdkError, state.active, state.focused,
+ state.inHover, state.disabled);
+#endif
+ NS_WARNING("GTK theme failed; disabling unsafe widget");
+ SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType);
+ // 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, aWidgetType, &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;
+}
+
+WidgetNodeType
+nsNativeThemeGTK::NativeThemeToGtkTheme(uint8_t aWidgetType, nsIFrame* aFrame)
+{
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+
+ if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags))
+ {
+ MOZ_ASSERT_UNREACHABLE("Unknown native widget to gtk widget mapping");
+ return MOZ_GTK_WINDOW;
+ }
+ return gtkWidgetType;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType, nsIntMargin* aResult)
+{
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ aResult->top = aResult->left = aResult->right = aResult->bottom = 0;
+ switch (aWidgetType) {
+ case NS_THEME_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 NS_THEME_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 NS_THEME_TAB:
+ {
+ WidgetNodeType gtkWidgetType;
+ gint flags;
+
+ if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ &flags))
+ return NS_OK;
+
+ moz_gtk_get_tab_border(&aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, direction,
+ (GtkTabFlags)flags, gtkWidgetType);
+ }
+ break;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_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;
+ MOZ_FALLTHROUGH;
+ default:
+ {
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+ if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags)) {
+ moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, direction,
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML));
+ }
+ }
+ }
+
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ aResult->top *= scale;
+ aResult->right *= scale;
+ aResult->bottom *= scale;
+ aResult->left *= scale;
+ return NS_OK;
+}
+
+bool
+nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON_FOCUS:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_DUALBUTTON:
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_TOOLBARBUTTON_DROPDOWN:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_ARROW_NEXT:
+ case NS_THEME_BUTTON_ARROW_PREVIOUS:
+ case NS_THEME_RANGE_THUMB:
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ {
+ // Menubar and menulist have their padding specified in CSS.
+ if (!IsRegularMenuItem(aFrame))
+ return false;
+
+ aResult->SizeTo(0, 0, 0, 0);
+ WidgetNodeType gtkWidgetType;
+ if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
+ nullptr)) {
+ moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, GetTextDirection(aFrame),
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML));
+ }
+
+ gint horizontal_padding;
+
+ if (aWidgetType == NS_THEME_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 = nsScreenGtk::GetGtkMonitorScaleFactor();
+ aResult->top *= scale;
+ aResult->right *= scale;
+ aResult->bottom *= scale;
+ aResult->left *= scale;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ nsRect* aOverflowRect)
+{
+ nsIntMargin extraSize;
+ if (!GetExtraSizeForWidget(aFrame, aWidgetType, &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, uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable)
+{
+ aResult->width = aResult->height = 0;
+ *aIsOverridable = true;
+
+ switch (aWidgetType) {
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ moz_gtk_get_widget_min_size(MOZ_GTK_SCROLLBAR_BUTTON,
+ &(aResult->width), &(aResult->height));
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ aResult->width = metrics.slider_width;
+ aResult->height = metrics.stepper_size;
+ }
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ moz_gtk_get_widget_min_size(MOZ_GTK_SCROLLBAR_BUTTON,
+ &(aResult->width), &(aResult->height));
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ aResult->width = metrics.stepper_size;
+ aResult->height = metrics.slider_width;
+ }
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_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 NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ {
+ /* 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. */
+ if (gtk_check_version(3,20,0) == nullptr) {
+ // Thumb min dimensions to start with
+ WidgetNodeType thumbType = aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ?
+ MOZ_GTK_SCROLLBAR_THUMB_VERTICAL : MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ moz_gtk_get_widget_min_size(thumbType, &(aResult->width), &(aResult->height));
+
+ // Add scrollbar's borders
+ nsIntMargin border;
+ nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, aWidgetType, &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+
+ // Add track's borders
+ uint8_t trackType = aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ?
+ NS_THEME_SCROLLBARTRACK_VERTICAL : NS_THEME_SCROLLBARTRACK_HORIZONTAL;
+ nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, trackType, &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ // Require room for the slider in the track if we don't have buttons.
+ bool hasScrollbarButtons = moz_gtk_has_scrollbar_buttons();
+
+ if (aWidgetType == NS_THEME_SCROLLBAR_VERTICAL) {
+ aResult->width = metrics.slider_width + 2 * metrics.trough_border;
+ if (!hasScrollbarButtons)
+ aResult->height = metrics.min_slider_size + 2 * metrics.trough_border;
+ } else {
+ aResult->height = metrics.slider_width + 2 * metrics.trough_border;
+ if (!hasScrollbarButtons)
+ aResult->width = metrics.min_slider_size + 2 * metrics.trough_border;
+ }
+ *aIsOverridable = false;
+ }
+
+ }
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ {
+ if (gtk_check_version(3,20,0) == nullptr) {
+ moz_gtk_get_widget_min_size(NativeThemeToGtkTheme(aWidgetType, aFrame),
+ &(aResult->width), &(aResult->height));
+ } else {
+ MozGtkScrollbarMetrics metrics;
+ moz_gtk_get_scrollbar_metrics(&metrics);
+
+ if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL) {
+ aResult->width = metrics.slider_width;
+ aResult->height = metrics.min_slider_size;
+ } else {
+ aResult->height = metrics.slider_width;
+ aResult->width = metrics.min_slider_size;
+ }
+ }
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_RANGE_THUMB:
+ {
+ 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 NS_THEME_RANGE:
+ {
+ gint scale_width, scale_height;
+
+ moz_gtk_get_scale_metrics(IsRangeHorizontal(aFrame) ?
+ GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL,
+ &scale_width, &scale_height);
+ aResult->width = scale_width;
+ aResult->height = scale_height;
+
+ *aIsOverridable = true;
+ }
+ break;
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ {
+ gint thumb_length, thumb_height;
+
+ if (aWidgetType == NS_THEME_SCALETHUMB_VERTICAL) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_length, &thumb_height);
+ aResult->width = thumb_height;
+ aResult->height = thumb_length;
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_length, &thumb_height);
+ aResult->width = thumb_length;
+ aResult->height = thumb_height;
+ }
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ {
+ moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height);
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_MENULIST_BUTTON:
+ {
+ moz_gtk_get_combo_box_entry_button_size(&aResult->width,
+ &aResult->height);
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_MENUSEPARATOR:
+ {
+ gint separator_height;
+
+ moz_gtk_get_menu_separator_height(&separator_height);
+ aResult->height = separator_height;
+
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ {
+ gint indicator_size, indicator_spacing;
+
+ if (aWidgetType == NS_THEME_CHECKBOX) {
+ moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing);
+ } else {
+ moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing);
+ }
+
+ // Include space for the indicator and the padding around it.
+ aResult->width = indicator_size;
+ aResult->height = indicator_size;
+ }
+ break;
+ case NS_THEME_TOOLBARBUTTON_DROPDOWN:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_ARROW_NEXT:
+ case NS_THEME_BUTTON_ARROW_PREVIOUS:
+ {
+ moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW,
+ &aResult->width, &aResult->height);
+ *aIsOverridable = false;
+ }
+ break;
+ case NS_THEME_CHECKBOX_CONTAINER:
+ case NS_THEME_RADIO_CONTAINER:
+ case NS_THEME_CHECKBOX_LABEL:
+ case NS_THEME_RADIO_LABEL:
+ case NS_THEME_BUTTON:
+ case NS_THEME_MENULIST:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_TREEHEADERCELL:
+ {
+ if (aWidgetType == NS_THEME_MENULIST) {
+ // 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.
+
+ nsIntMargin border;
+ nsNativeThemeGTK::GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, aWidgetType, &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+ }
+ break;
+#if (MOZ_WIDGET_GTK == 3)
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ {
+ moz_gtk_get_entry_min_height(&aResult->height);
+ }
+ break;
+#endif
+ case NS_THEME_SEPARATOR:
+ {
+ gint separator_width;
+
+ moz_gtk_get_toolbar_separator_width(&separator_width);
+
+ aResult->width = separator_width;
+ }
+ break;
+ case NS_THEME_SPINNER:
+ // hard code these sizes
+ aResult->width = 14;
+ aResult->height = 26;
+ break;
+ case NS_THEME_TREEHEADERSORTARROW:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ // hard code these sizes
+ aResult->width = 14;
+ aResult->height = 13;
+ break;
+ case NS_THEME_RESIZER:
+ // same as Windows to make our lives easier
+ aResult->width = aResult->height = 15;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_TREETWISTY:
+ case NS_THEME_TREETWISTYOPEN:
+ {
+ gint expander_size;
+
+ moz_gtk_get_treeview_expander_size(&expander_size);
+ aResult->width = aResult->height = expander_size;
+ *aIsOverridable = false;
+ }
+ break;
+ }
+
+ *aResult = *aResult * nsScreenGtk::GetGtkMonitorScaleFactor();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue)
+{
+ // Some widget types just never change state.
+ if (aWidgetType == NS_THEME_TOOLBOX ||
+ aWidgetType == NS_THEME_TOOLBAR ||
+ aWidgetType == NS_THEME_STATUSBAR ||
+ aWidgetType == NS_THEME_STATUSBARPANEL ||
+ aWidgetType == NS_THEME_RESIZERPANEL ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
+ aWidgetType == NS_THEME_PROGRESSBAR ||
+ aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL ||
+ aWidgetType == NS_THEME_MENUBAR ||
+ aWidgetType == NS_THEME_MENUPOPUP ||
+ aWidgetType == NS_THEME_TOOLTIP ||
+ aWidgetType == NS_THEME_MENUSEPARATOR ||
+ aWidgetType == NS_THEME_WINDOW ||
+ aWidgetType == NS_THEME_DIALOG) {
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ if ((aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL ||
+ aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) &&
+ aAttribute == nsGkAtoms::active) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ if ((aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
+ aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) &&
+ (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, aWidgetType);
+ bool disabledNow = ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType);
+ *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));
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType))
+ return false;
+
+ switch (aWidgetType) {
+ // Combobox dropdowns don't support native theming in vertical mode.
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXT:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ MOZ_FALLTHROUGH;
+
+ case NS_THEME_BUTTON:
+ case NS_THEME_BUTTON_FOCUS:
+ case NS_THEME_RADIO:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_TOOLBOX: // N/A
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_DUALBUTTON: // so we can override the border with 0
+ case NS_THEME_TOOLBARBUTTON_DROPDOWN:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_ARROW_NEXT:
+ case NS_THEME_BUTTON_ARROW_PREVIOUS:
+ case NS_THEME_SEPARATOR:
+ case NS_THEME_TOOLBARGRIPPER:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_RESIZER:
+ case NS_THEME_LISTBOX:
+ // case NS_THEME_LISTITEM:
+ case NS_THEME_TREEVIEW:
+ // case NS_THEME_TREEITEM:
+ case NS_THEME_TREETWISTY:
+ // case NS_THEME_TREELINE:
+ // case NS_THEME_TREEHEADER:
+ case NS_THEME_TREEHEADERCELL:
+ case NS_THEME_TREEHEADERSORTARROW:
+ case NS_THEME_TREETWISTYOPEN:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TAB:
+ // case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TAB_SCROLL_ARROW_BACK:
+ case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_SPINNER_TEXTFIELD:
+ // case NS_THEME_SCROLLBAR: (n/a for gtk)
+ // case NS_THEME_SCROLLBAR_SMALL: (n/a for gtk)
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ // case NS_THEME_SCALETHUMBSTART:
+ // case NS_THEME_SCALETHUMBEND:
+ // case NS_THEME_SCALETHUMBTICK:
+ case NS_THEME_CHECKBOX_CONTAINER:
+ case NS_THEME_RADIO_CONTAINER:
+ case NS_THEME_CHECKBOX_LABEL:
+ case NS_THEME_RADIO_LABEL:
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ case NS_THEME_SPLITTER:
+ case NS_THEME_WINDOW:
+ case NS_THEME_DIALOG:
+#if (MOZ_WIDGET_GTK == 3)
+ case NS_THEME_GTK_INFO_BAR:
+#endif
+ return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
+
+ case NS_THEME_MENULIST_BUTTON:
+ 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, aWidgetType);
+
+ case NS_THEME_FOCUS_OUTLINE:
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::WidgetIsContainer(uint8_t aWidgetType)
+{
+ // XXXdwh At some point flesh all of this out.
+ if (aWidgetType == NS_THEME_MENULIST_BUTTON ||
+ aWidgetType == NS_THEME_RADIO ||
+ aWidgetType == NS_THEME_RANGE_THUMB ||
+ aWidgetType == NS_THEME_CHECKBOX ||
+ aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK ||
+ aWidgetType == NS_THEME_TAB_SCROLL_ARROW_FORWARD ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_DOWN ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
+ aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
+ return false;
+ return true;
+}
+
+bool
+nsNativeThemeGTK::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
+{
+ if (aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_TREEHEADERCELL)
+ return true;
+
+ return false;
+}
+
+bool
+nsNativeThemeGTK::ThemeNeedsComboboxDropmarker()
+{
+ return false;
+}
+
+nsITheme::Transparency
+nsNativeThemeGTK::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ // These widgets always draw a default background.
+#if (MOZ_WIDGET_GTK == 2)
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_MENUBAR:
+#endif
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_WINDOW:
+ case NS_THEME_DIALOG:
+ return eOpaque;
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+#if (MOZ_WIDGET_GTK == 3)
+ // 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;
+#endif
+ return eOpaque;
+ // Tooltips use gtk_paint_flat_box() on Gtk2
+ // but are shaped on Gtk3
+ case NS_THEME_TOOLTIP:
+#if (MOZ_WIDGET_GTK == 2)
+ return eOpaque;
+#else
+ return eTransparent;
+#endif
+ }
+
+ return eUnknownTransparency;
+}
diff --git a/widget/gtk/nsNativeThemeGTK.h b/widget/gtk/nsNativeThemeGTK.h
new file mode 100644
index 000000000..56ae0317f
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.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 _GTK_NSNATIVETHEMEGTK_H_
+#define _GTK_NSNATIVETHEMEGTK_H_
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsIObserver.h"
+#include "nsNativeTheme.h"
+
+#include <gtk/gtk.h>
+#include "gtkdrawing.h"
+
+class nsNativeThemeGTK: private nsNativeTheme,
+ public nsITheme,
+ public nsIObserver {
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIOBSERVER
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+
+ NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame, uint8_t aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute,
+ bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+
+ NS_IMETHOD ThemeChanged() override;
+
+ NS_IMETHOD_(bool) ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+
+ NS_IMETHOD_(bool) WidgetIsContainer(uint8_t aWidgetType) override;
+
+ NS_IMETHOD_(bool) ThemeDrawsFocusForWidget(uint8_t aWidgetType) override;
+
+ virtual bool ThemeNeedsComboboxDropmarker() override;
+
+ virtual Transparency GetWidgetTransparency(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+
+ nsNativeThemeGTK();
+
+protected:
+ virtual ~nsNativeThemeGTK();
+
+private:
+ GtkTextDirection GetTextDirection(nsIFrame* aFrame);
+ gint GetTabMarginPixels(nsIFrame* aFrame);
+ bool GetGtkWidgetAndState(uint8_t aWidgetType, nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState, gint* aWidgetFlags);
+ bool GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIntMargin* aExtra);
+
+ void RefreshWidgetWindow(nsIFrame* aFrame);
+ WidgetNodeType NativeThemeToGtkTheme(uint8_t aWidgetType, nsIFrame* aFrame);
+
+ uint8_t mDisabledWidgetTypes[32];
+ uint8_t mSafeWidgetStates[1024]; // 256 widgets * 32 bits per widget
+ static const char* sDisabledEngines[];
+};
+
+#endif
diff --git a/widget/gtk/nsPSPrinters.cpp b/widget/gtk/nsPSPrinters.cpp
new file mode 100644
index 000000000..2d3183e9a
--- /dev/null
+++ b/widget/gtk/nsPSPrinters.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=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 "nscore.h"
+#include "nsCUPSShim.h"
+#include "nsIServiceManager.h"
+#include "nsPSPrinters.h"
+#include "nsReadableUtils.h" // StringBeginsWith()
+#include "nsCUPSShim.h"
+#include "mozilla/Preferences.h"
+
+#include "prlink.h"
+#include "prenv.h"
+#include "plstr.h"
+
+using namespace mozilla;
+
+#define NS_CUPS_PRINTER "CUPS/"
+#define NS_CUPS_PRINTER_LEN (sizeof(NS_CUPS_PRINTER) - 1)
+
+/* dummy printer name for the gfx/src/ps driver */
+#define NS_POSTSCRIPT_DRIVER_NAME "PostScript/"
+
+nsCUPSShim gCupsShim;
+
+nsPSPrinterList::nsPSPrinterList()
+{
+ // Should we try cups?
+ if (Preferences::GetBool("print.postscript.cups.enabled", true) &&
+ !gCupsShim.IsInitialized()) {
+ gCupsShim.Init();
+ }
+}
+
+
+/* Check whether the PostScript module has been disabled at runtime */
+bool
+nsPSPrinterList::Enabled()
+{
+ const char *val = PR_GetEnv("MOZILLA_POSTSCRIPT_ENABLED");
+ if (val && (val[0] == '0' || !PL_strcasecmp(val, "false")))
+ return false;
+
+ // is the PS module enabled?
+ return Preferences::GetBool("print.postscript.enabled", true);
+}
+
+
+/* Fetch a list of printers handled by the PostsScript module */
+void
+nsPSPrinterList::GetPrinterList(nsTArray<nsCString>& aList)
+{
+ aList.Clear();
+
+ // Query CUPS for a printer list. The default printer goes to the
+ // head of the output list; others are appended.
+ if (gCupsShim.IsInitialized()) {
+ cups_dest_t *dests;
+
+ int num_dests = (gCupsShim.mCupsGetDests)(&dests);
+ if (num_dests) {
+ for (int i = 0; i < num_dests; i++) {
+ nsAutoCString fullName(NS_CUPS_PRINTER);
+ fullName.Append(dests[i].name);
+ if (dests[i].instance != nullptr) {
+ fullName.Append('/');
+ fullName.Append(dests[i].instance);
+ }
+ if (dests[i].is_default)
+ aList.InsertElementAt(0, fullName);
+ else
+ aList.AppendElement(fullName);
+ }
+ }
+ (gCupsShim.mCupsFreeDests)(num_dests, dests);
+ }
+
+ // Build the "classic" list of printers -- those accessed by running
+ // an opaque command. This list always contains a printer named "default".
+ // In addition, we look for either an environment variable
+ // MOZILLA_POSTSCRIPT_PRINTER_LIST or a preference setting
+ // print.printer_list, which contains a space-separated list of printer
+ // names.
+ aList.AppendElement(
+ NS_LITERAL_CSTRING(NS_POSTSCRIPT_DRIVER_NAME "default"));
+
+ nsAutoCString list(PR_GetEnv("MOZILLA_POSTSCRIPT_PRINTER_LIST"));
+ if (list.IsEmpty()) {
+ list = Preferences::GetCString("print.printer_list");
+ }
+ if (!list.IsEmpty()) {
+ // For each printer (except "default" which was already added),
+ // construct a string "PostScript/<name>" and append it to the list.
+ char *state;
+
+ for (char *name = PL_strtok_r(list.BeginWriting(), " ", &state);
+ nullptr != name;
+ name = PL_strtok_r(nullptr, " ", &state)
+ ) {
+ if (0 != strcmp(name, "default")) {
+ nsAutoCString fullName(NS_POSTSCRIPT_DRIVER_NAME);
+ fullName.Append(name);
+ aList.AppendElement(fullName);
+ }
+ }
+ }
+}
+
+
+/* Identify the printer type */
+nsPSPrinterList::PrinterType
+nsPSPrinterList::GetPrinterType(const nsACString& aName)
+{
+ if (StringBeginsWith(aName, NS_LITERAL_CSTRING(NS_POSTSCRIPT_DRIVER_NAME)))
+ return kTypePS;
+ else if (StringBeginsWith(aName, NS_LITERAL_CSTRING(NS_CUPS_PRINTER)))
+ return kTypeCUPS;
+ else
+ return kTypeUnknown;
+}
diff --git a/widget/gtk/nsPSPrinters.h b/widget/gtk/nsPSPrinters.h
new file mode 100644
index 000000000..b584ef715
--- /dev/null
+++ b/widget/gtk/nsPSPrinters.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=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 nsPSPrinters_h___
+#define nsPSPrinters_h___
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsPSPrinterList {
+ public:
+ nsPSPrinterList();
+
+ /**
+ * Is the PostScript module enabled or disabled?
+ * @return true if enabled,
+ * false if not.
+ */
+ bool Enabled();
+
+ /**
+ * Obtain a list of printers (print destinations) supported by the
+ * PostScript module, Each entry will be in the form <type>/<name>,
+ * where <type> is a printer type string, and <name> is the actual
+ * printer name.
+ *
+ * @param aList Upon return, this is populated with the list of
+ * printer names as described above, replacing any
+ * previous contents. Each entry is a UTF8 string.
+ * There should always be at least one entry. The
+ * first entry is the default print destination.
+ */
+ void GetPrinterList(nsTArray<nsCString>& aList);
+
+ enum PrinterType {
+ kTypeUnknown, // Not actually handled by the PS module
+ kTypePS, // Generic postscript module printer
+ kTypeCUPS // CUPS printer
+ };
+
+ /**
+ * Identify a printer's type from its name.
+ * @param aName The printer's full name as a UTF8 string, including
+ * the <type> portion as described for GetPrinterList().
+ * @return The PrinterType value for this name.
+ */
+ static PrinterType GetPrinterType(const nsACString& aName);
+};
+
+#endif /* nsPSPrinters_h___ */
diff --git a/widget/gtk/nsPaperPS.cpp b/widget/gtk/nsPaperPS.cpp
new file mode 100644
index 000000000..7d583efac
--- /dev/null
+++ b/widget/gtk/nsPaperPS.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=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 "mozilla/ArrayUtils.h"
+
+#include "nsPaperPS.h"
+#include "plstr.h"
+#include "nsCoord.h"
+#include "nsMemory.h"
+
+using namespace mozilla;
+
+const nsPaperSizePS_ nsPaperSizePS::mList[] =
+{
+#define SIZE_MM(x) (x)
+#define SIZE_INCH(x) ((x) * MM_PER_INCH_FLOAT)
+ { "A5", SIZE_MM(148), SIZE_MM(210), true },
+ { "A4", SIZE_MM(210), SIZE_MM(297), true },
+ { "A3", SIZE_MM(297), SIZE_MM(420), true },
+ { "Letter", SIZE_INCH(8.5), SIZE_INCH(11), false },
+ { "Legal", SIZE_INCH(8.5), SIZE_INCH(14), false },
+ { "Tabloid", SIZE_INCH(11), SIZE_INCH(17), false },
+ { "Executive", SIZE_INCH(7.5), SIZE_INCH(10), false },
+#undef SIZE_INCH
+#undef SIZE_MM
+};
+
+const unsigned int nsPaperSizePS::mCount = ArrayLength(mList);
+
+bool
+nsPaperSizePS::Find(const char *aName)
+{
+ for (int i = mCount; i--; ) {
+ if (!PL_strcasecmp(aName, mList[i].name)) {
+ mCurrent = i;
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/widget/gtk/nsPaperPS.h b/widget/gtk/nsPaperPS.h
new file mode 100644
index 000000000..1acc1780c
--- /dev/null
+++ b/widget/gtk/nsPaperPS.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ex: set tabstop=8 softtabstop=4 shiftwidth=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 _PAPERPS_H_
+#define _PAPERPS_H_
+
+#include "nsDebug.h"
+
+struct nsPaperSizePS_ {
+ const char *name;
+ float width_mm;
+ float height_mm;
+ bool isMetric; // Present to the user in metric, if possible
+};
+
+class nsPaperSizePS {
+ public:
+ /** ---------------------------------------------------
+ * Constructor
+ */
+ nsPaperSizePS() { mCurrent = 0; }
+
+ /** ---------------------------------------------------
+ * @return true if the cursor points past the last item.
+ */
+ bool AtEnd() { return mCurrent >= mCount; }
+
+ /** ---------------------------------------------------
+ * Position the cursor at the beginning of the paper size list.
+ * @return VOID
+ */
+ void First() { mCurrent = 0; }
+
+ /** ---------------------------------------------------
+ * Advance the cursor to the next item.
+ * @return VOID
+ */
+ void Next() {
+ NS_ASSERTION(!AtEnd(), "Invalid current item");
+ mCurrent++;
+ }
+
+ /** ---------------------------------------------------
+ * Point the cursor to the entry with the given paper name.
+ * @return true if pointing to a valid entry.
+ */
+ bool Find(const char *aName);
+
+ /** ---------------------------------------------------
+ * @return a pointer to the name of the current paper size
+ */
+ const char *Name() {
+ NS_PRECONDITION(!AtEnd(), "Invalid current item");
+ return mList[mCurrent].name;
+ }
+
+ /** ---------------------------------------------------
+ * @return the width of the page in millimeters
+ */
+ float Width_mm() {
+ NS_PRECONDITION(!AtEnd(), "Invalid current item");
+ return mList[mCurrent].width_mm;
+ }
+
+ /** ---------------------------------------------------
+ * @return the height of the page in millimeters
+ */
+ float Height_mm() {
+ NS_PRECONDITION(!AtEnd(), "Invalid current item");
+ return mList[mCurrent].height_mm;
+ }
+
+ /** ---------------------------------------------------
+ * @return true if the paper should be presented to
+ * the user in metric units.
+ */
+ bool IsMetric() {
+ NS_PRECONDITION(!AtEnd(), "Invalid current item");
+ return mList[mCurrent].isMetric;
+ }
+
+ private:
+ unsigned int mCurrent;
+ static const nsPaperSizePS_ mList[];
+ static const unsigned int mCount;
+};
+
+#endif
+
diff --git a/widget/gtk/nsPrintDialogGTK.cpp b/widget/gtk/nsPrintDialogGTK.cpp
new file mode 100644
index 000000000..77fa2bb69
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.cpp
@@ -0,0 +1,609 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "mozcontainer.h"
+#include "nsIPrintSettings.h"
+#include "nsIWidget.h"
+#include "nsPrintDialogGTK.h"
+#include "nsPrintSettingsGTK.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+#include "nsIPrintSettingsService.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShell.h"
+#include "WidgetUtils.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));
+ nsXPIDLString intlString;
+
+ printBundle->GetStringFromName(u"headerFooterCustom", getter_Copies(intlString));
+ GtkWidget* prompt_dialog = gtk_dialog_new_with_buttons(NS_ConvertUTF16toUTF8(intlString).get(), printDialog,
+#if (MOZ_WIDGET_GTK == 2)
+ (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR),
+#else
+ (GtkDialogFlags)(GTK_DIALOG_MODAL),
+#endif
+ 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(u"customHeaderFooterPrompt", getter_Copies(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* radio_as_laid_out;
+ GtkWidget* radio_selected_frame;
+ GtkWidget* radio_separate_frames;
+ 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 ExportFramePrinting(nsIPrintSettings *aNS, GtkPrintSettings *aSettings);
+ 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_PAGE_SET
+ | GTK_PRINT_CAPABILITY_COPIES
+ | GTK_PRINT_CAPABILITY_COLLATE
+ | GTK_PRINT_CAPABILITY_REVERSE
+ | GTK_PRINT_CAPABILITY_SCALE
+ | GTK_PRINT_CAPABILITY_GENERATE_PDF
+ | GTK_PRINT_CAPABILITY_GENERATE_PS
+ )
+ );
+
+ // 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());
+
+ int16_t frameUIFlag;
+ aSettings->GetHowToEnableFrameUI(&frameUIFlag);
+ radio_as_laid_out = gtk_radio_button_new_with_mnemonic(nullptr, GetUTF8FromBundle("asLaidOut").get());
+ if (frameUIFlag == nsIPrintSettings::kFrameEnableNone)
+ gtk_widget_set_sensitive(radio_as_laid_out, FALSE);
+
+ radio_selected_frame = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(radio_as_laid_out),
+ GetUTF8FromBundle("selectedFrame").get());
+ if (frameUIFlag == nsIPrintSettings::kFrameEnableNone ||
+ frameUIFlag == nsIPrintSettings::kFrameEnableAsIsAndEach)
+ gtk_widget_set_sensitive(radio_selected_frame, FALSE);
+
+ radio_separate_frames = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(radio_as_laid_out),
+ GetUTF8FromBundle("separateFrames").get());
+ if (frameUIFlag == nsIPrintSettings::kFrameEnableNone)
+ gtk_widget_set_sensitive(radio_separate_frames, FALSE);
+
+ // "Print Frames" options label, bold and center-aligned
+ GtkWidget* print_frames_label = gtk_label_new(nullptr);
+ char* pangoMarkup = g_markup_printf_escaped("<b>%s</b>", GetUTF8FromBundle("printFramesTitleGTK").get());
+ gtk_label_set_markup(GTK_LABEL(print_frames_label), pangoMarkup);
+ g_free(pangoMarkup);
+ gtk_misc_set_alignment(GTK_MISC(print_frames_label), 0, 0);
+
+ // Align the radio buttons slightly so they appear to fall under the aforementioned label as per the GNOME HIG
+ GtkWidget* frames_radio_container = gtk_alignment_new(0, 0, 0, 0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(frames_radio_container), 8, 0, 12, 0);
+
+ // Radio buttons for the print frames options
+ GtkWidget* frames_radio_list = gtk_vbox_new(TRUE, 2);
+ gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_as_laid_out, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_selected_frame, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(frames_radio_list), radio_separate_frames, FALSE, FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(frames_radio_container), frames_radio_list);
+
+ // 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->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &canSelectText);
+ 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);
+ 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
+ nsXPIDLString header_footer_str[3];
+
+ aSettings->GetHeaderStrLeft(getter_Copies(header_footer_str[0]));
+ aSettings->GetHeaderStrCenter(getter_Copies(header_footer_str[1]));
+ aSettings->GetHeaderStrRight(getter_Copies(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(getter_Copies(header_footer_str[0]));
+ aSettings->GetFooterStrCenter(getter_Copies(header_footer_str[1]));
+ aSettings->GetFooterStrRight(getter_Copies(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), print_frames_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), frames_radio_container, FALSE, FALSE, 0);
+ 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)
+{
+ nsXPIDLString intlString;
+ printBundle->GetStringFromName(NS_ConvertUTF8toUTF16(aKey).get(), getter_Copies(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::ExportFramePrinting(nsIPrintSettings *aNS, GtkPrintSettings *aSettings)
+{
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_as_laid_out)))
+ aNS->SetPrintFrameType(nsIPrintSettings::kFramesAsIs);
+ else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_selected_frame)))
+ aNS->SetPrintFrameType(nsIPrintSettings::kSelectedFrame);
+ else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio_separate_frames)))
+ aNS->SetPrintFrameType(nsIPrintSettings::kEachFrameSep);
+ else
+ aNS->SetPrintFrameType(nsIPrintSettings::kNoFrames);
+}
+
+void
+nsPrintDialogWidgetGTK::ExportHeaderFooter(nsIPrintSettings *aNS)
+{
+ const char* header_footer_str;
+ header_footer_str = OptionWidgetToString(header_dropdown[0]);
+ aNS->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(header_dropdown[1]);
+ aNS->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(header_dropdown[2]);
+ aNS->SetHeaderStrRight(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[0]);
+ aNS->SetFooterStrLeft(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[1]);
+ aNS->SetFooterStrCenter(NS_ConvertUTF8toUTF16(header_footer_str).get());
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[2]);
+ aNS->SetFooterStrRight(NS_ConvertUTF8toUTF16(header_footer_str).get());
+}
+
+nsresult
+nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings *aNSSettings)
+{
+ NS_PRECONDITION(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);
+
+ aNSSettings->GetPrintBGColors(&geckoBool);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle), geckoBool);
+
+ aNSSettings->GetPrintBGImages(&geckoBool);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle), geckoBool);
+
+ 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)
+{
+ NS_PRECONDITION(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) {
+ ExportFramePrinting(aNSSettings, settings);
+ 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->SetForcePrintSelectionOnly(printSelectionOnly);
+ }
+ }
+
+ if (settings)
+ g_object_unref(settings);
+ return NS_OK;
+}
+
+GtkWidget*
+nsPrintDialogWidgetGTK::ConstructHeaderFooterDropdown(const char16_t *currentString)
+{
+#if (MOZ_WIDGET_GTK == 2)
+ GtkWidget* dropdown = gtk_combo_box_new_text();
+#else
+ GtkWidget* dropdown = gtk_combo_box_text_new();
+#endif
+ const char hf_options[][22] = {"headerFooterBlank", "headerFooterTitle",
+ "headerFooterURL", "headerFooterDate",
+ "headerFooterPage", "headerFooterPageTotal",
+ "headerFooterCustom"};
+
+ for (unsigned int i = 0; i < ArrayLength(hf_options); i++) {
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_combo_box_append_text(GTK_COMBO_BOX(dropdown), GetUTF8FromBundle(hf_options[i]).get());
+#else
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(dropdown), nullptr,
+ GetUTF8FromBundle(hf_options[i]).get());
+#endif
+ }
+
+ 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()
+{
+}
+
+nsPrintDialogServiceGTK::~nsPrintDialogServiceGTK()
+{
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aSettings,
+ nsIWebBrowserPrint *aWebBrowserPrint)
+{
+ NS_PRECONDITION(aParent, "aParent must not be null");
+ NS_PRECONDITION(aSettings, "aSettings must not be null");
+
+ 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)
+{
+ NS_PRECONDITION(aParent, "aParent must not be null");
+ NS_PRECONDITION(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) {
+ nsXPIDLString printName;
+ aNSSettings->GetPrinterName(getter_Copies(printName));
+ if (!printName) {
+ psService->GetDefaultPrinterName(getter_Copies(printName));
+ aNSSettings->SetPrinterName(printName.get());
+ }
+ psService->InitPrintSettingsFromPrefs(aNSSettings, true, nsIPrintSettings::kInitSaveAll);
+ }
+
+ GtkPrintSettings* gtkSettings = aNSSettingsGTK->GetGtkPrintSettings();
+ GtkPageSetup* oldPageSetup = aNSSettingsGTK->GetGtkPageSetup();
+
+ GtkPageSetup* newPageSetup = gtk_print_run_page_setup_dialog(gtkParent, oldPageSetup, gtkSettings);
+
+ 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::kInitSaveAll);
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsPrintDialogGTK.h b/widget/gtk/nsPrintDialogGTK.h
new file mode 100644
index 000000000..c60fa6a2b
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.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 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,
+ nsIWebBrowserPrint *aWebBrowserPrint) override;
+ NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aSettings) override;
+};
+
+#endif
diff --git a/widget/gtk/nsPrintOptionsGTK.cpp b/widget/gtk/nsPrintOptionsGTK.cpp
new file mode 100644
index 000000000..ca86a1f89
--- /dev/null
+++ b/widget/gtk/nsPrintOptionsGTK.cpp
@@ -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/. */
+
+#include "nsPrintOptionsGTK.h"
+#include "nsPrintSettingsGTK.h"
+
+using namespace mozilla::embedding;
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsWin.h
+ * @update 6/21/00 dwc
+ */
+nsPrintOptionsGTK::nsPrintOptionsGTK()
+{
+
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsImpl.h
+ * @update 6/21/00 dwc
+ */
+nsPrintOptionsGTK::~nsPrintOptionsGTK()
+{
+}
+
+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
+nsPrintOptionsGTK::SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ PrintData* data)
+{
+ nsresult rv = nsPrintOptions::SerializeToPrintData(aSettings, aWBP, 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
+nsPrintOptionsGTK::DeserializeToPrintSettings(const PrintData& data,
+ nsIPrintSettings* settings)
+{
+ nsCOMPtr<nsPrintSettingsGTK> settingsGTK(do_QueryInterface(settings));
+ NS_ENSURE_STATE(settingsGTK);
+
+ nsresult rv = nsPrintOptions::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 nsPrintOptionsGTK::_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/nsPrintOptionsGTK.h b/widget/gtk/nsPrintOptionsGTK.h
new file mode 100644
index 000000000..d558bb800
--- /dev/null
+++ b/widget/gtk/nsPrintOptionsGTK.h
@@ -0,0 +1,40 @@
+/* -*- 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 nsPrintOptionsGTK_h__
+#define nsPrintOptionsGTK_h__
+
+#include "nsPrintOptionsImpl.h"
+
+namespace mozilla
+{
+namespace embedding
+{
+ class PrintData;
+} // namespace embedding
+} // namespace mozilla
+
+//*****************************************************************************
+//*** nsPrintOptions
+//*****************************************************************************
+class nsPrintOptionsGTK : public nsPrintOptions
+{
+public:
+ nsPrintOptionsGTK();
+ virtual ~nsPrintOptionsGTK();
+
+ NS_IMETHODIMP SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ mozilla::embedding::PrintData* data);
+ NS_IMETHODIMP DeserializeToPrintSettings(const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings);
+
+ virtual nsresult _CreatePrintSettings(nsIPrintSettings **_retval);
+};
+
+
+
+#endif /* nsPrintOptions_h__ */
diff --git a/widget/gtk/nsPrintSettingsGTK.cpp b/widget/gtk/nsPrintSettingsGTK.cpp
new file mode 100644
index 000000000..a8fd60bd1
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.cpp
@@ -0,0 +1,813 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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),
+ mPrintSelectionOnly(false)
+{
+ // 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);
+}
+
+/** ---------------------------------------------------
+ */
+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),
+ mPrintSelectionOnly(false)
+{
+ *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);
+
+ mPrintSelectionOnly = rhs.mPrintSelectionOnly;
+
+ 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) {
+ const gchar* fmtGTK =
+ gtk_print_settings_get(mPrintSettings,
+ GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT);
+ if (fmtGTK) {
+ if (nsDependentCString(fmtGTK).EqualsIgnoreCase("pdf")) {
+ format = nsIPrintSettings::kOutputFormatPDF;
+ } else {
+ format = nsIPrintSettings::kOutputFormatPS;
+ }
+ } else if (GTK_IS_PRINTER(mGTKPrinter)) {
+ // Prior to gtk 2.24, gtk_printer_accepts_pdf() and
+ // gtk_printer_accepts_ps() always returned true regardless of the
+ // printer's capability.
+ bool shouldTrustGTK =
+ (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 24));
+ bool acceptsPDF = shouldTrustGTK && gtk_printer_accepts_pdf(mGTKPrinter);
+
+ format = acceptsPDF ? nsIPrintSettings::kOutputFormatPDF
+ : 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::GetPrintRange(int16_t *aPrintRange)
+{
+ NS_ENSURE_ARG_POINTER(aPrintRange);
+ if (mPrintSelectionOnly) {
+ *aPrintRange = kRangeSelection;
+ return NS_OK;
+ }
+
+ GtkPrintPages gtkRange = gtk_print_settings_get_print_pages(mPrintSettings);
+ if (gtkRange == GTK_PRINT_PAGES_RANGES)
+ *aPrintRange = kRangeSpecifiedPageRange;
+ else
+ *aPrintRange = kRangeAllPages;
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsGTK::SetPrintRange(int16_t aPrintRange)
+{
+ if (aPrintRange == kRangeSelection) {
+ mPrintSelectionOnly = true;
+ return NS_OK;
+ }
+
+ mPrintSelectionOnly = false;
+ if (aPrintRange == kRangeSpecifiedPageRange)
+ gtk_print_settings_set_print_pages(mPrintSettings, GTK_PRINT_PAGES_RANGES);
+ else
+ gtk_print_settings_set_print_pages(mPrintSettings, GTK_PRINT_PAGES_ALL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetStartPageRange(int32_t *aStartPageRange)
+{
+ gint ctRanges;
+ GtkPageRange* lstRanges = gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges);
+
+ // Make sure we got a range.
+ if (ctRanges < 1) {
+ *aStartPageRange = 1;
+ } else {
+ // GTK supports multiple page ranges; gecko only supports 1. So find
+ // the lowest start page.
+ int32_t start(lstRanges[0].start);
+ for (gint ii = 1; ii < ctRanges; ii++) {
+ start = std::min(lstRanges[ii].start, start);
+ }
+ *aStartPageRange = start + 1;
+ }
+
+ g_free(lstRanges);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetStartPageRange(int32_t aStartPageRange)
+{
+ int32_t endRange;
+ GetEndPageRange(&endRange);
+
+ GtkPageRange gtkRange;
+ gtkRange.start = aStartPageRange - 1;
+ gtkRange.end = endRange - 1;
+
+ gtk_print_settings_set_page_ranges(mPrintSettings, &gtkRange, 1);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetEndPageRange(int32_t *aEndPageRange)
+{
+ gint ctRanges;
+ GtkPageRange* lstRanges = gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges);
+
+ if (ctRanges < 1) {
+ *aEndPageRange = 1;
+ } else {
+ int32_t end(lstRanges[0].end);
+ for (gint ii = 1; ii < ctRanges; ii++) {
+ end = std::max(lstRanges[ii].end, end);
+ }
+ *aEndPageRange = end + 1;
+ }
+
+ g_free(lstRanges);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetEndPageRange(int32_t aEndPageRange)
+{
+ int32_t startRange;
+ GetStartPageRange(&startRange);
+
+ GtkPageRange gtkRange;
+ gtkRange.start = startRange - 1;
+ gtkRange.end = aEndPageRange - 1;
+
+ gtk_print_settings_set_page_ranges(mPrintSettings, &gtkRange, 1);
+
+ 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(char16_t * *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 = ToNewUnicode(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
+ nsAutoString path;
+ rv = file->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aToFileName = ToNewUnicode(path);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetToFileName(const char16_t * aToFileName)
+{
+ if (aToFileName[0] == 0) {
+ mToFileName.SetLength(0);
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI,
+ nullptr);
+ return NS_OK;
+ }
+
+ if (StringEndsWith(nsDependentString(aToFileName), NS_LITERAL_STRING(".ps"))) {
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT, "ps");
+ } else {
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT, "pdf");
+ }
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(nsDependentString(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(char16_t * *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
+ nsXPIDLString nullPrintName;
+ *aPrinter = ToNewUnicode(nullPrintName);
+ return NS_OK;
+ }
+ }
+ *aPrinter = UTF8ToNewUnicode(nsDependentCString(gtkPrintName));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrinterName(const char16_t * aPrinter)
+{
+ NS_ConvertUTF16toUTF8 gtkPrinter(aPrinter);
+
+ if (StringBeginsWith(gtkPrinter, NS_LITERAL_CSTRING("CUPS/"))) {
+ // 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::GetPaperName(char16_t * *aPaperName)
+{
+ NS_ENSURE_ARG_POINTER(aPaperName);
+ const gchar* name =
+ gtk_paper_size_get_name(gtk_page_setup_get_paper_size(mPageSetup));
+ *aPaperName = ToNewUnicode(NS_ConvertUTF8toUTF16(name));
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperName(const char16_t * aPaperName)
+{
+ NS_ConvertUTF16toUTF8 gtkPaperName(aPaperName);
+
+ // Convert these Gecko names to GTK names
+ 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);
+ *aWidth = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_width(paperSize, GTK_UNIT_INCH));
+ *aHeight = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_height(paperSize, GTK_UNIT_INCH));
+
+ 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)
+{
+ gint ctRanges;
+ GtkPageRange* lstRanges = gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges);
+
+ aPages.Clear();
+
+ if (ctRanges > 1) {
+ 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));
+ return NS_OK;
+}
+
diff --git a/widget/gtk/nsPrintSettingsGTK.h b/widget/gtk/nsPrintSettingsGTK.h
new file mode 100644
index 000000000..21f389449
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.h
@@ -0,0 +1,151 @@
+/* -*- 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();
+
+ // 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);
+
+ bool GetForcePrintSelectionOnly() { return mPrintSelectionOnly; };
+ void SetForcePrintSelectionOnly(bool aPrintSelectionOnly) { mPrintSelectionOnly = aPrintSelectionOnly; };
+
+ // If not printing the selection, this is stored in the GtkPrintSettings. Printing the
+ // selection is stored as a protected boolean (mPrintSelectionOnly).
+ NS_IMETHOD GetPrintRange(int16_t *aPrintRange) override;
+ NS_IMETHOD SetPrintRange(int16_t aPrintRange) override;
+
+ // The page range is stored as as single range in the GtkPrintSettings object.
+ NS_IMETHOD GetStartPageRange(int32_t *aStartPageRange) override;
+ NS_IMETHOD SetStartPageRange(int32_t aStartPageRange) override;
+ NS_IMETHOD GetEndPageRange(int32_t *aEndPageRange) override;
+ NS_IMETHOD SetEndPageRange(int32_t aEndPageRange) override;
+
+ // 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(char16_t * *aToFileName) override;
+ NS_IMETHOD SetToFileName(const char16_t * 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(char16_t * *aPrinter) override;
+ NS_IMETHOD SetPrinterName(const char16_t * 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 GetPaperName(char16_t * *aPaperName) override;
+ NS_IMETHOD SetPaperName(const char16_t * aPaperName) 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 GetPageRanges(nsTArray<int32_t> &aPages) 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;
+
+ bool mPrintSelectionOnly;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintSettingsGTK, NS_PRINTSETTINGSGTK_IID)
+
+
+#endif // nsPrintSettingsGTK_h_
diff --git a/widget/gtk/nsScreenGtk.cpp b/widget/gtk/nsScreenGtk.cpp
new file mode 100644
index 000000000..61e6605b7
--- /dev/null
+++ b/widget/gtk/nsScreenGtk.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsScreenGtk.h"
+
+#include "nsIWidget.h"
+
+#include <gdk/gdk.h>
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#endif
+#include <gtk/gtk.h>
+#include <dlfcn.h>
+#include "gfxPlatformGtk.h"
+
+static uint32_t sScreenId = 0;
+
+
+nsScreenGtk :: nsScreenGtk ( )
+ : mScreenNum(0),
+ mRect(0, 0, 0, 0),
+ mAvailRect(0, 0, 0, 0),
+ mId(++sScreenId)
+{
+}
+
+
+nsScreenGtk :: ~nsScreenGtk()
+{
+}
+
+
+NS_IMETHODIMP
+nsScreenGtk :: GetId(uint32_t *aId)
+{
+ *aId = mId;
+ return NS_OK;
+} // GetId
+
+
+NS_IMETHODIMP
+nsScreenGtk :: GetRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ *outLeft = mRect.x;
+ *outTop = mRect.y;
+ *outWidth = mRect.width;
+ *outHeight = mRect.height;
+
+ return NS_OK;
+
+} // GetRect
+
+
+NS_IMETHODIMP
+nsScreenGtk :: GetAvailRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ *outLeft = mAvailRect.x;
+ *outTop = mAvailRect.y;
+ *outWidth = mAvailRect.width;
+ *outHeight = mAvailRect.height;
+
+ return NS_OK;
+
+} // GetAvailRect
+
+gint
+nsScreenGtk :: GetGtkMonitorScaleFactor()
+{
+#if (MOZ_WIDGET_GTK >= 3)
+ // Since GDK 3.10
+ static auto sGdkScreenGetMonitorScaleFactorPtr = (gint (*)(GdkScreen*, gint))
+ dlsym(RTLD_DEFAULT, "gdk_screen_get_monitor_scale_factor");
+ if (sGdkScreenGetMonitorScaleFactorPtr) {
+ // FIXME: In the future, we'll want to fix this for GTK on Wayland which
+ // supports a variable scale factor per display.
+ GdkScreen *screen = gdk_screen_get_default();
+ return sGdkScreenGetMonitorScaleFactorPtr(screen, 0);
+ }
+#endif
+ return 1;
+}
+
+double
+nsScreenGtk :: GetDPIScale()
+{
+ double dpiScale = nsIWidget::DefaultScaleOverride();
+ if (dpiScale <= 0.0) {
+ dpiScale = GetGtkMonitorScaleFactor() * gfxPlatformGtk::GetDPIScale();
+ }
+ return dpiScale;
+}
+
+NS_IMETHODIMP
+nsScreenGtk :: GetPixelDepth(int32_t *aPixelDepth)
+{
+ GdkVisual * visual = gdk_screen_get_system_visual(gdk_screen_get_default());
+ *aPixelDepth = gdk_visual_get_depth(visual);
+
+ return NS_OK;
+
+} // GetPixelDepth
+
+NS_IMETHODIMP
+nsScreenGtk :: GetColorDepth(int32_t *aColorDepth)
+{
+ return GetPixelDepth ( aColorDepth );
+
+} // GetColorDepth
+
+NS_IMETHODIMP
+nsScreenGtk::GetDefaultCSSScaleFactor(double* aScaleFactor)
+{
+ *aScaleFactor = GetDPIScale();
+ return NS_OK;
+}
+
+void
+nsScreenGtk :: Init (GdkWindow *aRootWindow)
+{
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ gint width = gdk_screen_width()*scale;
+ gint height = gdk_screen_height()*scale;
+
+ // We listen for configure events on the root window to pick up
+ // changes to this rect. We could listen for "size_changed" signals
+ // on the default screen to do this, except that doesn't work with
+ // versions of GDK predating the GdkScreen object. See bug 256646.
+ mAvailRect = mRect = nsIntRect(0, 0, width, height);
+
+#ifdef MOZ_X11
+ // We need to account for the taskbar, etc in the available rect.
+ // See http://freedesktop.org/Standards/wm-spec/index.html#id2767771
+
+ // XXX do we care about _NET_WM_STRUT_PARTIAL? That will
+ // add much more complexity to the code here (our screen
+ // could have a non-rectangular shape), but should
+ // lead to greater accuracy.
+
+ long *workareas;
+ GdkAtom type_returned;
+ int format_returned;
+ int length_returned;
+
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+
+ gdk_error_trap_push();
+
+ // gdk_property_get uses (length + 3) / 4, hence G_MAXLONG - 3 here.
+ if (!gdk_property_get(aRootWindow,
+ gdk_atom_intern ("_NET_WORKAREA", FALSE),
+ cardinal_atom,
+ 0, G_MAXLONG - 3, FALSE,
+ &type_returned,
+ &format_returned,
+ &length_returned,
+ (guchar **) &workareas)) {
+ // This window manager doesn't support the freedesktop standard.
+ // Nothing we can do about it, so assume full screen size.
+ return;
+ }
+
+ // Flush the X queue to catch errors now.
+ gdk_flush();
+
+ if (!gdk_error_trap_pop() &&
+ type_returned == cardinal_atom &&
+ length_returned && (length_returned % 4) == 0 &&
+ format_returned == 32) {
+ int num_items = length_returned / sizeof(long);
+
+ for (int i = 0; i < num_items; i += 4) {
+ nsIntRect workarea(workareas[i], workareas[i + 1],
+ workareas[i + 2], workareas[i + 3]);
+ if (!mRect.Contains(workarea)) {
+ // Note that we hit this when processing screen size changes,
+ // since we'll get the configure event before the toolbars have
+ // been moved. We'll end up cleaning this up when we get the
+ // change notification to the _NET_WORKAREA property. However,
+ // we still want to listen to both, so we'll handle changes
+ // properly for desktop environments that don't set the
+ // _NET_WORKAREA property.
+ NS_WARNING("Invalid bounds");
+ continue;
+ }
+
+ mAvailRect.IntersectRect(mAvailRect, workarea);
+ }
+ }
+ g_free (workareas);
+#endif
+}
+
+#ifdef MOZ_X11
+void
+nsScreenGtk :: Init (XineramaScreenInfo *aScreenInfo)
+{
+ nsIntRect xineRect(aScreenInfo->x_org, aScreenInfo->y_org,
+ aScreenInfo->width, aScreenInfo->height);
+
+ mScreenNum = aScreenInfo->screen_number;
+
+ mAvailRect = mRect = xineRect;
+}
+#endif
diff --git a/widget/gtk/nsScreenGtk.h b/widget/gtk/nsScreenGtk.h
new file mode 100644
index 000000000..d58ea4b1e
--- /dev/null
+++ b/widget/gtk/nsScreenGtk.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 nsScreenGtk_h___
+#define nsScreenGtk_h___
+
+#include "nsBaseScreen.h"
+#include "nsRect.h"
+#include "gdk/gdk.h"
+#ifdef MOZ_X11
+#include <X11/Xlib.h>
+
+// from Xinerama.h
+typedef struct {
+ int screen_number;
+ short x_org;
+ short y_org;
+ short width;
+ short height;
+} XineramaScreenInfo;
+#endif /* MOZ_X11 */
+
+//------------------------------------------------------------------------
+
+class nsScreenGtk : public nsBaseScreen
+{
+public:
+ nsScreenGtk();
+ ~nsScreenGtk();
+
+ NS_IMETHOD GetId(uint32_t* aId) override;
+ 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;
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor) override;
+
+ void Init(GdkWindow *aRootWindow);
+#ifdef MOZ_X11
+ void Init(XineramaScreenInfo *aScreenInfo);
+#endif /* MOZ_X11 */
+
+ static gint GetGtkMonitorScaleFactor();
+ static double GetDPIScale();
+
+private:
+ uint32_t mScreenNum;
+ nsIntRect mRect;
+ nsIntRect mAvailRect;
+ uint32_t mId;
+};
+
+#endif // nsScreenGtk_h___
diff --git a/widget/gtk/nsScreenManagerGtk.cpp b/widget/gtk/nsScreenManagerGtk.cpp
new file mode 100644
index 000000000..98166cc92
--- /dev/null
+++ b/widget/gtk/nsScreenManagerGtk.cpp
@@ -0,0 +1,366 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsScreenManagerGtk.h"
+
+#include "mozilla/RefPtr.h"
+#include "nsScreenGtk.h"
+#include "nsIComponentManager.h"
+#include "nsRect.h"
+#include "nsGtkUtils.h"
+
+#define SCREEN_MANAGER_LIBRARY_LOAD_FAILED ((PRLibrary*)1)
+
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+// prototypes from Xinerama.h
+typedef Bool (*_XnrmIsActive_fn)(Display *dpy);
+typedef XineramaScreenInfo* (*_XnrmQueryScreens_fn)(Display *dpy, int *number);
+#endif
+
+#include <gtk/gtk.h>
+
+void
+monitors_changed(GdkScreen* aScreen, gpointer aClosure)
+{
+ nsScreenManagerGtk *manager = static_cast<nsScreenManagerGtk*>(aClosure);
+ manager->Init();
+}
+
+static GdkFilterReturn
+root_window_event_filter(GdkXEvent *aGdkXEvent, GdkEvent *aGdkEvent,
+ gpointer aClosure)
+{
+ nsScreenManagerGtk *manager = static_cast<nsScreenManagerGtk*>(aClosure);
+#ifdef MOZ_X11
+ XEvent *xevent = static_cast<XEvent*>(aGdkXEvent);
+
+ // See comments in nsScreenGtk::Init below.
+ switch (xevent->type) {
+ case PropertyNotify:
+ {
+ XPropertyEvent *propertyEvent = &xevent->xproperty;
+ if (propertyEvent->atom == manager->NetWorkareaAtom()) {
+ manager->Init();
+ }
+ }
+ break;
+ default:
+ break;
+ }
+#endif
+
+ return GDK_FILTER_CONTINUE;
+}
+
+nsScreenManagerGtk :: nsScreenManagerGtk ( )
+ : mXineramalib(nullptr)
+ , mRootWindow(nullptr)
+ , mNetWorkareaAtom(0)
+{
+ // nothing else to do. I guess we could cache a bunch of information
+ // here, but we want to ask the device at runtime in case anything
+ // has changed.
+}
+
+
+nsScreenManagerGtk :: ~nsScreenManagerGtk()
+{
+ g_signal_handlers_disconnect_by_func(gdk_screen_get_default(),
+ FuncToGpointer(monitors_changed),
+ this);
+
+ if (mRootWindow) {
+ gdk_window_remove_filter(mRootWindow, root_window_event_filter, this);
+ g_object_unref(mRootWindow);
+ mRootWindow = nullptr;
+ }
+
+ /* XineramaIsActive() registers a callback function close_display()
+ * in X, which is to be called in XCloseDisplay(). This is the case
+ * if Xinerama is active, even if only with one screen.
+ *
+ * We can't unload libXinerama.so.1 here because this will make
+ * the address of close_display() registered in X to be invalid and
+ * it will crash when XCloseDisplay() is called later. */
+}
+
+
+// addref, release, QI
+NS_IMPL_ISUPPORTS(nsScreenManagerGtk, nsIScreenManager)
+
+
+// this function will make sure that everything has been initialized.
+nsresult
+nsScreenManagerGtk :: EnsureInit()
+{
+ if (mCachedScreenArray.Count() > 0)
+ return NS_OK;
+
+ mRootWindow = gdk_get_default_root_window();
+ if (!mRootWindow) {
+ // Sometimes we don't initial X (e.g., xpcshell)
+ return NS_OK;
+ }
+
+ 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(gdk_screen_get_default(), "monitors-changed",
+ G_CALLBACK(monitors_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", False);
+#endif
+
+ return Init();
+}
+
+nsresult
+nsScreenManagerGtk :: Init()
+{
+#ifdef MOZ_X11
+ XineramaScreenInfo *screenInfo = nullptr;
+ int numScreens;
+
+ bool useXinerama = GDK_IS_X11_DISPLAY(gdk_display_get_default());
+
+ if (useXinerama && !mXineramalib) {
+ mXineramalib = PR_LoadLibrary("libXinerama.so.1");
+ if (!mXineramalib) {
+ mXineramalib = SCREEN_MANAGER_LIBRARY_LOAD_FAILED;
+ }
+ }
+ if (mXineramalib && mXineramalib != SCREEN_MANAGER_LIBRARY_LOAD_FAILED) {
+ _XnrmIsActive_fn _XnrmIsActive = (_XnrmIsActive_fn)
+ PR_FindFunctionSymbol(mXineramalib, "XineramaIsActive");
+
+ _XnrmQueryScreens_fn _XnrmQueryScreens = (_XnrmQueryScreens_fn)
+ PR_FindFunctionSymbol(mXineramalib, "XineramaQueryScreens");
+
+ // get the number of screens via xinerama
+ Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ if (_XnrmIsActive && _XnrmQueryScreens && _XnrmIsActive(display)) {
+ screenInfo = _XnrmQueryScreens(display, &numScreens);
+ }
+ }
+
+ // screenInfo == nullptr if either Xinerama couldn't be loaded or
+ // isn't running on the current display
+ if (!screenInfo || numScreens == 1) {
+ numScreens = 1;
+#endif
+ RefPtr<nsScreenGtk> screen;
+
+ if (mCachedScreenArray.Count() > 0) {
+ screen = static_cast<nsScreenGtk*>(mCachedScreenArray[0]);
+ } else {
+ screen = new nsScreenGtk();
+ if (!screen || !mCachedScreenArray.AppendObject(screen)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ screen->Init(mRootWindow);
+#ifdef MOZ_X11
+ }
+ // If Xinerama is enabled and there's more than one screen, fill
+ // in the info for all of the screens. If that's not the case
+ // then nsScreenGTK() defaults to the screen width + height
+ else {
+#ifdef DEBUG
+ printf("Xinerama superpowers activated for %d screens!\n", numScreens);
+#endif
+ for (int i = 0; i < numScreens; ++i) {
+ RefPtr<nsScreenGtk> screen;
+ if (mCachedScreenArray.Count() > i) {
+ screen = static_cast<nsScreenGtk*>(mCachedScreenArray[i]);
+ } else {
+ screen = new nsScreenGtk();
+ if (!screen || !mCachedScreenArray.AppendObject(screen)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // initialize this screen object
+ screen->Init(&screenInfo[i]);
+ }
+ }
+ // Remove any screens that are no longer present.
+ while (mCachedScreenArray.Count() > numScreens) {
+ mCachedScreenArray.RemoveObjectAt(mCachedScreenArray.Count() - 1);
+ }
+
+ if (screenInfo) {
+ XFree(screenInfo);
+ }
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGtk :: ScreenForId ( uint32_t aId, nsIScreen **outScreen )
+{
+ *outScreen = nullptr;
+
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForId");
+ return rv;
+ }
+
+ for (int32_t i = 0, i_end = mCachedScreenArray.Count(); i < i_end; ++i) {
+ uint32_t id;
+ rv = mCachedScreenArray[i]->GetId(&id);
+ if (NS_SUCCEEDED(rv) && id == aId) {
+ NS_IF_ADDREF(*outScreen = mCachedScreenArray[i]);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+//
+// ScreenForRect
+//
+// 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
+nsScreenManagerGtk::ScreenForRect(int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight,
+ nsIScreen **aOutScreen)
+{
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForRect");
+ return rv;
+ }
+
+ // which screen ( index from zero ) should we return?
+ uint32_t which = 0;
+ // Optimize for the common case. If the number of screens is only
+ // one then this will fall through with which == 0 and will get the
+ // primary screen.
+ if (mCachedScreenArray.Count() > 1) {
+ // walk the list of screens and find the one that has the most
+ // surface area.
+ uint32_t area = 0;
+ nsIntRect windowRect(aX, aY, aWidth, aHeight);
+ for (int32_t i = 0, i_end = mCachedScreenArray.Count(); i < i_end; ++i) {
+ int32_t x, y, width, height;
+ x = y = width = height = 0;
+ mCachedScreenArray[i]->GetRect(&x, &y, &width, &height);
+ // calculate the surface area
+ nsIntRect screenRect(x, y, width, height);
+ screenRect.IntersectRect(screenRect, windowRect);
+ uint32_t tempArea = screenRect.width * screenRect.height;
+ if (tempArea >= area) {
+ which = i;
+ area = tempArea;
+ }
+ }
+ }
+ *aOutScreen = mCachedScreenArray.SafeObjectAt(which);
+ NS_IF_ADDREF(*aOutScreen);
+ return NS_OK;
+
+} // ScreenForRect
+
+
+//
+// GetPrimaryScreen
+//
+// The screen with the menubar/taskbar. This shouldn't be needed very
+// often.
+//
+NS_IMETHODIMP
+nsScreenManagerGtk :: GetPrimaryScreen(nsIScreen * *aPrimaryScreen)
+{
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from GetPrimaryScreen");
+ return rv;
+ }
+ *aPrimaryScreen = mCachedScreenArray.SafeObjectAt(0);
+ NS_IF_ADDREF(*aPrimaryScreen);
+ return NS_OK;
+
+} // GetPrimaryScreen
+
+
+//
+// GetNumberOfScreens
+//
+// Returns how many physical screens are available.
+//
+NS_IMETHODIMP
+nsScreenManagerGtk :: GetNumberOfScreens(uint32_t *aNumberOfScreens)
+{
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from GetNumberOfScreens");
+ return rv;
+ }
+ *aNumberOfScreens = mCachedScreenArray.Count();
+ return NS_OK;
+
+} // GetNumberOfScreens
+
+NS_IMETHODIMP
+nsScreenManagerGtk::GetSystemDefaultScale(float *aDefaultScale)
+{
+ *aDefaultScale = nsScreenGtk::GetDPIScale();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGtk :: ScreenForNativeWidget (void *aWidget, nsIScreen **outScreen)
+{
+ nsresult rv;
+ rv = EnsureInit();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForNativeWidget");
+ return rv;
+ }
+
+ if (mCachedScreenArray.Count() > 1) {
+ // I don't know how to go from GtkWindow to nsIScreen, especially
+ // given xinerama and stuff, so let's just do this
+ gint x, y, width, height;
+#if (MOZ_WIDGET_GTK == 2)
+ gint depth;
+#endif
+ x = y = width = height = 0;
+
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_window_get_geometry(GDK_WINDOW(aWidget), &x, &y, &width, &height,
+ &depth);
+#else
+ gdk_window_get_geometry(GDK_WINDOW(aWidget), &x, &y, &width, &height);
+#endif
+ gdk_window_get_origin(GDK_WINDOW(aWidget), &x, &y);
+ rv = ScreenForRect(x, y, width, height, outScreen);
+ } else {
+ rv = GetPrimaryScreen(outScreen);
+ }
+
+ return rv;
+}
diff --git a/widget/gtk/nsScreenManagerGtk.h b/widget/gtk/nsScreenManagerGtk.h
new file mode 100644
index 000000000..9afb3bf22
--- /dev/null
+++ b/widget/gtk/nsScreenManagerGtk.h
@@ -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/. */
+
+#ifndef nsScreenManagerGtk_h___
+#define nsScreenManagerGtk_h___
+
+#include "nsIScreenManager.h"
+#include "nsIScreen.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "prlink.h"
+#include "gdk/gdk.h"
+#ifdef MOZ_X11
+#include <X11/Xlib.h>
+#endif
+
+//------------------------------------------------------------------------
+
+class nsScreenManagerGtk : public nsIScreenManager
+{
+public:
+ nsScreenManagerGtk ( );
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+#ifdef MOZ_X11
+ Atom NetWorkareaAtom() { return mNetWorkareaAtom; }
+#endif
+
+ // For internal use, or reinitialization from change notification.
+ nsresult Init();
+
+private:
+ virtual ~nsScreenManagerGtk();
+
+ nsresult EnsureInit();
+
+ // Cached screen array. Its length is the number of screens we have.
+ nsCOMArray<nsIScreen> mCachedScreenArray;
+
+ PRLibrary *mXineramalib;
+
+ GdkWindow *mRootWindow;
+#ifdef MOZ_X11
+ Atom mNetWorkareaAtom;
+#endif
+};
+
+#endif // nsScreenManagerGtk_h___
diff --git a/widget/gtk/nsSound.cpp b/widget/gtk/nsSound.cpp
new file mode 100644
index 000000000..4e81fe43f
--- /dev/null
+++ b/widget/gtk/nsSound.cpp
@@ -0,0 +1,444 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+
+#include "nscore.h"
+#include "plstr.h"
+#include "prlink.h"
+
+#include "nsSound.h"
+
+#include "nsIURL.h"
+#include "nsIFileURL.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/Services.h"
+#include "mozilla/Unused.h"
+#include "nsIStringBundle.h"
+#include "nsIXULAppInfo.h"
+#include "nsContentUtils.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 GStaticPrivate ctx_static_private = G_STATIC_PRIVATE_INIT;
+
+ ca_context* ctx = (ca_context*) g_static_private_get(&ctx_static_private);
+
+ if (ctx) {
+ return ctx;
+ }
+
+ ca_context_create(&ctx);
+ if (!ctx) {
+ return nullptr;
+ }
+
+ g_static_private_set(&ctx_static_private, ctx, (GDestroyNotify) ca_context_destroy);
+
+ 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);
+ }
+ }
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> brandingBundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(brandingBundle));
+ if (brandingBundle) {
+ nsAutoString wbrand;
+ brandingBundle->GetStringFromName(u"brandShortName",
+ getter_Copies(wbrand));
+ NS_ConvertUTF16toUTF8 brand(wbrand);
+
+ ca_context_change_props(ctx, "application.name", brand.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()
+{
+}
+
+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) {
+ 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()
+{
+ if (libcanberra) {
+ PR_UnloadLibrary(libcanberra);
+ libcanberra = nullptr;
+ }
+}
+
+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;
+
+ bool isFile;
+ nsresult rv = aURL->SchemeIs("file", &isFile);
+ if (NS_SUCCEEDED(rv) && isFile) {
+ 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_DATA_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;
+}
+
+NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias)
+{
+ if (NS_IsMozAliasSound(aSoundAlias)) {
+ NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead");
+ uint32_t eventId;
+ if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG))
+ eventId = EVENT_ALERT_DIALOG_OPEN;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG))
+ eventId = EVENT_CONFIRM_DIALOG_OPEN;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP))
+ eventId = EVENT_NEW_MAIL_RECEIVED;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE))
+ eventId = EVENT_MENU_EXECUTE;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP))
+ eventId = EVENT_MENU_POPUP;
+ else
+ return NS_OK;
+ return PlayEventSound(eventId);
+ }
+
+ nsresult rv;
+ nsCOMPtr <nsIURI> fileURI;
+
+ // create a nsIFile and then a nsIFileURL from that
+ nsCOMPtr <nsIFile> soundFile;
+ rv = NS_NewLocalFile(aSoundAlias, true,
+ getter_AddRefs(soundFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = NS_NewFileURI(getter_AddRefs(fileURI), soundFile);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI,&rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = Play(fileURL);
+
+ return rv;
+}
diff --git a/widget/gtk/nsSound.h b/widget/gtk/nsSound.h
new file mode 100644
index 000000000..0039b8556
--- /dev/null
+++ b/widget/gtk/nsSound.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __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();
+
+ 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 000000000..41d47ff96
--- /dev/null
+++ b/widget/gtk/nsToolkit.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp
new file mode 100644
index 000000000..7e4274377
--- /dev/null
+++ b/widget/gtk/nsWidgetFactory.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ModuleUtils.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 "nsTransferable.h"
+#include "nsHTMLFormatConverter.h"
+#ifdef MOZ_X11
+#include "nsClipboardHelper.h"
+#include "nsClipboard.h"
+#include "nsDragService.h"
+#endif
+#if (MOZ_WIDGET_GTK == 3)
+#include "nsApplicationChooser.h"
+#endif
+#include "nsColorPicker.h"
+#include "nsFilePicker.h"
+#include "nsSound.h"
+#include "nsBidiKeyboard.h"
+#include "nsScreenManagerGtk.h"
+#include "nsGTKToolkit.h"
+#include "WakeLockListener.h"
+
+#ifdef NS_PRINTING
+#include "nsPrintOptionsGTK.h"
+#include "nsPrintSession.h"
+#include "nsDeviceContextSpecG.h"
+#endif
+
+#include "mozilla/Preferences.h"
+
+#include "nsImageToPixbuf.h"
+#include "nsPrintDialogGTK.h"
+
+#if defined(MOZ_X11)
+#include "nsIdleServiceGTK.h"
+#include "GfxInfoX11.h"
+#endif
+
+#include "nsNativeThemeGTK.h"
+
+#include "nsIComponentRegistrar.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/gfx/2D.h"
+#include <gtk/gtk.h>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+/* from nsFilePicker.js */
+#define XULFILEPICKER_CID \
+ { 0x54ae32f8, 0x1dd2, 0x11b2, \
+ { 0xa2, 0x09, 0xdf, 0x7c, 0x50, 0x53, 0x70, 0xf8} }
+static NS_DEFINE_CID(kXULFilePickerCID, XULFILEPICKER_CID);
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsChildWindow)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+#ifdef MOZ_X11
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceGTK, nsIdleServiceGTK::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsClipboard, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService)
+#endif
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerGtk)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsImageToPixbuf)
+
+
+// from nsWindow.cpp
+extern bool gDisableNativeTheme;
+
+static nsresult
+nsNativeThemeGTKConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ nsresult rv;
+ nsNativeThemeGTK * inst;
+
+ if (gDisableNativeTheme)
+ return NS_ERROR_NO_INTERFACE;
+
+ *aResult = nullptr;
+ if (nullptr != aOuter) {
+ rv = NS_ERROR_NO_AGGREGATION;
+ return rv;
+ }
+
+ inst = new nsNativeThemeGTK();
+ if (nullptr == inst) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ return rv;
+ }
+ NS_ADDREF(inst);
+ rv = inst->QueryInterface(aIID, aResult);
+ NS_RELEASE(inst);
+
+ return rv;
+}
+
+#if defined(MOZ_X11)
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+}
+}
+#endif
+
+#ifdef NS_PRINTING
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecGTK)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsGTK, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrinterEnumeratorGTK)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintDialogServiceGTK, Init)
+#endif
+
+static nsresult
+nsFilePickerConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ bool allowPlatformPicker =
+ Preferences::GetBool("ui.allow_platform_file_picker", true);
+
+ nsCOMPtr<nsIFilePicker> picker;
+ if (allowPlatformPicker) {
+ picker = new nsFilePicker;
+ } else {
+ picker = do_CreateInstance(kXULFilePickerCID);
+ }
+
+ if (!picker) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return picker->QueryInterface(aIID, aResult);
+}
+
+#if (MOZ_WIDGET_GTK == 3)
+static nsresult
+nsApplicationChooserConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIApplicationChooser> chooser = new nsApplicationChooser;
+
+ if (!chooser) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return chooser->QueryInterface(aIID, aResult);
+}
+#endif
+
+static nsresult
+nsColorPickerConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ nsCOMPtr<nsIColorPicker> picker = new nsColorPicker;
+
+ if (!picker) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return picker->QueryInterface(aIID, aResult);
+}
+
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
+#if (MOZ_WIDGET_GTK == 3)
+NS_DEFINE_NAMED_CID(NS_APPLICATIONCHOOSER_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_SOUND_CID);
+NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
+#ifdef MOZ_X11
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID);
+#ifdef NS_PRINTING
+NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTER_ENUMERATOR_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTDIALOGSERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_IMAGE_TO_PIXBUF_CID);
+#if defined(MOZ_X11)
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+#endif
+
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_CHILD_CID, false, nullptr, nsChildWindowConstructor },
+ { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor, Module::ALLOW_IN_GPU_PROCESS },
+ { &kNS_COLORPICKER_CID, false, nullptr, nsColorPickerConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_FILEPICKER_CID, false, nullptr, nsFilePickerConstructor, Module::MAIN_PROCESS_ONLY },
+#if (MOZ_WIDGET_GTK == 3)
+ { &kNS_APPLICATIONCHOOSER_CID, false, nullptr, nsApplicationChooserConstructor, Module::MAIN_PROCESS_ONLY },
+#endif
+ { &kNS_SOUND_CID, false, nullptr, nsSoundConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
+#ifdef MOZ_X11
+ { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
+ { &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceConstructor, Module::MAIN_PROCESS_ONLY },
+#endif
+ { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
+ { &kNS_BIDIKEYBOARD_CID, false, nullptr, nsBidiKeyboardConstructor },
+ { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerGtkConstructor,
+ Module::MAIN_PROCESS_ONLY },
+ { &kNS_THEMERENDERER_CID, false, nullptr, nsNativeThemeGTKConstructor },
+#ifdef NS_PRINTING
+ { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsGTKConstructor },
+ { &kNS_PRINTER_ENUMERATOR_CID, false, nullptr, nsPrinterEnumeratorGTKConstructor },
+ { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor },
+ { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecGTKConstructor },
+ { &kNS_PRINTDIALOGSERVICE_CID, false, nullptr, nsPrintDialogServiceGTKConstructor },
+#endif
+ { &kNS_IMAGE_TO_PIXBUF_CID, false, nullptr, nsImageToPixbufConstructor },
+#if defined(MOZ_X11)
+ { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceGTKConstructor },
+ { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
+#endif
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widget/window/gtk;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/child_window/gtk;1", &kNS_CHILD_CID },
+ { "@mozilla.org/widget/appshell/gtk;1", &kNS_APPSHELL_CID, Module::ALLOW_IN_GPU_PROCESS },
+ { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID, Module::MAIN_PROCESS_ONLY },
+#if (MOZ_WIDGET_GTK == 3)
+ { "@mozilla.org/applicationchooser;1", &kNS_APPLICATIONCHOOSER_CID, Module::MAIN_PROCESS_ONLY },
+#endif
+ { "@mozilla.org/sound;1", &kNS_SOUND_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+#ifdef MOZ_X11
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::MAIN_PROCESS_ONLY },
+#endif
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID,
+ Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID,
+ Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID },
+#ifdef NS_PRINTING
+ { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
+ { "@mozilla.org/gfx/printerenumerator;1", &kNS_PRINTER_ENUMERATOR_CID },
+ { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
+ { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
+ { NS_PRINTDIALOGSERVICE_CONTRACTID, &kNS_PRINTDIALOGSERVICE_CID },
+#endif
+ { "@mozilla.org/widget/image-to-gdk-pixbuf;1", &kNS_IMAGE_TO_PIXBUF_CID },
+#if defined(MOZ_X11)
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+#endif
+ { nullptr }
+};
+
+static void
+nsWidgetGtk2ModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ NativeKeyBindings::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsFilePicker::Shutdown();
+ nsSound::Shutdown();
+ nsWindow::ReleaseGlobals();
+ KeymapWrapper::Shutdown();
+ nsGTKToolkit::Shutdown();
+ nsAppShellShutdown();
+#ifdef MOZ_ENABLE_DBUS
+ WakeLockListener::Shutdown();
+#endif
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ nullptr,
+ nullptr,
+ nsAppShellInit,
+ nsWidgetGtk2ModuleDtor,
+ Module::ALLOW_IN_GPU_PROCESS
+};
+
+NSMODULE_DEFN(nsWidgetGtk2Module) = &kWidgetModule;
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
new file mode 100644
index 000000000..d97b35002
--- /dev/null
+++ b/widget/gtk/nsWindow.cpp
@@ -0,0 +1,7036 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindow.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include <algorithm>
+
+#include "GeckoProfiler.h"
+
+#include "prlink.h"
+#include "nsGTKToolkit.h"
+#include "nsIRollupListener.h"
+#include "nsIDOMNode.h"
+
+#include "nsWidgetsCID.h"
+#include "nsDragService.h"
+#include "nsIWidgetListener.h"
+#include "nsIScreenManager.h"
+#include "SystemTimeConverter.h"
+
+#include "nsGtkKeyUtils.h"
+#include "nsGtkCursors.h"
+#include "nsScreenGtk.h"
+
+#include <gtk/gtk.h>
+#if (MOZ_WIDGET_GTK == 3)
+#include <gtk/gtkx.h>
+#endif
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/XShm.h>
+#include <X11/extensions/shape.h>
+#if (MOZ_WIDGET_GTK == 3)
+#include <gdk/gdkkeysyms-compat.h>
+#endif
+
+#if (MOZ_WIDGET_GTK == 2)
+#include "gtk2xtbin.h"
+#endif
+#endif /* MOZ_X11 */
+#include <gdk/gdkkeysyms.h>
+#if (MOZ_WIDGET_GTK == 2)
+#include <gtk/gtkprivate.h>
+#endif
+
+#include "nsGkAtoms.h"
+
+#ifdef MOZ_ENABLE_STARTUP_NOTIFICATION
+#define SN_API_NOT_YET_FROZEN
+#include <startup-notification-1.0/libsn/sn.h>
+#endif
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Preferences.h"
+#include "nsIPrefService.h"
+#include "nsIGConfService.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsGfxCIID.h"
+#include "nsGtkUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsIIdleServiceInternal.h"
+#include "nsIPropertyBag2.h"
+#include "GLContext.h"
+#include "gfx2DGlue.h"
+#include "nsPluginNativeWindowGtk.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 "nsXPIDLString.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 "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/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+
+#ifdef MOZ_X11
+#include "X11CompositorWidget.h"
+#include "gfxXlibSurface.h"
+#include "WindowSurfaceX11Image.h"
+#include "WindowSurfaceX11SHM.h"
+#include "WindowSurfaceXRender.h"
+#endif // MOZ_X11
+
+#include "nsShmImage.h"
+
+#include "nsIDOMWheelEvent.h"
+
+#include "NativeKeyBindings.h"
+
+#include <dlfcn.h>
+
+#include "mozilla/layers/APZCTreeManager.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+using namespace mozilla::layers;
+using mozilla::gl::GLContext;
+
+// 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
+
+const gint kEvents = 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 |
+#if GTK_CHECK_VERSION(3,4,0)
+ GDK_SMOOTH_SCROLL_MASK |
+ GDK_TOUCH_MASK |
+#endif
+ GDK_SCROLL_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_PROPERTY_CHANGE_MASK;
+
+/* 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 inline bool is_context_menu_key(const WidgetKeyboardEvent& inKeyEvent);
+
+static int is_parent_ungrab_enter(GdkEventCrossing *aEvent);
+static int is_parent_grab_leave(GdkEventCrossing *aEvent);
+
+static void GetBrandName(nsXPIDLString& brandName);
+
+/* callbacks from widgets */
+#if (MOZ_WIDGET_GTK == 2)
+static gboolean expose_event_cb (GtkWidget *widget,
+ GdkEventExpose *event);
+#else
+static gboolean expose_event_cb (GtkWidget *widget,
+ cairo_t *rect);
+#endif
+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 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 gboolean visibility_notify_event_cb(GtkWidget *widget,
+ GdkEventVisibility *event);
+static void hierarchy_changed_cb (GtkWidget *widget,
+ GtkWidget *previous_toplevel);
+static gboolean window_state_event_cb (GtkWidget *widget,
+ GdkEventWindowState *event);
+static void theme_changed_cb (GtkSettings *settings,
+ GParamSpec *pspec,
+ nsWindow *data);
+static void check_resize_cb (GtkContainer* container,
+ gpointer user_data);
+
+#if (MOZ_WIDGET_GTK == 3)
+static void scale_changed_cb (GtkWidget* widget,
+ GParamSpec* aPSpec,
+ gpointer aPointer);
+#endif
+#if GTK_CHECK_VERSION(3,4,0)
+static gboolean touch_event_cb (GtkWidget* aWidget,
+ GdkEventTouch* aEvent);
+#endif
+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);
+static GdkFilterReturn plugin_window_filter_func (GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data);
+static GdkFilterReturn plugin_client_message_filter (GdkXEvent *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;
+}
+
+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 nsWindow *gPluginFocusWindow = nullptr;
+
+#if GTK_CHECK_VERSION(3,4,0)
+static uint32_t gLastTouchID = 0;
+#endif
+
+#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) || (MOZ_WIDGET_GTK == 2)
+ 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<nsIIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/idleservice;1");
+ if (idleService) {
+ idleService->ResetIdleTimeOut(0);
+ }
+
+ guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
+ if (timestamp == GDK_CURRENT_TIME)
+ return;
+
+ sLastUserInputTime = timestamp;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
+
+nsWindow::nsWindow()
+{
+ mIsTopLevel = false;
+ mIsDestroyed = false;
+ mListenForResizes = false;
+ mNeedsDispatchResized = false;
+ mIsShown = false;
+ mNeedsShow = false;
+ mEnabled = true;
+ mCreated = false;
+#if GTK_CHECK_VERSION(3,4,0)
+ mHandleTouchEvent = false;
+#endif
+ mIsDragPopup = false;
+ mIsX11Display = GDK_IS_X11_DISPLAY(gdk_display_get_default());
+
+ mContainer = nullptr;
+ mGdkWindow = nullptr;
+ mShell = nullptr;
+ mPluginNativeWindow = nullptr;
+ mHasMappedToplevel = false;
+ mIsFullyObscured = false;
+ mRetryPointerGrab = false;
+ mWindowType = eWindowType_child;
+ mSizeState = nsSizeMode_Normal;
+ mLastSizeMode = nsSizeMode_Normal;
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
+
+#ifdef MOZ_X11
+ mOldFocusWindow = 0;
+
+ mXDisplay = nullptr;
+ mXWindow = X11None;
+ mXVisual = nullptr;
+ mXDepth = 0;
+#endif /* MOZ_X11 */
+ mPluginType = PluginType_NONE;
+
+ if (!gGlobalsInitialized) {
+ gGlobalsInitialized = true;
+
+ // It's OK if either of these fail, but it may not be one day.
+ initialize_prefs();
+ }
+
+ mLastMotionPressure = 0;
+
+#ifdef ACCESSIBILITY
+ mRootAccessible = nullptr;
+#endif
+
+ mIsTransparent = false;
+ mTransparencyBitmap = nullptr;
+
+ mTransparencyBitmapWidth = 0;
+ mTransparencyBitmapHeight = 0;
+
+#if GTK_CHECK_VERSION(3,4,0)
+ mLastScrollEventTime = GDK_CURRENT_TIME;
+#endif
+ mPendingConfigures = 0;
+}
+
+nsWindow::~nsWindow()
+{
+ LOG(("nsWindow::~nsWindow() [%p]\n", (void *)this));
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = nullptr;
+
+ Destroy();
+}
+
+/* static */ void
+nsWindow::ReleaseGlobals()
+{
+ for (uint32_t i = 0; i < ArrayLength(gCursorCache); ++i) {
+ if (gCursorCache[i]) {
+#if (MOZ_WIDGET_GTK == 3)
+ g_object_unref(gCursorCache[i]);
+#else
+ gdk_cursor_unref(gCursorCache[i]);
+#endif
+ gCursorCache[i] = 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(void)
+{
+ if (mBounds.width > 0 && mBounds.height > 0)
+ return true;
+
+ return false;
+}
+
+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()
+{
+ NS_PRECONDITION(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;
+
+ GtkWidget* 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;
+
+ // 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_func(gtk_settings_get_default(),
+ FuncToGpointer(theme_changed_cb),
+ 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.
+ 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;
+ }
+
+#if (MOZ_WIDGET_GTK == 2) && defined(MOZ_X11)
+ // make sure that we remove ourself as the plugin focus window
+ if (gPluginFocusWindow == this) {
+ gPluginFocusWindow->LoseNonXEmbedPluginFocus();
+ }
+#endif /* MOZ_X11 && MOZ_WIDGET_GTK == 2 && defined(MOZ_X11) */
+
+ 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()
+{
+ GdkScreen *screen = gdk_display_get_default_screen(gdk_display_get_default());
+ double heightInches = gdk_screen_get_height_mm(screen)/MM_PER_INCH_FLOAT;
+ if (heightInches < 0.25) {
+ // Something's broken, but we'd better not crash.
+ return 96.0f;
+ }
+ return float(gdk_screen_get_height(screen)/heightInches);
+}
+
+double
+nsWindow::GetDefaultScaleInternal()
+{
+ return GdkScaleFactor() * gfxPlatformGtk::GetDPIScale();
+}
+
+NS_IMETHODIMP
+nsWindow::SetParent(nsIWidget *aNewParent)
+{
+ if (mContainer || !mGdkWindow) {
+ NS_NOTREACHED("nsWindow::SetParent called illegally");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ 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 NS_OK;
+ }
+
+ if (aNewParent) {
+ aNewParent->AddChild(this);
+ ReparentNativeWidget(aNewParent);
+ } 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.
+ GtkWidget* newContainer = EnsureInvisibleContainer();
+ GdkWindow* newParentWindow = gtk_widget_get_window(newContainer);
+ ReparentNativeWidgetInternal(aNewParent, newContainer, newParentWindow,
+ oldContainer);
+ }
+ return NS_OK;
+}
+
+bool
+nsWindow::WidgetTypeSupportsAcceleration()
+{
+ return !IsSmallPopup();
+}
+
+void
+nsWindow::ReparentNativeWidget(nsIWidget* aNewParent)
+{
+ NS_PRECONDITION(aNewParent, "");
+ NS_ASSERTION(!mIsDestroyed, "");
+ NS_ASSERTION(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
+
+ 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;
+ }
+ MOZ_ASSERT(!gdk_window_is_destroyed(mGdkWindow),
+ "destroyed GdkWindow with widget");
+
+ nsWindow* newParent = static_cast<nsWindow*>(aNewParent);
+ GdkWindow* newParentWindow = newParent->mGdkWindow;
+ GtkWidget* newContainer = newParent->GetMozContainerWidget();
+ GtkWindow* shell = GTK_WINDOW(mShell);
+
+ if (shell && gtk_window_get_transient_for(shell)) {
+ GtkWindow* topLevelParent =
+ GTK_WINDOW(gtk_widget_get_toplevel(newContainer));
+ gtk_window_set_transient_for(shell, topLevelParent);
+ }
+
+ ReparentNativeWidgetInternal(aNewParent, newContainer, newParentWindow,
+ oldContainer);
+}
+
+void
+nsWindow::ReparentNativeWidgetInternal(nsIWidget* aNewParent,
+ GtkWidget* aNewContainer,
+ GdkWindow* aNewParentWindow,
+ GtkWidget* aOldContainer)
+{
+ if (!aNewContainer) {
+ // The new parent GdkWindow has been destroyed.
+ MOZ_ASSERT(!aNewParentWindow ||
+ gdk_window_is_destroyed(aNewParentWindow),
+ "live GdkWindow with no widget");
+ Destroy();
+ } else {
+ if (aNewContainer != aOldContainer) {
+ MOZ_ASSERT(!gdk_window_is_destroyed(aNewParentWindow),
+ "destroyed GdkWindow with widget");
+ SetWidgetForHierarchy(mGdkWindow, aOldContainer, aNewContainer);
+
+ if (aOldContainer == gInvisibleContainer) {
+ CheckDestroyInvisibleContainer();
+ }
+ }
+
+ if (!mIsTopLevel) {
+ gdk_window_reparent(mGdkWindow, aNewParentWindow,
+ DevicePixelsToGdkCoordRoundDown(mBounds.x),
+ DevicePixelsToGdkCoordRoundDown(mBounds.y));
+ }
+ }
+
+ nsWindow* newParent = static_cast<nsWindow*>(aNewParent);
+ bool parentHasMappedToplevel =
+ newParent && newParent->mHasMappedToplevel;
+ if (mHasMappedToplevel != parentHasMappedToplevel) {
+ SetHasMappedToplevel(parentHasMappedToplevel);
+ }
+}
+
+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()
+{
+#if GTK_CHECK_VERSION(3,4,0)
+ mHandleTouchEvent = true;
+ mTouches.Clear();
+#endif
+}
+
+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);
+
+ 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 (aConstraints.mMinSize != LayoutDeviceIntSize(0, 0)) {
+ hints |= GDK_HINT_MIN_SIZE;
+ }
+ if (aConstraints.mMaxSize !=
+ LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
+ hints |= GDK_HINT_MAX_SIZE;
+ }
+ gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr,
+ &geometry, GdkWindowHints(hints));
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::Show(bool aState)
+{
+ if (aState == mIsShown)
+ return NS_OK;
+
+ // 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 NS_OK;
+ }
+
+ // 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);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aWidth, double aHeight, bool aRepaint)
+{
+ double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(scale * aWidth);
+ int32_t height = NSToIntRound(scale * aHeight);
+ ConstrainSize(&width, &height);
+
+ // 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(width, height);
+
+ if (!mCreated)
+ return NS_OK;
+
+ NativeResize();
+
+ NotifyRollupGeometryChange();
+ ResizePluginSocketWidget();
+
+ // send a resize notification if this is a toplevel
+ if (mIsTopLevel || mListenForResizes) {
+ DispatchResized();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint)
+{
+ double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(scale * aWidth);
+ int32_t height = NSToIntRound(scale * aHeight);
+ ConstrainSize(&width, &height);
+
+ int32_t x = NSToIntRound(scale * aX);
+ int32_t y = NSToIntRound(scale * aY);
+ mBounds.x = x;
+ mBounds.y = y;
+ mBounds.SizeTo(width, height);
+
+ if (!mCreated)
+ return NS_OK;
+
+ NativeMoveResize();
+
+ NotifyRollupGeometryChange();
+ ResizePluginSocketWidget();
+
+ if (mIsTopLevel || mListenForResizes) {
+ DispatchResized();
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindow::ResizePluginSocketWidget()
+{
+ // e10s specific, a eWindowType_plugin_ipc_chrome holds its own
+ // nsPluginNativeWindowGtk wrapper. We are responsible for resizing
+ // the embedded socket widget.
+ if (mWindowType == eWindowType_plugin_ipc_chrome) {
+ nsPluginNativeWindowGtk* wrapper = (nsPluginNativeWindowGtk*)
+ GetNativeData(NS_NATIVE_PLUGIN_OBJECT_PTR);
+ if (wrapper) {
+ wrapper->width = mBounds.width;
+ wrapper->height = mBounds.height;
+ wrapper->SetAllocation();
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::Enable(bool aState)
+{
+ mEnabled = aState;
+
+ return NS_OK;
+}
+
+bool
+nsWindow::IsEnabled() const
+{
+ return mEnabled;
+}
+
+
+
+NS_IMETHODIMP
+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 NS_OK;
+
+ // XXX Should we do some AreBoundsSane check here?
+
+ mBounds.x = x;
+ mBounds.y = y;
+
+ if (!mCreated)
+ return NS_OK;
+
+ NativeMove();
+
+ NotifyRollupGeometryChange();
+ return NS_OK;
+}
+
+
+void
+nsWindow::NativeMove()
+{
+ GdkPoint point = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+
+ 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) {
+ return;
+ }
+
+ switch (aMode) {
+ case nsSizeMode_Maximized:
+ gtk_window_maximize(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Minimized:
+ gtk_window_iconify(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Fullscreen:
+ MakeFullScreen(true);
+ break;
+
+ default:
+ // 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;
+ }
+
+ mSizeState = mSizeMode;
+}
+
+typedef void (* SetUserTimeFunc)(GdkWindow* aWindow, guint32 aTimestamp);
+
+// This will become obsolete when new GTK APIs are widely supported,
+// as described here: http://bugzilla.gnome.org/show_bug.cgi?id=347375
+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;
+ }
+
+#if defined(MOZ_ENABLE_STARTUP_NOTIFICATION)
+ // TODO - Implement for non-X11 Gtk backends (Bug 726479)
+ if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ GdkWindow* gdkWindow = gtk_widget_get_window(aWindow);
+
+ GdkScreen* screen = gdk_window_get_screen(gdkWindow);
+ SnDisplay* snd =
+ sn_display_new(gdk_x11_display_get_xdisplay(gdk_window_get_display(gdkWindow)),
+ nullptr, nullptr);
+ if (!snd)
+ return;
+ SnLauncheeContext* ctx =
+ sn_launchee_context_new(snd, gdk_screen_get_number(screen),
+ desktopStartupID.get());
+ if (!ctx) {
+ sn_display_unref(snd);
+ return;
+ }
+
+ if (sn_launchee_context_get_id_has_timestamp(ctx)) {
+ gdk_x11_window_set_user_time(gdkWindow,
+ sn_launchee_context_get_timestamp(ctx));
+ }
+
+ sn_launchee_context_setup_window(ctx, gdk_x11_window_get_xid(gdkWindow));
+ sn_launchee_context_complete(ctx);
+
+ sn_launchee_context_unref(ctx);
+ sn_display_unref(snd);
+ }
+#endif
+
+ // 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(EmptyCString());
+}
+
+/* static */ guint32
+nsWindow::GetLastUserInputTime()
+{
+ // gdk_x11_display_get_user_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.
+ guint32 timestamp =
+ gdk_x11_display_get_user_time(gdk_display_get_default());
+ if (sLastUserInputTime != GDK_CURRENT_TIME &&
+ TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
+ return sLastUserInputTime;
+ }
+
+ return timestamp;
+}
+
+NS_IMETHODIMP
+nsWindow::SetFocus(bool aRaise)
+{
+ // 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, (void *)this));
+
+ GtkWidget *owningWidget = GetMozContainerWidget();
+ if (!owningWidget)
+ return NS_ERROR_FAILURE;
+
+ // Raise the window if someone passed in true and the prefs are
+ // set properly.
+ GtkWidget *toplevelWidget = gtk_widget_get_toplevel(owningWidget);
+
+ if (gRaiseWindows && aRaise && 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 NS_ERROR_FAILURE;
+
+ if (aRaise) {
+ // aRaise == true 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))) {
+
+ 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 NS_OK;
+ }
+
+ // aRaise == false 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 NS_OK;
+ }
+
+ // 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));
+
+ return NS_OK;
+}
+
+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());
+ LOG(("GetScreenBounds %d,%d | %dx%d\n",
+ rect.x, rect.y, rect.width, rect.height));
+ 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::UpdateClientOffset()
+{
+ PROFILER_LABEL("nsWindow", "UpdateClientOffset", js::ProfileEntry::Category::GRAPHICS);
+
+ if (!mIsTopLevel || !mShell || !mGdkWindow || !mIsX11Display ||
+ 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(mGdkWindow,
+ 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);
+ return;
+ }
+
+ // data returned is in the order left, right, top, bottom
+ int32_t left = int32_t(frame_extents[0]);
+ int32_t top = int32_t(frame_extents[2]);
+
+ g_free(frame_extents);
+
+ mClientOffset = nsIntPoint(left, top);
+}
+
+LayoutDeviceIntPoint
+nsWindow::GetClientOffset()
+{
+ return LayoutDeviceIntPoint::FromUnknownPoint(mClientOffset);
+}
+
+gboolean
+nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget, GdkEventProperty* aEvent)
+
+{
+ if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) {
+ UpdateClientOffset();
+ return FALSE;
+ }
+
+ if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+NS_IMETHODIMP
+nsWindow::SetCursor(nsCursor aCursor)
+{
+ // 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 NS_ERROR_FAILURE;
+
+ return window->SetCursor(aCursor);
+ }
+
+ // Only change cursor if it's actually been changed
+ if (aCursor != mCursor || mUpdateCursor) {
+ GdkCursor *newCursor = nullptr;
+ mUpdateCursor = false;
+
+ newCursor = get_gtk_cursor(aCursor);
+
+ if (nullptr != newCursor) {
+ mCursor = aCursor;
+
+ if (!mContainer)
+ return NS_OK;
+
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), newCursor);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::SetCursor(imgIContainer* aCursor,
+ 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 NS_ERROR_FAILURE;
+
+ return window->SetCursor(aCursor, aHotspotX, aHotspotY);
+ }
+
+ mCursor = nsCursor(-1);
+
+ // Get the image's current frame
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(aCursor);
+ if (!pixbuf)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ int width = gdk_pixbuf_get_width(pixbuf);
+ int height = gdk_pixbuf_get_height(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.
+ if (width > 128 || height > 128) {
+ g_object_unref(pixbuf);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // 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);
+ if (!alphaBuf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ pixbuf = alphaBuf;
+ }
+
+ GdkCursor* cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(),
+ pixbuf,
+ aHotspotX, aHotspotY);
+ g_object_unref(pixbuf);
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ if (cursor) {
+ if (mContainer) {
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), cursor);
+ rv = NS_OK;
+ }
+#if (MOZ_WIDGET_GTK == 3)
+ g_object_unref(cursor);
+#else
+ gdk_cursor_unref(cursor);
+#endif
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ if (!mGdkWindow)
+ return NS_OK;
+
+ 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));
+
+ return NS_OK;
+}
+
+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_PLUGIN_PORT:
+ return SetupPluginPort();
+
+ case NS_NATIVE_PLUGIN_ID:
+ if (!mPluginNativeWindow) {
+ NS_WARNING("no native plugin instance!");
+ return nullptr;
+ }
+ // Return the socket widget XID
+ return (void*)mPluginNativeWindow->window;
+
+ case NS_NATIVE_DISPLAY: {
+#ifdef MOZ_X11
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ return GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ }
+#endif /* MOZ_X11 */
+ return nullptr;
+ }
+ case NS_NATIVE_SHELLWIDGET:
+ return GetToplevelWidget();
+
+ case NS_NATIVE_SHAREABLE_WINDOW:
+ return (void *) GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
+ case NS_NATIVE_PLUGIN_OBJECT_PTR:
+ return (void *) mPluginNativeWindow;
+ 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;
+#ifdef MOZ_X11
+ case NS_NATIVE_COMPOSITOR_DISPLAY:
+ return gfxPlatformGtk::GetPlatform()->GetCompositorDisplay();
+#endif // MOZ_X11
+ default:
+ NS_WARNING("nsWindow::GetNativeData called with bad value");
+ return nullptr;
+ }
+}
+
+void
+nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal)
+{
+ if (aDataType != NS_NATIVE_PLUGIN_OBJECT_PTR) {
+ NS_WARNING("nsWindow::SetNativeData called with bad value");
+ return;
+ }
+ mPluginNativeWindow = (nsPluginNativeWindowGtk*)aVal;
+}
+
+NS_IMETHODIMP
+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;
+}
+
+NS_IMETHODIMP
+nsWindow::SetIcon(const nsAString& aIconSpec)
+{
+ if (!mShell)
+ return NS_OK;
+
+ nsAutoCString iconName;
+
+ if (aIconSpec.EqualsLiteral("default")) {
+ nsXPIDLString brandName;
+ GetBrandName(brandName);
+ 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 char extensions[6][7] = { ".png", "16.png", "32.png", "48.png",
+ ".xpm", "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;
+
+ nsAutoString extension;
+ extension.AppendASCII(extensions[i]);
+
+ ResolveIconName(aIconSpec, extension, 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());
+ }
+
+ return NS_OK;
+}
+
+
+LayoutDeviceIntPoint
+nsWindow::WidgetToScreenOffset()
+{
+ gint x = 0, y = 0;
+
+ if (mGdkWindow) {
+ gdk_window_get_origin(mGdkWindow, &x, &y);
+ }
+
+ return GdkPointToDevicePixels({ x, 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;
+ }
+}
+
+NS_IMETHODIMP
+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;
+
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height,nullptr);
+#else
+ gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height);
+#endif
+
+ 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
+
+#if (MOZ_WIDGET_GTK == 2)
+static bool
+ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, GdkEventExpose* aEvent)
+{
+ GdkRectangle* rects;
+ gint nrects;
+ gdk_region_get_rectangles(aEvent->region, &rects, &nrects);
+
+ if (nrects > MAX_RECTS_IN_REGION) {
+ // Just use the bounding box
+ rects[0] = aEvent->area;
+ nrects = 1;
+ }
+
+ for (GdkRectangle* r = rects; r < rects + nrects; r++) {
+ aRegion.Or(aRegion, LayoutDeviceIntRect(r->x, r->y, r->width, r->height));
+ LOGDRAW(("\t%d %d %d %d\n", r->x, r->y, r->width, r->height));
+ }
+
+ g_free(rects);
+ return true;
+}
+
+#else
+# 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%d %d %d %d\n", r.x, r.y, r.width, r.height));
+ }
+
+ cairo_rectangle_list_destroy(rects);
+ return true;
+}
+#endif
+
+#if (MOZ_WIDGET_GTK == 2)
+gboolean
+nsWindow::OnExposeEvent(GdkEventExpose *aEvent)
+#else
+gboolean
+nsWindow::OnExposeEvent(cairo_t *cr)
+#endif
+{
+ // 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 || mIsFullyObscured || !mHasMappedToplevel)
+ return FALSE;
+
+ nsIWidgetListener *listener = GetListener();
+ if (!listener)
+ return FALSE;
+
+ LayoutDeviceIntRegion exposeRegion;
+#if (MOZ_WIDGET_GTK == 2)
+ if (!ExtractExposeRegion(exposeRegion, aEvent)) {
+#else
+ if (!ExtractExposeRegion(exposeRegion, cr)) {
+#endif
+ return FALSE;
+ }
+
+ gint scale = GdkScaleFactor();
+ LayoutDeviceIntRegion region = exposeRegion;
+ region.ScaleRoundOut(scale, scale);
+
+ ClientLayerManager *clientLayers = GetLayerManager()->AsClientLayerManager();
+
+ if (clientLayers && 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.
+ clientLayers->SetNeedsComposite(true);
+ clientLayers->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 (clientLayers && clientLayers->NeedsComposite()) {
+ clientLayers->Composite();
+ clientLayers->SetNeedsComposite(false);
+ }
+
+ LOGDRAW(("sending expose event [%p] %p 0x%lx (rects follow):\n",
+ (void *)this, (void *)mGdkWindow,
+ gdk_x11_window_get_xid(mGdkWindow)));
+
+ // 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()) {
+ GdkScreen *screen = gdk_window_get_screen(mGdkWindow);
+ if (gdk_screen_is_composited(screen) &&
+ gdk_window_get_visual(mGdkWindow) ==
+ gdk_screen_get_rgba_visual(screen)) {
+ // Remove possible shape mask from when window manger was not
+ // previously compositing.
+ static_cast<nsWindow*>(GetTopLevelWidget())->
+ 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) {
+ 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) {
+ GdkScreen *screen = gdk_window_get_screen(mGdkWindow);
+ if (GetTransparencyMode() == eTransparencyTransparent &&
+ layerBuffering == BufferMode::BUFFER_NONE &&
+ gdk_screen_is_composited(screen) &&
+ gdk_window_get_visual(mGdkWindow) ==
+ gdk_screen_get_rgba_visual(screen)) {
+ // 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
+#if (MOZ_WIDGET_GTK == 2)
+ GdkRegion* dirtyArea = gdk_window_get_update_area(mGdkWindow);
+#else
+ cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
+#endif
+
+ if (dirtyArea) {
+ gdk_window_invalidate_region(mGdkWindow, dirtyArea, false);
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_region_destroy(dirtyArea);
+#else
+ cairo_region_destroy(dirtyArea);
+#endif
+ 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);
+ }
+ }
+
+ // This event indicates that the window position may have changed.
+ // mBounds.Size() is updated in OnSizeAllocate().
+
+ 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 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.
+ return FALSE;
+ }
+
+ mBounds.MoveTo(screenBounds.TopLeft());
+
+ // XXX mozilla will invalidate the entire window after this move
+ // complete. wtf?
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+
+ 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(("size_allocate [%p] %d %d %d %d\n",
+ (void *)this, aAllocation->x, aAllocation->y,
+ aAllocation->width, aAllocation->height));
+
+ LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
+
+ if (mBounds.Size() == size)
+ 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 X11CompositorWidget 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(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)
+{
+ gint x = gint(aEvent->x_root);
+ gint 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 = is_top_level_mouse_exit(mGdkWindow, aEvent)
+ ? WidgetMouseEvent::eTopLevel : WidgetMouseEvent::eChild;
+
+ 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)
+{
+ // 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);
+ }
+#if (MOZ_WIDGET_GTK == 2)
+ // if plugins still keeps the focus, get it back
+ if (gPluginFocusWindow && gPluginFocusWindow != this) {
+ RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow;
+ gPluginFocusWindow->LoseNonXEmbedPluginFocus();
+ }
+#endif /* MOZ_WIDGET_GTK == 2 */
+ }
+#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.pressure = 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 = WidgetMouseEvent::eLeftButton;
+ break;
+ case GDK_BUTTON2_MASK:
+ buttonType = WidgetMouseEvent::eMiddleButton;
+ break;
+ default:
+ NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
+ "Unexpected button mask");
+ buttonType = WidgetMouseEvent::eRightButton;
+ }
+
+ 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.button = 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::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 = WidgetMouseEvent::eLeftButton;
+ break;
+ case 2:
+ domButton = WidgetMouseEvent::eMiddleButton;
+ break;
+ case 3:
+ domButton = WidgetMouseEvent::eRightButton;
+ 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:
+ DispatchCommandEvent(nsGkAtoms::Back);
+ return;
+ case 9:
+ DispatchCommandEvent(nsGkAtoms::Forward);
+ return;
+ default:
+ return;
+ }
+
+ gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
+
+ WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal);
+ event.button = domButton;
+ InitButtonEvent(event, aEvent);
+ event.pressure = mLastMotionPressure;
+
+ DispatchInputEvent(&event);
+
+ // right menu click on linux should also pop up a context menu
+ if (domButton == WidgetMouseEvent::eRightButton &&
+ MOZ_LIKELY(!mIsDestroyed)) {
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
+ WidgetMouseEvent::eReal);
+ InitButtonEvent(contextMenuEvent, aEvent);
+ contextMenuEvent.pressure = mLastMotionPressure;
+ DispatchInputEvent(&contextMenuEvent);
+ }
+}
+
+void
+nsWindow::OnButtonReleaseEvent(GdkEventButton *aEvent)
+{
+ LOG(("Button %u release on %p\n", aEvent->button, (void *)this));
+
+ uint16_t domButton;
+ switch (aEvent->button) {
+ case 1:
+ domButton = WidgetMouseEvent::eLeftButton;
+ break;
+ case 2:
+ domButton = WidgetMouseEvent::eMiddleButton;
+ break;
+ case 3:
+ domButton = WidgetMouseEvent::eRightButton;
+ break;
+ default:
+ return;
+ }
+
+ gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
+
+ WidgetMouseEvent event(true, eMouseUp, this,
+ WidgetMouseEvent::eReal);
+ event.button = domButton;
+ InitButtonEvent(event, aEvent);
+ gdouble pressure = 0;
+ gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ event.pressure = pressure ? pressure : mLastMotionPressure;
+
+ DispatchInputEvent(&event);
+ mLastMotionPressure = pressure;
+}
+
+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<nsIDOMNode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ shouldRollup = (sourceNode == nullptr);
+ }
+
+ if (shouldRollup) {
+ CheckForRollup(0, 0, false, true);
+ }
+ }
+
+#if (MOZ_WIDGET_GTK == 2) && defined(MOZ_X11)
+ // plugin lose focus
+ if (gPluginFocusWindow) {
+ RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow;
+ gPluginFocusWindow->LoseNonXEmbedPluginFocus();
+ }
+#endif /* MOZ_X11 && MOZ_WIDGET_GTK == 2 */
+
+ if (gFocusWindow) {
+ RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow;
+ if (gFocusWindow->mIMContext) {
+ gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow);
+ }
+ gFocusWindow = nullptr;
+ }
+
+ DispatchDeactivateEvent();
+
+ LOGFOCUS(("Done with container focus out [%p]\n", (void *)this));
+}
+
+bool
+nsWindow::DispatchCommandEvent(nsIAtom* aCommand)
+{
+ nsEventStatus status;
+ WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, aCommand, this);
+ DispatchEvent(&event, status);
+ return TRUE;
+}
+
+bool
+nsWindow::DispatchContentCommandEvent(EventMessage aMsg)
+{
+ nsEventStatus status;
+ WidgetContentCommandEvent event(true, aMsg, this);
+ DispatchEvent(&event, status);
+ return TRUE;
+}
+
+static bool
+IsCtrlAltTab(GdkEventKey *aEvent)
+{
+ return aEvent->keyval == GDK_Tab &&
+ KeymapWrapper::AreModifiersActive(
+ KeymapWrapper::CTRL | KeymapWrapper::ALT, aEvent->state);
+}
+
+bool
+nsWindow::DispatchKeyDownEvent(GdkEventKey *aEvent, bool *aCancelled)
+{
+ NS_PRECONDITION(aCancelled, "aCancelled must not be null");
+
+ *aCancelled = false;
+
+ if (IsCtrlAltTab(aEvent)) {
+ return false;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return FALSE;
+ }
+
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, this);
+ KeymapWrapper::InitKeyEvent(keydownEvent, aEvent);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ bool dispatched =
+ dispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent,
+ status, aEvent);
+ *aCancelled = (status == nsEventStatus_eConsumeNoDefault);
+ return dispatched ? TRUE : FALSE;
+}
+
+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();
+ }
+ CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter();
+ MOZ_ASSERT(getCurrentTime,
+ "Null current time getter despite having a window");
+ return TimeConverter().GetTimeStampFromSystemTime(aEventTime,
+ *getCurrentTime);
+}
+
+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));
+
+ // 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;
+ if (mIMContext) {
+ IMEWasEnabled = mIMContext->IsEnabled();
+ if (mIMContext->OnKeyEvent(this, aEvent)) {
+ return TRUE;
+ }
+ }
+
+ // work around for annoying things.
+ if (IsCtrlAltTab(aEvent)) {
+ return TRUE;
+ }
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+
+ // 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 (DispatchKeyDownEvent(aEvent, &isKeyDownCancelled) &&
+ (MOZ_UNLIKELY(mIsDestroyed) || isKeyDownCancelled)) {
+ return TRUE;
+ }
+
+ // 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.
+ if (!IMEWasEnabled && mIMContext && mIMContext->IsEnabled()) {
+ // Notice our keydown event was already dispatched. This prevents
+ // unnecessary DOM keydown event in the editor.
+ if (mIMContext->OnKeyEvent(this, aEvent, true)) {
+ return TRUE;
+ }
+ }
+
+ // Look for specialized app-command keys
+ switch (aEvent->keyval) {
+ case GDK_Back:
+ return DispatchCommandEvent(nsGkAtoms::Back);
+ case GDK_Forward:
+ return DispatchCommandEvent(nsGkAtoms::Forward);
+ case GDK_Refresh:
+ return DispatchCommandEvent(nsGkAtoms::Reload);
+ case GDK_Stop:
+ return DispatchCommandEvent(nsGkAtoms::Stop);
+ case GDK_Search:
+ return DispatchCommandEvent(nsGkAtoms::Search);
+ case GDK_Favorites:
+ return DispatchCommandEvent(nsGkAtoms::Bookmarks);
+ case GDK_HomePage:
+ return DispatchCommandEvent(nsGkAtoms::Home);
+ case GDK_Copy:
+ case GDK_F16: // F16, F20, F18, F14 are old keysyms for Copy Cut Paste Undo
+ return DispatchContentCommandEvent(eContentCommandCopy);
+ case GDK_Cut:
+ case GDK_F20:
+ return DispatchContentCommandEvent(eContentCommandCut);
+ case GDK_Paste:
+ case GDK_F18:
+ return DispatchContentCommandEvent(eContentCommandPaste);
+ case GDK_Redo:
+ return DispatchContentCommandEvent(eContentCommandRedo);
+ case GDK_Undo:
+ case GDK_F14:
+ return DispatchContentCommandEvent(eContentCommandUndo);
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, this);
+ KeymapWrapper::InitKeyEvent(keypressEvent, aEvent);
+
+ // before we dispatch a key, check if it's the context menu key.
+ // If so, send a context menu key event instead.
+ if (is_context_menu_key(keypressEvent)) {
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
+ WidgetMouseEvent::eReal,
+ WidgetMouseEvent::eContextMenuKey);
+
+ contextMenuEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ contextMenuEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
+ contextMenuEvent.mClickCount = 1;
+ KeymapWrapper::InitInputEvent(contextMenuEvent, aEvent->state);
+ DispatchInputEvent(&contextMenuEvent);
+ } else {
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return TRUE;
+ }
+
+ // 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.
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (keypressEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ||
+ keypressEvent.mKeyValue.Length() == 1) {
+ dispatcher->MaybeDispatchKeypressEvents(keypressEvent,
+ status, aEvent);
+ } else {
+ WidgetEventTime eventTime = GetWidgetEventTime(aEvent->time);
+ dispatcher->CommitComposition(status, &keypressEvent.mKeyValue,
+ &eventTime);
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+nsWindow::OnKeyReleaseEvent(GdkEventKey *aEvent)
+{
+ LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void *)this));
+
+ if (mIMContext && mIMContext->OnKeyEvent(this, aEvent)) {
+ return TRUE;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, this);
+ KeymapWrapper::InitKeyEvent(keyupEvent, aEvent);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ dispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, aEvent);
+
+ 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;
+#if GTK_CHECK_VERSION(3,4,0)
+ // check for duplicate legacy scroll event, see GNOME bug 726878
+ if (aEvent->direction != GDK_SCROLL_SMOOTH &&
+ mLastScrollEventTime == aEvent->time)
+ return;
+#endif
+ WidgetWheelEvent wheelEvent(true, eWheel, this);
+ wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE;
+ switch (aEvent->direction) {
+#if GTK_CHECK_VERSION(3,4,0)
+ case GDK_SCROLL_SMOOTH:
+ {
+ // As of GTK 3.4, all directional scroll events are provided by
+ // the GDK_SCROLL_SMOOTH direction on XInput2 devices.
+ mLastScrollEventTime = aEvent->time;
+ // 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;
+ // This next step manually unsets smooth scrolling for touch devices
+ // that trigger GDK_SCROLL_SMOOTH. We use the slave device, which
+ // represents the actual input.
+ 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) {
+ wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
+ }
+ break;
+ }
+#endif
+ 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::OnVisibilityNotifyEvent(GdkEventVisibility *aEvent)
+{
+ LOGDRAW(("Visibility event %i on [%p] %p\n",
+ aEvent->state, this, aEvent->window));
+
+ if (!mGdkWindow)
+ return;
+
+ switch (aEvent->state) {
+ case GDK_VISIBILITY_UNOBSCURED:
+ case GDK_VISIBILITY_PARTIAL:
+ if (mIsFullyObscured && mHasMappedToplevel) {
+ // GDK_EXPOSE events have been ignored, so make sure GDK
+ // doesn't think that the window has already been painted.
+ gdk_window_invalidate_rect(mGdkWindow, nullptr, FALSE);
+ }
+
+ mIsFullyObscured = false;
+
+ // if we have to retry the grab, retry it.
+ EnsureGrabs();
+ break;
+ default: // includes GDK_VISIBILITY_FULLY_OBSCURED
+ mIsFullyObscured = true;
+ break;
+ }
+}
+
+void
+nsWindow::OnWindowStateEvent(GtkWidget *aWidget, GdkEventWindowState *aEvent)
+{
+ LOG(("nsWindow::OnWindowStateEvent [%p] changed %d new_window_state %d\n",
+ (void *)this, 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);
+ }
+ return;
+ }
+ // else the widget is a shell widget.
+
+ // We don't care about anything but changes in the maximized/icon/fullscreen
+ // states
+ if ((aEvent->changed_mask
+ & (GDK_WINDOW_STATE_ICONIFIED |
+ GDK_WINDOW_STATE_MAXIMIZED |
+ GDK_WINDOW_STATE_FULLSCREEN)) == 0) {
+ 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 (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeState);
+ if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
+ mWidgetListener->FullscreenChanged(
+ aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN);
+ }
+ }
+}
+
+void
+nsWindow::ThemeChanged()
+{
+ NotifyThemeChanged();
+
+ 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);
+
+ nsWindow *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;
+ }
+}
+
+void
+nsWindow::OnDPIChanged()
+{
+ if (mWidgetListener) {
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->BackingScaleFactorChanged();
+ // Update menu's font size etc
+ presShell->ThemeChanged();
+ }
+ }
+}
+
+void
+nsWindow::OnCheckResize()
+{
+ mPendingConfigures++;
+}
+
+void
+nsWindow::DispatchDragEvent(EventMessage aMsg, const LayoutDeviceIntPoint& aRefPoint,
+ guint aTime)
+{
+ WidgetDragEvent event(true, aMsg, this);
+
+ if (aMsg == eDragOver) {
+ 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));
+
+ nsDragService::GetInstance()->
+ TargetDataReceived(aWidget, aDragContext, aX, aY,
+ aSelectionData, aInfo, aTime);
+}
+
+#if GTK_CHECK_VERSION(3,4,0)
+gboolean
+nsWindow::OnTouchEvent(GdkEventTouch* aEvent)
+{
+ if (!mHandleTouchEvent) {
+ 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, touch.forget());
+ // 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() = touch.forget();
+ }
+
+ DispatchInputEvent(&event);
+ return TRUE;
+}
+#endif
+
+static void
+GetBrandName(nsXPIDLString& brandName)
+{
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (bundleService)
+ bundleService->CreateBundle(
+ "chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+
+ if (bundle)
+ bundle->GetStringFromName(
+ u"brandShortName",
+ getter_Copies(brandName));
+
+ if (brandName.IsEmpty())
+ brandName.AssignLiteral(u"Mozilla");
+}
+
+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;
+
+#if (MOZ_WIDGET_GTK == 2)
+ attributes_mask |= GDK_WA_COLORMAP;
+ attributes.colormap = gtk_widget_get_colormap(widget);
+#endif
+
+ GdkWindow *window = gdk_window_new(parent, &attributes, attributes_mask);
+ gdk_window_set_user_data(window, widget);
+
+// GTK3 TODO?
+#if (MOZ_WIDGET_GTK == 2)
+ /* set the default pixmap to None so that you don't end up with the
+ gtk default which is BlackPixel. */
+ gdk_window_set_back_pixmap(window, nullptr, FALSE);
+#endif
+
+ return window;
+}
+
+nsresult
+nsWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ // 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;
+ ConstrainSize(&mBounds.width, &mBounds.height);
+
+ // figure out our parent window
+ GtkWidget *parentMozContainer = nullptr;
+ GtkContainer *parentGtkContainer = nullptr;
+ GdkWindow *parentGdkWindow = nullptr;
+ GtkWindow *topLevelParent = nullptr;
+ nsWindow *parentnsWindow = nullptr;
+ GtkWidget *eventWidget = nullptr;
+ bool shellHasCSD = false;
+
+ 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;
+
+ // get the toplevel window just in case someone needs to use it
+ // for setting transients or whatever.
+ topLevelParent =
+ GTK_WINDOW(gtk_widget_get_toplevel(parentMozContainer));
+ }
+
+ // 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 ...
+ GtkWindowType type =
+ mWindowType != eWindowType_popup || aInitData->mNoAutoHide ?
+ GTK_WINDOW_TOPLEVEL : GTK_WINDOW_POPUP;
+ mShell = gtk_window_new(type);
+
+ // 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) {
+ SetDefaultIcon();
+ gtk_window_set_wmclass(GTK_WINDOW(mShell), "Dialog",
+ gdk_get_program_class());
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_DIALOG);
+ gtk_window_set_transient_for(GTK_WINDOW(mShell),
+ topLevelParent);
+ }
+ else if (mWindowType == eWindowType_popup) {
+ // 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();
+
+ gtk_window_set_wmclass(GTK_WINDOW(mShell), "Popup",
+ gdk_get_program_class());
+
+ if (aInitData->mSupportTranslucency) {
+ // We need to select an ARGB visual here instead of in
+ // SetTransparencyMode() because it has to be done before the
+ // widget is realized. An ARGB visual is only useful if we
+ // are on a compositing window manager.
+ GdkScreen *screen = gtk_widget_get_screen(mShell);
+ if (gdk_screen_is_composited(screen)) {
+#if (MOZ_WIDGET_GTK == 2)
+ GdkColormap *colormap =
+ gdk_screen_get_rgba_colormap(screen);
+ gtk_widget_set_colormap(mShell, colormap);
+#else
+ GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
+ gtk_widget_set_visual(mShell, visual);
+#endif
+ }
+ }
+ 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.
+ 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 (topLevelParent) {
+ gtk_window_set_transient_for(GTK_WINDOW(mShell),
+ topLevelParent);
+ }
+ }
+ else { // must be eWindowType_toplevel
+ SetDefaultIcon();
+ gtk_window_set_wmclass(GTK_WINDOW(mShell), "Toplevel",
+ gdk_get_program_class());
+
+ // 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);
+ }
+
+ // Create a container to hold child windows and child GtkWidgets.
+ GtkWidget *container = moz_container_new();
+ mContainer = MOZ_CONTAINER(container);
+
+#if (MOZ_WIDGET_GTK == 3)
+ // "csd" style is set when widget is realized so we need to call
+ // it explicitly now.
+ gtk_widget_realize(mShell);
+
+ // We can't draw directly to top-level window when client side
+ // decorations are enabled. We use container with GdkWindow instead.
+ GtkStyleContext* style = gtk_widget_get_style_context(mShell);
+ shellHasCSD = gtk_style_context_has_class(style, "csd");
+#endif
+ if (!shellHasCSD) {
+ // Use mShell's window for drawing and events.
+ gtk_widget_set_has_window(container, FALSE);
+ // Prevent GtkWindow from painting a background to flicker.
+ gtk_widget_set_app_paintable(mShell, TRUE);
+ }
+ // Set up event widget
+ eventWidget = shellHasCSD ? container : mShell;
+ gtk_widget_add_events(eventWidget, kEvents);
+
+ gtk_container_add(GTK_CONTAINER(mShell), container);
+ gtk_widget_realize(container);
+
+ // make sure this is the focus widget in the container
+ gtk_widget_show(container);
+ 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);
+
+ 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.
+ if (aInitData->mMouseTransparent) {
+#if (MOZ_WIDGET_GTK == 2)
+ GdkRectangle rect = { 0, 0, 0, 0 };
+ GdkRegion *region = gdk_region_rectangle(&rect);
+
+ gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0);
+ gdk_region_destroy(region);
+#else
+ cairo_rectangle_int_t rect = { 0, 0, 0, 0 };
+ cairo_region_t *region = cairo_region_create_rectangle(&rect);
+
+ gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0);
+ cairo_region_destroy(region);
+#endif
+ }
+ }
+ }
+ break;
+ case eWindowType_plugin:
+ case eWindowType_plugin_ipc_chrome:
+ case eWindowType_plugin_ipc_content:
+ 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 (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);
+
+ GtkSettings* default_settings = gtk_settings_get_default();
+ g_signal_connect_after(default_settings,
+ "notify::gtk-theme-name",
+ G_CALLBACK(theme_changed_cb), this);
+ g_signal_connect_after(default_settings,
+ "notify::gtk-font-name",
+ G_CALLBACK(theme_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);
+#if (MOZ_WIDGET_GTK == 3)
+ g_signal_connect(mContainer, "notify::scale-factor",
+ G_CALLBACK(scale_changed_cb), nullptr);
+#endif
+ // Initialize mHasMappedToplevel.
+ hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr);
+ // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW
+ // widgets.
+#if (MOZ_WIDGET_GTK == 2)
+ g_signal_connect(mContainer, "expose_event",
+ G_CALLBACK(expose_event_cb), nullptr);
+#else
+ g_signal_connect(G_OBJECT(mContainer), "draw",
+ G_CALLBACK(expose_event_cb), nullptr);
+#endif
+ 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);
+
+ GtkWidget *widgets[] = { GTK_WIDGET(mContainer),
+ !shellHasCSD ? mShell : nullptr };
+ for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) {
+ // Visibility events are sent to the owning widget of the relevant
+ // window but do not propagate to parent widgets so connect on
+ // mShell (if it exists) as well as mContainer.
+ g_signal_connect(widgets[i], "visibility-notify-event",
+ G_CALLBACK(visibility_notify_event_cb), nullptr);
+ // Similarly 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);
+ }
+
+ // 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) {
+#if (MOZ_WIDGET_GTK == 2)
+ // Don't let GTK mess with the shapes of our GdkWindows
+ GTK_PRIVATE_SET_FLAG(eventWidget, GTK_HAS_SHAPE_MASK);
+#endif
+
+ // 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, "property-notify-event",
+ G_CALLBACK(property_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "scroll-event",
+ G_CALLBACK(scroll_event_cb), nullptr);
+#if GTK_CHECK_VERSION(3,4,0)
+ g_signal_connect(eventWidget, "touch-event",
+ G_CALLBACK(touch_event_cb), nullptr);
+#endif
+ }
+
+ LOG(("nsWindow [%p]\n", (void *)this));
+ if (mShell) {
+ LOG(("\tmShell %p mContainer %p mGdkWindow %p 0x%lx\n",
+ mShell, mContainer, mGdkWindow,
+ gdk_x11_window_get_xid(mGdkWindow)));
+ } 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);
+
+ mSurfaceProvider.Initialize(mXDisplay, mXWindow, mXVisual, mXDepth);
+ }
+#endif
+
+ return NS_OK;
+}
+
+void
+nsWindow::SetWindowClass(const nsAString &xulWinType)
+{
+ if (!mShell)
+ return;
+
+ const char *res_class = gdk_get_program_class();
+ if (!res_class)
+ return;
+
+ char *res_name = ToNewCString(xulWinType);
+ 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;
+
+ gdk_window_set_role(mGdkWindow, role);
+
+#ifdef MOZ_X11
+ if (mIsX11Display) {
+ XClassHint *class_hint = XAllocClassHint();
+ if (!class_hint) {
+ free(res_name);
+ return;
+ }
+ class_hint->res_name = res_name;
+ 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(mGdkWindow),
+ class_hint);
+ XFree(class_hint);
+ }
+#endif /* MOZ_X11 */
+
+ free(res_name);
+}
+
+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) {
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ }
+ 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 X11CompositorWidget 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();
+ }
+
+ 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 (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.
+ 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 X11CompositorWidget 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::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);
+ }
+
+ gtk_widget_show(mShell);
+ }
+ else if (mContainer) {
+ gtk_widget_show(GTK_WIDGET(mContainer));
+ }
+ else if (mGdkWindow) {
+ gdk_window_show_unraised(mGdkWindow);
+ }
+ }
+ 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 && !mIsFullyObscured) {
+ // GDK_EXPOSE events have been ignored but the window is now visible,
+ // so make sure GDK doesn't think that the window has already been
+ // painted.
+ gdk_window_invalidate_rect(mGdkWindow, nullptr, FALSE);
+
+ // 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.
+ LayoutDeviceIntSize result = aSize;
+ const int32_t kInt16Max = 32767;
+ if (result.width > kInt16Max) {
+ result.width = kInt16Max;
+ }
+ if (result.height > kInt16Max) {
+ result.height = kInt16Max;
+ }
+ 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 (!isTransparent) {
+ ClearTransparencyBitmap();
+ } // else the new default alpha values are "all 1", so we don't
+ // need to change anything yet
+
+ mIsTransparent = isTransparent;
+
+ // Need to clean our LayerManager up while still alive because
+ // we don't want to use layers acceleration on shaped windows
+ 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;
+}
+
+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];
+ nsWindow* 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;
+
+#if (MOZ_WIDGET_GTK == 2)
+ GdkRegion *region = gdk_region_new(); // aborts on OOM
+ for (uint32_t i = 0; i < newRects->Length(); ++i) {
+ const LayoutDeviceIntRect& r = newRects->ElementAt(i);
+ GdkRectangle rect = { r.x, r.y, r.width, r.height };
+ gdk_region_union_with_rect(region, &rect);
+ }
+
+ gdk_window_shape_combine_region(mGdkWindow, region, 0, 0);
+ gdk_region_destroy(region);
+#else
+ 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);
+#endif
+
+ 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;
+ gchar* 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
+#if (MOZ_WIDGET_GTK == 2)
+ gtk_widget_reset_shapes(mShell);
+ GdkBitmap* maskBitmap = gdk_bitmap_create_from_data(mGdkWindow,
+ mTransparencyBitmap,
+ mTransparencyBitmapWidth, mTransparencyBitmapHeight);
+ if (!maskBitmap)
+ return;
+
+ gtk_widget_shape_combine_mask(mShell, maskBitmap, 0, 0);
+ g_object_unref(maskBitmap);
+#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_WIDGET_GTK == 2
+#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");
+
+ 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;
+}
+
+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 || mIsFullyObscured) {
+ LOG(("GrabPointer: window not visible\n"));
+ mRetryPointerGrab = true;
+ return;
+ }
+
+ if (!mGdkWindow)
+ return;
+
+ gint retval;
+ 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),
+ (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(this, &nsWindow::CheckForRollupDuringGrab);
+ NS_DispatchToCurrentThread(event.forget());
+ }
+}
+
+void
+nsWindow::ReleaseGrabs(void)
+{
+ LOG(("ReleaseGrabs\n"));
+
+ mRetryPointerGrab = false;
+ 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::SetupPluginPort(void)
+{
+ if (!mGdkWindow)
+ return nullptr;
+
+ if (gdk_window_is_destroyed(mGdkWindow) == TRUE)
+ return nullptr;
+
+ Window window = gdk_x11_window_get_xid(mGdkWindow);
+
+ // we have to flush the X queue here so that any plugins that
+ // might be running on separate X connections will be able to use
+ // this window in case it was just created
+#ifdef MOZ_X11
+ XWindowAttributes xattrs;
+ Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ XGetWindowAttributes(display, window, &xattrs);
+ XSelectInput (display, window,
+ xattrs.your_event_mask |
+ SubstructureNotifyMask);
+
+ gdk_window_add_filter(mGdkWindow, plugin_window_filter_func, this);
+
+ XSync(display, False);
+#endif /* MOZ_X11 */
+
+ return (void *)window;
+}
+
+void
+nsWindow::SetDefaultIcon(void)
+{
+ SetIcon(NS_LITERAL_STRING("default"));
+}
+
+void
+nsWindow::SetPluginType(PluginType aPluginType)
+{
+ mPluginType = aPluginType;
+}
+
+#ifdef MOZ_X11
+void
+nsWindow::SetNonXEmbedPluginFocus()
+{
+ if (gPluginFocusWindow == this || mPluginType!=PluginType_NONXEMBED) {
+ return;
+ }
+
+ if (gPluginFocusWindow) {
+ RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow;
+ gPluginFocusWindow->LoseNonXEmbedPluginFocus();
+ }
+
+ LOGFOCUS(("nsWindow::SetNonXEmbedPluginFocus\n"));
+
+ Window curFocusWindow;
+ int focusState;
+
+ GdkDisplay *gdkDisplay = gdk_window_get_display(mGdkWindow);
+ XGetInputFocus(gdk_x11_display_get_xdisplay(gdkDisplay),
+ &curFocusWindow,
+ &focusState);
+
+ LOGFOCUS(("\t curFocusWindow=%p\n", curFocusWindow));
+
+ GdkWindow* toplevel = gdk_window_get_toplevel(mGdkWindow);
+#if (MOZ_WIDGET_GTK == 2)
+ GdkWindow *gdkfocuswin = gdk_window_lookup(curFocusWindow);
+#else
+ GdkWindow *gdkfocuswin = gdk_x11_window_lookup_for_display(gdkDisplay,
+ curFocusWindow);
+#endif
+
+ // lookup with the focus proxy window is supposed to get the
+ // same GdkWindow as toplevel. If the current focused window
+ // is not the focus proxy, we return without any change.
+ if (gdkfocuswin != toplevel) {
+ return;
+ }
+
+ // switch the focus from the focus proxy to the plugin window
+ mOldFocusWindow = curFocusWindow;
+ XRaiseWindow(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ gdk_x11_window_get_xid(mGdkWindow));
+ gdk_error_trap_push();
+ XSetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ gdk_x11_window_get_xid(mGdkWindow),
+ RevertToNone,
+ CurrentTime);
+ gdk_flush();
+#if (MOZ_WIDGET_GTK == 3)
+ gdk_error_trap_pop_ignored();
+#else
+ gdk_error_trap_pop();
+#endif
+ gPluginFocusWindow = this;
+ gdk_window_add_filter(nullptr, plugin_client_message_filter, this);
+
+ LOGFOCUS(("nsWindow::SetNonXEmbedPluginFocus oldfocus=%p new=%p\n",
+ mOldFocusWindow, gdk_x11_window_get_xid(mGdkWindow)));
+}
+
+void
+nsWindow::LoseNonXEmbedPluginFocus()
+{
+ LOGFOCUS(("nsWindow::LoseNonXEmbedPluginFocus\n"));
+
+ // This method is only for the nsWindow which contains a
+ // Non-XEmbed plugin, for example, JAVA plugin.
+ if (gPluginFocusWindow != this || mPluginType!=PluginType_NONXEMBED) {
+ return;
+ }
+
+ Window curFocusWindow;
+ int focusState;
+
+ XGetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ &curFocusWindow,
+ &focusState);
+
+ // we only switch focus between plugin window and focus proxy. If the
+ // current focused window is not the plugin window, just removing the
+ // event filter that blocks the WM_TAKE_FOCUS is enough. WM and gtk2
+ // will take care of the focus later.
+ if (!curFocusWindow ||
+ curFocusWindow == gdk_x11_window_get_xid(mGdkWindow)) {
+
+ gdk_error_trap_push();
+ XRaiseWindow(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ mOldFocusWindow);
+ XSetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow),
+ mOldFocusWindow,
+ RevertToParent,
+ CurrentTime);
+ gdk_flush();
+#if (MOZ_WIDGET_GTK == 3)
+ gdk_error_trap_pop_ignored();
+#else
+ gdk_error_trap_pop();
+#endif
+ }
+ gPluginFocusWindow = nullptr;
+ mOldFocusWindow = 0;
+ gdk_window_remove_filter(nullptr, plugin_client_message_filter, this);
+
+ LOGFOCUS(("nsWindow::LoseNonXEmbedPluginFocus end\n"));
+}
+#endif /* MOZ_X11 */
+
+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);
+ 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)
+{
+ GdkScreen* screen = gtk_widget_get_screen(mShell);
+ if (!gdk_screen_is_composited(screen)) {
+ 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);
+}
+
+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 (!IsFullscreenSupported(mShell)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aFullScreen) {
+ if (mSizeMode != nsSizeMode_Fullscreen)
+ mLastSizeMode = mSizeMode;
+
+ mSizeMode = nsSizeMode_Fullscreen;
+ gtk_window_fullscreen(GTK_WINDOW(mShell));
+ }
+ else {
+ mSizeMode = mLastSizeMode;
+ gtk_window_unfullscreen(GTK_WINDOW(mShell));
+ }
+
+ NS_ASSERTION(mLastSizeMode != nsSizeMode_Fullscreen,
+ "mLastSizeMode should never be fullscreen");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::HideWindowChrome(bool aShouldHide)
+{
+ 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->HideWindowChrome(aShouldHide);
+ }
+
+ // 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(mGdkWindow)) {
+ gdk_window_hide(mGdkWindow);
+ wasVisible = true;
+ }
+
+ gint wmd;
+ if (aShouldHide)
+ wmd = 0;
+ else
+ wmd = ConvertBorderStyles(mBorderStyle);
+
+ if (wmd != -1)
+ gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration) wmd);
+
+ if (wasVisible)
+ gdk_window_show(mGdkWindow);
+
+ // 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
+ XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()) , False);
+#else
+ gdk_flush ();
+#endif /* MOZ_X11 */
+
+ return NS_OK;
+}
+
+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;
+ GdkWindow *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 (uint32_t i=0; i<widgetChain.Length(); ++i) {
+ nsIWidget* widget = widgetChain[i];
+ GdkWindow* 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 = IntPoint::Truncate(aMouseX, aMouseY);
+ 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);
+ }
+
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_drawable_get_size(aWindow, &w, &h);
+#else
+ w = gdk_window_get_width(aWindow);
+ h = gdk_window_get_height(aWindow);
+#endif
+
+ 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:
+ newType = MOZ_CURSOR_ZOOM_IN;
+ break;
+ case eCursor_zoom_out:
+ 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
+
+#if (MOZ_WIDGET_GTK == 2)
+static gboolean
+expose_event_cb(GtkWidget *widget, GdkEventExpose *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window)
+ return FALSE;
+
+ window->OnExposeEvent(event);
+ return FALSE;
+}
+#else
+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;
+}
+#endif //MOZ_WIDGET_GTK == 2
+
+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 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.
+ gint x = gint(event->x_root);
+ gint 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)
+{
+ XEvent* 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;
+}
+
+static GdkFilterReturn
+plugin_window_filter_func(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
+{
+ GdkWindow *plugin_window;
+ XEvent *xevent;
+ Window xeventWindow;
+
+ RefPtr<nsWindow> nswindow = (nsWindow*)data;
+ GdkFilterReturn return_val;
+
+ xevent = (XEvent *)gdk_xevent;
+ return_val = GDK_FILTER_CONTINUE;
+
+ switch (xevent->type)
+ {
+ case CreateNotify:
+ case ReparentNotify:
+ if (xevent->type==CreateNotify) {
+ xeventWindow = xevent->xcreatewindow.window;
+ }
+ else {
+ if (xevent->xreparent.event != xevent->xreparent.parent)
+ break;
+ xeventWindow = xevent->xreparent.window;
+ }
+#if (MOZ_WIDGET_GTK == 2)
+ plugin_window = gdk_window_lookup(xeventWindow);
+#else
+ plugin_window = gdk_x11_window_lookup_for_display(
+ gdk_x11_lookup_xdisplay(xevent->xcreatewindow.display), xeventWindow);
+#endif
+ if (plugin_window) {
+ GtkWidget *widget =
+ get_gtk_widget_for_gdk_window(plugin_window);
+
+// TODO GTK3
+#if (MOZ_WIDGET_GTK == 2)
+ if (GTK_IS_XTBIN(widget)) {
+ nswindow->SetPluginType(nsWindow::PluginType_NONXEMBED);
+ break;
+ }
+ else
+#endif
+ if(GTK_IS_SOCKET(widget)) {
+ if (!g_object_get_data(G_OBJECT(widget), "enable-xt-focus")) {
+ nswindow->SetPluginType(nsWindow::PluginType_XEMBED);
+ break;
+ }
+ }
+ }
+ nswindow->SetPluginType(nsWindow::PluginType_NONXEMBED);
+ return_val = GDK_FILTER_REMOVE;
+ break;
+ case EnterNotify:
+ nswindow->SetNonXEmbedPluginFocus();
+ break;
+ case DestroyNotify:
+ gdk_window_remove_filter
+ ((GdkWindow*)(nswindow->GetNativeData(NS_NATIVE_WINDOW)),
+ plugin_window_filter_func,
+ nswindow);
+ // Currently we consider all plugins are non-xembed and calls
+ // LoseNonXEmbedPluginFocus without any checking.
+ nswindow->LoseNonXEmbedPluginFocus();
+ break;
+ default:
+ break;
+ }
+ return return_val;
+}
+
+static GdkFilterReturn
+plugin_client_message_filter(GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data)
+{
+ XEvent *xevent;
+ xevent = (XEvent *)gdk_xevent;
+
+ GdkFilterReturn return_val;
+ return_val = GDK_FILTER_CONTINUE;
+
+ if (!gPluginFocusWindow || xevent->type!=ClientMessage) {
+ return return_val;
+ }
+
+ // When WM sends out WM_TAKE_FOCUS, gtk2 will use XSetInputFocus
+ // to set the focus to the focus proxy. To prevent this happen
+ // while the focus is on the plugin, we filter the WM_TAKE_FOCUS
+ // out.
+ if (gdk_x11_get_xatom_by_name("WM_PROTOCOLS")
+ != xevent->xclient.message_type) {
+ return return_val;
+ }
+
+ if ((Atom) xevent->xclient.data.l[0] ==
+ gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS")) {
+ // block it from gtk2.0 focus proxy
+ return_val = GDK_FILTER_REMOVE;
+ }
+
+ return return_val;
+}
+#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 */
+ 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 gboolean
+visibility_notify_event_cb (GtkWidget *widget, GdkEventVisibility *event)
+{
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window)
+ return FALSE;
+
+ window->OnVisibilityNotifyEvent(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
+theme_changed_cb (GtkSettings *settings, GParamSpec *pspec, nsWindow *data)
+{
+ RefPtr<nsWindow> window = data;
+ window->ThemeChanged();
+}
+
+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();
+}
+
+#if (MOZ_WIDGET_GTK == 3)
+static void
+scale_changed_cb (GtkWidget* widget, GParamSpec* aPSpec, gpointer aPointer)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnDPIChanged();
+
+ // configure_event is already fired before scale-factor signal,
+ // but size-allocate isn't fired by changing scale
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(widget, &allocation);
+ window->OnSizeAllocate(&allocation);
+}
+#endif
+
+#if GTK_CHECK_VERSION(3,4,0)
+static gboolean
+touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent)
+{
+ UpdateLastInputEventTime(aEvent);
+
+ nsWindow* window = GetFirstNSWindowForGDKWindow(aEvent->window);
+ if (!window) {
+ return FALSE;
+ }
+
+ return window->OnTouchEvent(aEvent);
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// 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);
+}
+
+static gboolean
+drag_motion_event_cb(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ guint aTime,
+ gpointer aData)
+{
+ 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 });
+
+ return nsDragService::GetInstance()->
+ ScheduleMotionEvent(innerMostWindow, aDragContext,
+ point, aTime);
+}
+
+static void
+drag_leave_event_cb(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ guint aTime,
+ gpointer aData)
+{
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window)
+ return;
+
+ 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 gboolean
+drag_drop_event_cb(GtkWidget *aWidget,
+ GdkDragContext *aDragContext,
+ gint aX,
+ gint aY,
+ guint aTime,
+ gpointer aData)
+{
+ 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 });
+
+ return nsDragService::GetInstance()->
+ ScheduleDropEvent(innerMostWindow, aDragContext,
+ point, 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);
+
+ 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)) {
+ GdkWindow *childWindow = (GdkWindow *) child->data;
+ if (get_window_for_gdk_window(childWindow)) {
+#if (MOZ_WIDGET_GTK == 2)
+ gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch, nullptr);
+#else
+ gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch);
+#endif
+ 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 inline bool
+is_context_menu_key(const WidgetKeyboardEvent& aKeyEvent)
+{
+ return ((aKeyEvent.mKeyCode == NS_VK_F10 && aKeyEvent.IsShift() &&
+ !aKeyEvent.IsControl() && !aKeyEvent.IsMeta() &&
+ !aKeyEvent.IsAlt()) ||
+ (aKeyEvent.mKeyCode == NS_VK_CONTEXT_MENU && !aKeyEvent.IsShift() &&
+ !aKeyEvent.IsControl() && !aKeyEvent.IsMeta() &&
+ !aKeyEvent.IsAlt()));
+}
+
+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 */
+
+// nsChildWindow class
+
+nsChildWindow::nsChildWindow()
+{
+}
+
+nsChildWindow::~nsChildWindow()
+{
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ if (!mIMContext) {
+ return;
+ }
+ mIMContext->SetInputContext(this, &aContext, &aAction);
+}
+
+NS_IMETHODIMP_(InputContext)
+nsWindow::GetInputContext()
+{
+ InputContext context;
+ if (!mIMContext) {
+ context.mIMEState.mEnabled = IMEState::DISABLED;
+ context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ } else {
+ context = mIMContext->GetInputContext();
+ }
+ return context;
+}
+
+nsIMEUpdatePreference
+nsWindow::GetIMEUpdatePreference()
+{
+ if (!mIMContext) {
+ return nsIMEUpdatePreference();
+ }
+ return mIMContext->GetIMEUpdatePreference();
+}
+
+NS_IMETHODIMP_(TextEventDispatcherListener*)
+nsWindow::GetNativeTextEventDispatcherListener()
+{
+ if (NS_WARN_IF(!mIMContext)) {
+ return nullptr;
+ }
+ return mIMContext;
+}
+
+bool
+nsWindow::ExecuteNativeKeyBindingRemapped(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ uint32_t aGeckoKeyCode,
+ uint32_t aNativeKeyCode)
+{
+ WidgetKeyboardEvent modifiedEvent(aEvent);
+ modifiedEvent.mKeyCode = aGeckoKeyCode;
+ static_cast<GdkEventKey*>(modifiedEvent.mNativeKeyEvent)->keyval =
+ aNativeKeyCode;
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(modifiedEvent, aCallback, aCallbackData);
+}
+
+NS_IMETHODIMP_(bool)
+nsWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ 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.
+ WidgetQueryContentEvent query(true, eQuerySelectedText, this);
+ nsEventStatus status;
+ DispatchEvent(&query, status);
+
+ if (query.mSucceeded && query.mReply.mWritingMode.IsVertical()) {
+ uint32_t geckoCode = 0;
+ uint32_t gdkCode = 0;
+ switch (aEvent.mKeyCode) {
+ case NS_VK_LEFT:
+ if (query.mReply.mWritingMode.IsVerticalLR()) {
+ geckoCode = NS_VK_UP;
+ gdkCode = GDK_Up;
+ } else {
+ geckoCode = NS_VK_DOWN;
+ gdkCode = GDK_Down;
+ }
+ break;
+
+ case NS_VK_RIGHT:
+ if (query.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;
+ }
+
+ return ExecuteNativeKeyBindingRemapped(aType, aEvent, aCallback,
+ aCallbackData,
+ geckoCode, gdkCode);
+ }
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(aEvent, aCallback, aCallbackData);
+}
+
+#if defined(MOZ_X11) && (MOZ_WIDGET_GTK == 2)
+/* static */ already_AddRefed<DrawTarget>
+nsWindow::GetDrawTargetForGdkDrawable(GdkDrawable* aDrawable,
+ const IntSize& aSize)
+{
+ GdkVisual* visual = gdk_drawable_get_visual(aDrawable);
+ Screen* xScreen =
+ gdk_x11_screen_get_xscreen(gdk_drawable_get_screen(aDrawable));
+ Display* xDisplay = DisplayOfScreen(xScreen);
+ Drawable xDrawable = gdk_x11_drawable_get_xid(aDrawable);
+
+ RefPtr<gfxASurface> surface;
+
+ if (visual) {
+ Visual* xVisual = gdk_x11_visual_get_xvisual(visual);
+
+ surface = new gfxXlibSurface(xDisplay, xDrawable, xVisual, aSize);
+ } else {
+ // no visual? we must be using an xrender format. Find a format
+ // for this depth.
+ XRenderPictFormat *pf = nullptr;
+ switch (gdk_drawable_get_depth(aDrawable)) {
+ case 32:
+ pf = XRenderFindStandardFormat(xDisplay, PictStandardARGB32);
+ break;
+ case 24:
+ pf = XRenderFindStandardFormat(xDisplay, PictStandardRGB24);
+ break;
+ default:
+ NS_ERROR("Don't know how to handle the given depth!");
+ break;
+ }
+
+ surface = new gfxXlibSurface(xScreen, xDrawable, pf, aSize);
+ }
+
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surface, aSize);
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ return dt.forget();
+}
+#endif
+
+already_AddRefed<DrawTarget>
+nsWindow::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode)
+{
+ return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion, aBufferMode);
+}
+
+void
+nsWindow::EndRemoteDrawingInRegion(DrawTarget* aDrawTarget,
+ 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->button != WidgetMouseEvent::eLeftButton) {
+ // 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;
+ }
+
+ // 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;
+}
+
+NS_IMETHODIMP
+nsWindow::BeginMoveDrag(WidgetMouseEvent* aEvent)
+{
+ MOZ_ASSERT(aEvent, "must have event");
+ MOZ_ASSERT(aEvent->mClass == eMouseEventClass,
+ "event must have correct struct type");
+
+ GdkWindow *gdk_window;
+ gint button, screenX, screenY;
+ if (!GetDragInfo(aEvent, &gdk_window, &button, &screenX, &screenY)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // tell the window manager to start the move
+ screenX = DevicePixelsToGdkCoordRoundDown(screenX);
+ screenY = DevicePixelsToGdkCoordRoundDown(screenY);
+ gdk_window_begin_move_drag(gdk_window, button, screenX, screenY,
+ aEvent->mTime);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+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;
+ }
+ if (!mLayerManager && eTransparencyTransparent == GetTransparencyMode()) {
+ mLayerManager = CreateBasicLayerManager();
+ }
+
+ return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint, aPersistence);
+}
+
+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();
+ }
+ }
+}
+
+gint
+nsWindow::GdkScaleFactor()
+{
+#if (MOZ_WIDGET_GTK >= 3)
+ // Available as of GTK 3.10+
+ static auto sGdkWindowGetScaleFactorPtr = (gint (*)(GdkWindow*))
+ dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor");
+ if (sGdkWindowGetScaleFactorPtr && mGdkWindow)
+ return (*sGdkWindowGetScaleFactorPtr)(mGdkWindow);
+#endif
+ return nsScreenGtk::GetGtkMonitorScaleFactor();
+}
+
+
+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;
+
+#if (MOZ_WIDGET_GTK == 3)
+ // 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);
+#endif
+
+ 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;
+#if (MOZ_WIDGET_GTK == 3)
+ // 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);
+#endif
+ 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.
+#if GTK_CHECK_VERSION(3,4,0)
+ // TODO: is this correct? I don't have GTK 3.4+ so I can't check
+ event.scroll.direction = GDK_SCROLL_SMOOTH;
+ event.scroll.delta_x = -aDeltaX;
+ event.scroll.delta_y = -aDeltaY;
+#else
+ if (aDeltaX < 0) {
+ event.scroll.direction = GDK_SCROLL_RIGHT;
+ } else if (aDeltaX > 0) {
+ event.scroll.direction = GDK_SCROLL_LEFT;
+ } else if (aDeltaY < 0) {
+ event.scroll.direction = GDK_SCROLL_DOWN;
+ } else if (aDeltaY > 0) {
+ event.scroll.direction = GDK_SCROLL_UP;
+ } else {
+ return NS_OK;
+ }
+#endif
+
+ gdk_event_put(&event);
+
+ return NS_OK;
+}
+
+#if GTK_CHECK_VERSION(3,4,0)
+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;
+}
+#endif
+
+int32_t
+nsWindow::RoundsWidgetCoordinatesTo()
+{
+ return GdkScaleFactor();
+}
+
+void nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData)
+{
+ #ifdef MOZ_X11
+ *aInitData = mozilla::widget::CompositorWidgetInitData(
+ mXWindow,
+ nsCString(XDisplayString(mXDisplay)),
+ GetClientSize());
+ #endif
+}
diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h
new file mode 100644
index 000000000..49a8d4baf
--- /dev/null
+++ b/widget/gtk/nsWindow.h
@@ -0,0 +1,580 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsWindow_h__
+#define __nsWindow_h__
+
+#include "mozcontainer.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIDragService.h"
+#include "nsITimer.h"
+#include "nsGkAtoms.h"
+#include "nsRefPtrHashtable.h"
+
+#include "nsBaseWidget.h"
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#ifdef MOZ_X11
+#include <gdk/gdkx.h>
+#endif /* MOZ_X11 */
+
+#include "mozilla/widget/WindowSurface.h"
+#include "mozilla/widget/WindowSurfaceProvider.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 PRLogModuleInfo *gWidgetLog;
+extern PRLogModuleInfo *gWidgetFocusLog;
+extern PRLogModuleInfo *gWidgetDragLog;
+extern PRLogModuleInfo *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 */
+
+class gfxPattern;
+class nsPluginNativeWindowGtk;
+
+namespace mozilla {
+class TimeStamp;
+class CurrentX11TimeGetter;
+}
+
+class nsWindow : public nsBaseWidget
+{
+public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::WidgetEventTime WidgetEventTime;
+
+ nsWindow();
+
+ static void ReleaseGlobals();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ 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
+ virtual MOZ_MUST_USE 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;
+ // Under Gtk, we manage windows using device pixels so no scaling is needed:
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final
+ {
+ return mozilla::DesktopToLayoutDeviceScale(1.0);
+ }
+ virtual nsresult 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;
+ NS_IMETHOD Move(double aX,
+ double aY) override;
+ NS_IMETHOD Show (bool aState) override;
+ NS_IMETHOD Resize (double aWidth,
+ double aHeight,
+ bool aRepaint) override;
+ NS_IMETHOD Resize (double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint) override;
+ virtual bool IsEnabled() const override;
+
+ void SetZIndex(int32_t aZIndex) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ NS_IMETHOD Enable(bool aState) override;
+ NS_IMETHOD SetFocus(bool aRaise = false) override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntSize GetClientSize() override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ NS_IMETHOD SetCursor(nsCursor aCursor) override;
+ NS_IMETHOD SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) override;
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override;
+ NS_IMETHOD 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;
+ NS_IMETHOD 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 nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aTargetScreen = nullptr) override;
+ NS_IMETHOD 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);
+
+ // event callbacks
+#if (MOZ_WIDGET_GTK == 2)
+ gboolean OnExposeEvent(GdkEventExpose *aEvent);
+#else
+ gboolean OnExposeEvent(cairo_t *cr);
+#endif
+ 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 OnVisibilityNotifyEvent(GdkEventVisibility *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);
+#if GTK_CHECK_VERSION(3,4,0)
+ gboolean OnTouchEvent(GdkEventTouch* aEvent);
+#endif
+
+ virtual already_AddRefed<mozilla::gfx::DrawTarget>
+ StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ mozilla::layers::BufferMode* aBufferMode) override;
+ virtual void EndRemoteDrawingInRegion(mozilla::gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion) override;
+
+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 UpdateClientOffset();
+
+public:
+ enum PluginType {
+ PluginType_NONE = 0, /* do not have any plugin */
+ PluginType_XEMBED, /* the plugin support xembed */
+ PluginType_NONXEMBED /* the plugin does not support xembed */
+ };
+
+ void SetPluginType(PluginType aPluginType);
+#ifdef MOZ_X11
+ void SetNonXEmbedPluginFocus(void);
+ void LoseNonXEmbedPluginFocus(void);
+#endif /* MOZ_X11 */
+
+ void ThemeChanged(void);
+ void OnDPIChanged(void);
+ void OnCheckResize(void);
+
+#ifdef MOZ_X11
+ Window mOldFocusWindow;
+#endif /* MOZ_X11 */
+
+ static guint32 sLastButtonPressTime;
+
+ NS_IMETHOD BeginResizeDrag(mozilla::WidgetGUIEvent* aEvent,
+ int32_t aHorizontal,
+ int32_t aVertical) override;
+ NS_IMETHOD BeginMoveDrag(mozilla::WidgetMouseEvent* aEvent) override;
+
+ MozContainer* GetMozContainer() { return mContainer; }
+ // GetMozContainerWidget returns the MozContainer even for undestroyed
+ // descendant windows
+ GtkWidget* GetMozContainerWidget();
+ GdkWindow* GetGdkWindow() { return mGdkWindow; }
+ bool IsDestroyed() { return mIsDestroyed; }
+
+ void DispatchDragEvent(mozilla::EventMessage aMsg,
+ const LayoutDeviceIntPoint& aRefPoint,
+ guint aTime);
+ static void UpdateDragStatus (GdkDragContext *aDragContext,
+ nsIDragService *aDragService);
+ // If this dispatched the keydown event actually, this returns TRUE,
+ // otherwise, FALSE.
+ bool DispatchKeyDownEvent(GdkEventKey *aEvent,
+ bool *aIsCancelled);
+ WidgetEventTime GetWidgetEventTime(guint32 aEventTime);
+ mozilla::TimeStamp GetEventTimeStamp(guint32 aEventTime);
+ mozilla::CurrentX11TimeGetter* GetCurrentTimeGetter();
+
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override;
+ virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
+ NS_IMETHOD_(TextEventDispatcherListener*)
+ GetNativeTextEventDispatcherListener() override;
+ bool ExecuteNativeKeyBindingRemapped(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ uint32_t aGeckoKeyCode,
+ uint32_t aNativeKeyCode);
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) 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 nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ nsresult UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
+ uint8_t* aAlphas, int32_t aStride);
+
+#if (MOZ_WIDGET_GTK == 2)
+ static already_AddRefed<DrawTarget> GetDrawTargetForGdkDrawable(GdkDrawable* aDrawable,
+ const mozilla::gfx::IntSize& aSize);
+#endif
+ 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;
+
+#if GTK_CHECK_VERSION(3,4,0)
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+#endif
+
+#ifdef MOZ_X11
+ Display* XDisplay() { return mXDisplay; }
+#endif
+ virtual void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) 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;
+protected:
+ virtual ~nsWindow();
+
+ // event handling code
+ void DispatchActivateEvent(void);
+ void DispatchDeactivateEvent(void);
+ void DispatchResized();
+ void MaybeDispatchResized();
+
+ // Helper for SetParent and ReparentNativeWidget.
+ void ReparentNativeWidgetInternal(nsIWidget* aNewParent,
+ GtkWidget* aNewContainer,
+ GdkWindow* aNewParentWindow,
+ GtkWidget* aOldContainer);
+
+ virtual void RegisterTouchWindow() override;
+
+ 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;
+#if GTK_CHECK_VERSION(3,4,0)
+ // whether we handle touch event
+ bool mHandleTouchEvent;
+#endif
+ // true if this is a drag and drop feedback popup
+ bool mIsDragPopup;
+ // Can we access X?
+ bool mIsX11Display;
+
+private:
+ void DestroyChildWindows();
+ GtkWidget *GetToplevelWidget();
+ nsWindow *GetContainerWindow();
+ void SetUrgencyHint(GtkWidget *top_window, bool state);
+ void *SetupPluginPort(void);
+ void SetDefaultIcon(void);
+ void InitButtonEvent(mozilla::WidgetMouseEvent& aEvent,
+ GdkEventButton* aGdkEvent);
+ bool DispatchCommandEvent(nsIAtom* aCommand);
+ bool DispatchContentCommandEvent(mozilla::EventMessage aMsg);
+ 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();
+
+ GtkWidget *mShell;
+ MozContainer *mContainer;
+ GdkWindow *mGdkWindow;
+
+ uint32_t mHasMappedToplevel : 1,
+ mIsFullyObscured : 1,
+ mRetryPointerGrab : 1;
+ nsSizeMode mSizeState;
+ PluginType mPluginType;
+
+ int32_t mTransparencyBitmapWidth;
+ int32_t mTransparencyBitmapHeight;
+
+ nsIntPoint mClientOffset;
+
+#if GTK_CHECK_VERSION(3,4,0)
+ // This field omits duplicate scroll events caused by GNOME bug 726878.
+ guint32 mLastScrollEventTime;
+
+ // for touch event handling
+ nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch> mTouches;
+#endif
+
+#ifdef MOZ_X11
+ Display* mXDisplay;
+ Window mXWindow;
+ Visual* mXVisual;
+ int mXDepth;
+ mozilla::widget::WindowSurfaceProvider mSurfaceProvider;
+#endif
+
+ // Upper bound on pending ConfigureNotify events to be dispatched to the
+ // window. See bug 1225044.
+ unsigned int mPendingConfigures;
+
+#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
+
+ // Updates the bounds of the socket widget we manage for remote plugins.
+ void ResizePluginSocketWidget();
+
+ // e10s specific - for managing the socket widget this window hosts.
+ nsPluginNativeWindowGtk* mPluginNativeWindow;
+
+ // 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;
+
+ // 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;
+
+ 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 CleanLayerManagerRecursive();
+
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ /**
+ * |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;
+};
+
+class nsChildWindow : public nsWindow {
+public:
+ nsChildWindow();
+ ~nsChildWindow();
+};
+
+#endif /* __nsWindow_h__ */
diff --git a/widget/gtkxtbin/gtk2xtbin.c b/widget/gtkxtbin/gtk2xtbin.c
new file mode 100644
index 000000000..189478bad
--- /dev/null
+++ b/widget/gtkxtbin/gtk2xtbin.c
@@ -0,0 +1,860 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set 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/. */
+
+/*
+ * The GtkXtBin widget allows for Xt toolkit code to be used
+ * inside a GTK application.
+ */
+
+#include "xembed.h"
+#include "gtk2xtbin.h"
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <glib.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Xlib/Xt stuff */
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Shell.h>
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+
+/* uncomment this if you want debugging information about widget
+ creation and destruction */
+#undef DEBUG_XTBIN
+
+#define XTBIN_MAX_EVENTS 30
+
+static void gtk_xtbin_class_init (GtkXtBinClass *klass);
+static void gtk_xtbin_init (GtkXtBin *xtbin);
+static void gtk_xtbin_realize (GtkWidget *widget);
+static void gtk_xtbin_unrealize (GtkWidget *widget);
+static void gtk_xtbin_destroy (GtkObject *object);
+
+/* Xt aware XEmbed */
+static void xt_client_handle_xembed_message (Widget w,
+ XtPointer client_data,
+ XEvent *event);
+static void xt_add_focus_listener( Widget w, XtPointer user_data );
+static void xt_add_focus_listener_tree ( Widget treeroot, XtPointer user_data);
+static void xt_remove_focus_listener(Widget w, XtPointer user_data);
+static void xt_client_event_handler (Widget w, XtPointer client_data, XEvent *event);
+static void xt_client_focus_listener (Widget w, XtPointer user_data, XEvent *event);
+static void xt_client_set_info (Widget xtplug, unsigned long flags);
+static void send_xembed_message (XtClient *xtclient,
+ long message,
+ long detail,
+ long data1,
+ long data2,
+ long time);
+static int error_handler (Display *display,
+ XErrorEvent *error);
+/* For error trap of XEmbed */
+static void trap_errors(void);
+static int untrap_error(void);
+static int (*old_error_handler) (Display *, XErrorEvent *);
+static int trapped_error_code = 0;
+
+static GtkWidgetClass *parent_class = NULL;
+
+static Display *xtdisplay = NULL;
+static String *fallback = NULL;
+static gboolean xt_is_initialized = FALSE;
+static gint num_widgets = 0;
+
+static GPollFD xt_event_poll_fd;
+static gint xt_polling_timer_id = 0;
+static guint tag = 0;
+
+static gboolean
+xt_event_prepare (GSource* source_data,
+ gint *timeout)
+{
+ int mask;
+
+ mask = XPending(xtdisplay);
+
+ return (gboolean)mask;
+}
+
+static gboolean
+xt_event_check (GSource* source_data)
+{
+ if (xt_event_poll_fd.revents & G_IO_IN) {
+ int mask;
+ mask = XPending(xtdisplay);
+ return (gboolean)mask;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+xt_event_dispatch (GSource* source_data,
+ GSourceFunc call_back,
+ gpointer user_data)
+{
+ XtAppContext ac;
+ int i = 0;
+
+ ac = XtDisplayToApplicationContext(xtdisplay);
+
+ /* Process only real X traffic here. We only look for data on the
+ * pipe, limit it to XTBIN_MAX_EVENTS and only call
+ * XtAppProcessEvent so that it will look for X events. There's no
+ * timer processing here since we already have a timer callback that
+ * does it. */
+ for (i=0; i < XTBIN_MAX_EVENTS && XPending(xtdisplay); i++) {
+ XtAppProcessEvent(ac, XtIMXEvent);
+ }
+
+ return TRUE;
+}
+
+static GSourceFuncs xt_event_funcs = {
+ xt_event_prepare,
+ xt_event_check,
+ xt_event_dispatch,
+ NULL,
+ (GSourceFunc)NULL,
+ (GSourceDummyMarshal)NULL
+};
+
+static gboolean
+xt_event_polling_timer_callback(gpointer user_data)
+{
+ Display * display;
+ XtAppContext ac;
+ int eventsToProcess = 20;
+
+ display = (Display *)user_data;
+ ac = XtDisplayToApplicationContext(display);
+
+ /* We need to process many Xt events here. If we just process
+ one event we might starve one or more Xt consumers. On the other hand
+ this could hang the whole app if Xt events come pouring in. So process
+ up to 20 Xt events right now and save the rest for later. This is a hack,
+ but it oughta work. We *really* should have out of process plugins.
+ */
+ while (eventsToProcess-- && XtAppPending(ac))
+ XtAppProcessEvent(ac, XtIMAll);
+ return TRUE;
+}
+
+GType
+gtk_xtbin_get_type (void)
+{
+ static GType xtbin_type = 0;
+
+ if (!xtbin_type) {
+ static const GTypeInfo xtbin_info =
+ {
+ sizeof (GtkXtBinClass), /* class_size */
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) gtk_xtbin_class_init, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (GtkXtBin), /* instance_size */
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gtk_xtbin_init, /* instance_init */
+ NULL /* value_table */
+ };
+ xtbin_type = g_type_register_static(GTK_TYPE_SOCKET, "GtkXtBin",
+ &xtbin_info, 0);
+ }
+ return xtbin_type;
+}
+
+static void
+gtk_xtbin_class_init (GtkXtBinClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GtkObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent(klass);
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ widget_class->realize = gtk_xtbin_realize;
+ widget_class->unrealize = gtk_xtbin_unrealize;
+
+ object_class = GTK_OBJECT_CLASS (klass);
+ object_class->destroy = gtk_xtbin_destroy;
+}
+
+static void
+gtk_xtbin_init (GtkXtBin *xtbin)
+{
+ xtbin->xtdisplay = NULL;
+ xtbin->parent_window = NULL;
+ xtbin->xtwindow = 0;
+}
+
+static void
+gtk_xtbin_realize (GtkWidget *widget)
+{
+ GtkXtBin *xtbin;
+ GtkAllocation allocation = { 0, 0, 200, 200 };
+ gint x, y, w, h, d; /* geometry of window */
+
+#ifdef DEBUG_XTBIN
+ printf("gtk_xtbin_realize()\n");
+#endif
+
+ g_return_if_fail (GTK_IS_XTBIN (widget));
+
+ xtbin = GTK_XTBIN (widget);
+
+ /* caculate the allocation before realize */
+ gdk_window_get_geometry(xtbin->parent_window, &x, &y, &w, &h, &d);
+ allocation.width = w;
+ allocation.height = h;
+ gtk_widget_size_allocate (widget, &allocation);
+
+#ifdef DEBUG_XTBIN
+ printf("initial allocation %d %d %d %d\n", x, y, w, h);
+#endif
+
+ /* use GtkSocket's realize */
+ (*GTK_WIDGET_CLASS(parent_class)->realize)(widget);
+
+ /* create the Xt client widget */
+ xt_client_create(&(xtbin->xtclient),
+ gtk_socket_get_id(GTK_SOCKET(xtbin)),
+ h, w);
+ xtbin->xtwindow = XtWindow(xtbin->xtclient.child_widget);
+
+ gdk_flush();
+
+ /* now that we have created the xt client, add it to the socket. */
+ gtk_socket_add_id(GTK_SOCKET(widget), xtbin->xtwindow);
+}
+
+
+
+GtkWidget*
+gtk_xtbin_new (GdkWindow *parent_window, String * f)
+{
+ GtkXtBin *xtbin;
+ gpointer user_data;
+
+ assert(parent_window != NULL);
+ xtbin = g_object_new (GTK_TYPE_XTBIN, NULL);
+
+ if (!xtbin)
+ return (GtkWidget*)NULL;
+
+ if (f)
+ fallback = f;
+
+ /* Initialize the Xt toolkit */
+ xtbin->parent_window = parent_window;
+
+ xt_client_init(&(xtbin->xtclient),
+ GDK_VISUAL_XVISUAL(gdk_rgb_get_visual()),
+ GDK_COLORMAP_XCOLORMAP(gdk_rgb_get_colormap()),
+ gdk_rgb_get_visual()->depth);
+
+ if (!xtbin->xtclient.xtdisplay) {
+ /* If XtOpenDisplay failed, we can't go any further.
+ * Bail out.
+ */
+#ifdef DEBUG_XTBIN
+ printf("gtk_xtbin_init: XtOpenDisplay() returned NULL.\n");
+#endif
+ g_free (xtbin);
+ return (GtkWidget *)NULL;
+ }
+
+ /* Launch X event loop */
+ xt_client_xloop_create();
+
+ /* Build the hierachy */
+ xtbin->xtdisplay = xtbin->xtclient.xtdisplay;
+ gtk_widget_set_parent_window(GTK_WIDGET(xtbin), parent_window);
+ gdk_window_get_user_data(xtbin->parent_window, &user_data);
+ if (user_data)
+ gtk_container_add(GTK_CONTAINER(user_data), GTK_WIDGET(xtbin));
+
+ /* This GtkSocket has a visible window, but the Xt plug will cover this
+ * window. Normally GtkSockets let the X server paint their background and
+ * this would happen immediately (before the plug is mapped). Setting the
+ * background to None prevents the server from painting this window,
+ * avoiding flicker.
+ */
+ gtk_widget_realize(GTK_WIDGET(xtbin));
+ gdk_window_set_back_pixmap(GTK_WIDGET(xtbin)->window, NULL, FALSE);
+
+ return GTK_WIDGET (xtbin);
+}
+
+static void
+gtk_xtbin_unrealize (GtkWidget *object)
+{
+ GtkXtBin *xtbin;
+ GtkWidget *widget;
+
+#ifdef DEBUG_XTBIN
+ printf("gtk_xtbin_unrealize()\n");
+#endif
+
+ /* gtk_object_destroy() will already hold a refcount on object
+ */
+ xtbin = GTK_XTBIN(object);
+ widget = GTK_WIDGET(object);
+
+ GTK_WIDGET_UNSET_FLAGS (widget, GTK_VISIBLE);
+ if (GTK_WIDGET_REALIZED (widget)) {
+ xt_client_unrealize(&(xtbin->xtclient));
+ }
+
+ (*GTK_WIDGET_CLASS (parent_class)->unrealize)(widget);
+}
+
+static void
+gtk_xtbin_destroy (GtkObject *object)
+{
+ GtkXtBin *xtbin;
+
+#ifdef DEBUG_XTBIN
+ printf("gtk_xtbin_destroy()\n");
+#endif
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_XTBIN (object));
+
+ xtbin = GTK_XTBIN (object);
+
+ if(xtbin->xtwindow) {
+ /* remove the event handler */
+ xt_client_destroy(&(xtbin->xtclient));
+ xtbin->xtwindow = 0;
+
+ /* stop X event loop */
+ xt_client_xloop_destroy();
+ }
+
+ GTK_OBJECT_CLASS(parent_class)->destroy(object);
+}
+
+/*
+* Following is the implementation of Xt XEmbedded for client side
+*/
+
+/* Initial Xt plugin */
+void
+xt_client_init( XtClient * xtclient,
+ Visual *xtvisual,
+ Colormap xtcolormap,
+ int xtdepth)
+{
+ XtAppContext app_context;
+ char *mArgv[1];
+ int mArgc = 0;
+
+ /*
+ * Initialize Xt stuff
+ */
+ xtclient->top_widget = NULL;
+ xtclient->child_widget = NULL;
+ xtclient->xtdisplay = NULL;
+ xtclient->xtvisual = NULL;
+ xtclient->xtcolormap = 0;
+ xtclient->xtdepth = 0;
+
+ if (!xt_is_initialized) {
+#ifdef DEBUG_XTBIN
+ printf("starting up Xt stuff\n");
+#endif
+ XtToolkitInitialize();
+ app_context = XtCreateApplicationContext();
+ if (fallback)
+ XtAppSetFallbackResources(app_context, fallback);
+
+ xtdisplay = XtOpenDisplay(app_context, gdk_get_display(), NULL,
+ "Wrapper", NULL, 0, &mArgc, mArgv);
+ if (xtdisplay)
+ xt_is_initialized = TRUE;
+ }
+ xtclient->xtdisplay = xtdisplay;
+ xtclient->xtvisual = xtvisual;
+ xtclient->xtcolormap = xtcolormap;
+ xtclient->xtdepth = xtdepth;
+}
+
+void
+xt_client_xloop_create(void)
+{
+ /* If this is the first running widget, hook this display into the
+ mainloop */
+ if (0 == num_widgets) {
+ int cnumber;
+ GSource* gs;
+
+ /* Set up xtdisplay in case we're missing one */
+ if (!xtdisplay) {
+ (void)xt_client_get_display();
+ }
+
+ /*
+ * hook Xt event loop into the glib event loop.
+ */
+ /* the assumption is that gtk_init has already been called */
+ gs = g_source_new(&xt_event_funcs, sizeof(GSource));
+ if (!gs) {
+ return;
+ }
+
+ g_source_set_priority(gs, GDK_PRIORITY_EVENTS);
+ g_source_set_can_recurse(gs, TRUE);
+ tag = g_source_attach(gs, (GMainContext*)NULL);
+ g_source_unref(gs);
+#ifdef VMS
+ cnumber = XConnectionNumber(xtdisplay);
+#else
+ cnumber = ConnectionNumber(xtdisplay);
+#endif
+ xt_event_poll_fd.fd = cnumber;
+ xt_event_poll_fd.events = G_IO_IN;
+ xt_event_poll_fd.revents = 0; /* hmm... is this correct? */
+
+ g_main_context_add_poll ((GMainContext*)NULL,
+ &xt_event_poll_fd,
+ G_PRIORITY_LOW);
+ /* add a timer so that we can poll and process Xt timers */
+ xt_polling_timer_id =
+ g_timeout_add(25,
+ (GtkFunction)xt_event_polling_timer_callback,
+ xtdisplay);
+ }
+
+ /* Bump up our usage count */
+ num_widgets++;
+}
+
+void
+xt_client_xloop_destroy(void)
+{
+ num_widgets--; /* reduce our usage count */
+
+ /* If this is the last running widget, remove the Xt display
+ connection from the mainloop */
+ if (0 == num_widgets) {
+#ifdef DEBUG_XTBIN
+ printf("removing the Xt connection from the main loop\n");
+#endif
+ g_main_context_remove_poll((GMainContext*)NULL, &xt_event_poll_fd);
+ g_source_remove(tag);
+
+ g_source_remove(xt_polling_timer_id);
+ xt_polling_timer_id = 0;
+ }
+}
+
+/* Get Xt Client display */
+Display *
+xt_client_get_display(void)
+{
+ if (!xtdisplay) {
+ XtClient tmp;
+ xt_client_init(&tmp,NULL,0,0);
+ }
+ return xtdisplay;
+}
+
+/* Create the Xt client widgets
+* */
+void
+xt_client_create ( XtClient* xtclient ,
+ Window embedderid,
+ int height,
+ int width )
+{
+ int n;
+ Arg args[6];
+ Widget child_widget;
+ Widget top_widget;
+
+#ifdef DEBUG_XTBIN
+ printf("xt_client_create() \n");
+#endif
+ top_widget = XtAppCreateShell("drawingArea", "Wrapper",
+ applicationShellWidgetClass,
+ xtclient->xtdisplay,
+ NULL, 0);
+ xtclient->top_widget = top_widget;
+
+ /* set size of Xt window */
+ n = 0;
+ XtSetArg(args[n], XtNheight, height);n++;
+ XtSetArg(args[n], XtNwidth, width);n++;
+ XtSetValues(top_widget, args, n);
+
+ child_widget = XtVaCreateWidget("form",
+ compositeWidgetClass,
+ top_widget, NULL);
+
+ n = 0;
+ XtSetArg(args[n], XtNheight, height);n++;
+ XtSetArg(args[n], XtNwidth, width);n++;
+ XtSetArg(args[n], XtNvisual, xtclient->xtvisual ); n++;
+ XtSetArg(args[n], XtNdepth, xtclient->xtdepth ); n++;
+ XtSetArg(args[n], XtNcolormap, xtclient->xtcolormap ); n++;
+ XtSetArg(args[n], XtNborderWidth, 0); n++;
+ XtSetValues(child_widget, args, n);
+
+ XSync(xtclient->xtdisplay, FALSE);
+ xtclient->oldwindow = top_widget->core.window;
+ top_widget->core.window = embedderid;
+
+ /* this little trick seems to finish initializing the widget */
+#if XlibSpecificationRelease >= 6
+ XtRegisterDrawable(xtclient->xtdisplay,
+ embedderid,
+ top_widget);
+#else
+ _XtRegisterWindow( embedderid,
+ top_widget);
+#endif
+ XtRealizeWidget(child_widget);
+
+ /* listen to all Xt events */
+ XSelectInput(xtclient->xtdisplay,
+ embedderid,
+ XtBuildEventMask(top_widget));
+ xt_client_set_info (child_widget, 0);
+
+ XtManageChild(child_widget);
+ xtclient->child_widget = child_widget;
+
+ /* set the event handler */
+ XtAddEventHandler(child_widget,
+ StructureNotifyMask | KeyPressMask,
+ TRUE,
+ (XtEventHandler)xt_client_event_handler, xtclient);
+ XtAddEventHandler(child_widget,
+ SubstructureNotifyMask | ButtonReleaseMask,
+ FALSE,
+ (XtEventHandler)xt_client_focus_listener,
+ xtclient);
+ XSync(xtclient->xtdisplay, FALSE);
+}
+
+void
+xt_client_unrealize ( XtClient* xtclient )
+{
+ /* Explicitly destroy the child_widget window because this is actually a
+ child of the socket window. It is not a child of top_widget's window
+ when that is destroyed. */
+ XtUnrealizeWidget(xtclient->child_widget);
+
+#if XlibSpecificationRelease >= 6
+ XtUnregisterDrawable(xtclient->xtdisplay,
+ xtclient->top_widget->core.window);
+#else
+ _XtUnregisterWindow(xtclient->top_widget->core.window,
+ xtclient->top_widget);
+#endif
+
+ /* flush the queue before we returning origin top_widget->core.window
+ or we can get X error since the window is gone */
+ XSync(xtclient->xtdisplay, False);
+
+ xtclient->top_widget->core.window = xtclient->oldwindow;
+ XtUnrealizeWidget(xtclient->top_widget);
+}
+
+void
+xt_client_destroy (XtClient* xtclient)
+{
+ if(xtclient->top_widget) {
+ XtRemoveEventHandler(xtclient->child_widget,
+ StructureNotifyMask | KeyPressMask,
+ TRUE,
+ (XtEventHandler)xt_client_event_handler, xtclient);
+ XtDestroyWidget(xtclient->top_widget);
+ xtclient->top_widget = NULL;
+ }
+}
+
+void
+xt_client_set_info (Widget xtplug, unsigned long flags)
+{
+ unsigned long buffer[2];
+
+ Atom infoAtom = XInternAtom(XtDisplay(xtplug), "_XEMBED_INFO", False);
+
+ buffer[1] = 0; /* Protocol version */
+ buffer[1] = flags;
+
+ XChangeProperty (XtDisplay(xtplug), XtWindow(xtplug),
+ infoAtom, infoAtom, 32,
+ PropModeReplace,
+ (unsigned char *)buffer, 2);
+}
+
+static void
+xt_client_handle_xembed_message(Widget w, XtPointer client_data, XEvent *event)
+{
+ XtClient *xtplug = (XtClient*)client_data;
+ switch (event->xclient.data.l[1])
+ {
+ case XEMBED_EMBEDDED_NOTIFY:
+ break;
+ case XEMBED_WINDOW_ACTIVATE:
+#ifdef DEBUG_XTBIN
+ printf("Xt client get XEMBED_WINDOW_ACTIVATE\n");
+#endif
+ break;
+ case XEMBED_WINDOW_DEACTIVATE:
+#ifdef DEBUG_XTBIN
+ printf("Xt client get XEMBED_WINDOW_DEACTIVATE\n");
+#endif
+ break;
+ case XEMBED_MODALITY_ON:
+#ifdef DEBUG_XTBIN
+ printf("Xt client get XEMBED_MODALITY_ON\n");
+#endif
+ break;
+ case XEMBED_MODALITY_OFF:
+#ifdef DEBUG_XTBIN
+ printf("Xt client get XEMBED_MODALITY_OFF\n");
+#endif
+ break;
+ case XEMBED_FOCUS_IN:
+ case XEMBED_FOCUS_OUT:
+ {
+ XEvent xevent;
+ memset(&xevent, 0, sizeof(xevent));
+
+ if(event->xclient.data.l[1] == XEMBED_FOCUS_IN) {
+#ifdef DEBUG_XTBIN
+ printf("XTEMBED got focus in\n");
+#endif
+ xevent.xfocus.type = FocusIn;
+ }
+ else {
+#ifdef DEBUG_XTBIN
+ printf("XTEMBED got focus out\n");
+#endif
+ xevent.xfocus.type = FocusOut;
+ }
+
+ xevent.xfocus.window = XtWindow(xtplug->child_widget);
+ xevent.xfocus.display = XtDisplay(xtplug->child_widget);
+ XSendEvent(XtDisplay(xtplug->child_widget),
+ xevent.xfocus.window,
+ False, NoEventMask,
+ &xevent );
+ XSync( XtDisplay(xtplug->child_widget), False);
+ }
+ break;
+ default:
+ break;
+ } /* End of XEmbed Message */
+}
+
+void
+xt_client_event_handler( Widget w, XtPointer client_data, XEvent *event)
+{
+ XtClient *xtplug = (XtClient*)client_data;
+
+ switch(event->type)
+ {
+ case ClientMessage:
+ /* Handle xembed message */
+ if (event->xclient.message_type==
+ XInternAtom (XtDisplay(xtplug->child_widget),
+ "_XEMBED", False)) {
+ xt_client_handle_xembed_message(w, client_data, event);
+ }
+ break;
+ case ReparentNotify:
+ break;
+ case MappingNotify:
+ xt_client_set_info (w, XEMBED_MAPPED);
+ break;
+ case UnmapNotify:
+ xt_client_set_info (w, 0);
+ break;
+ case KeyPress:
+#ifdef DEBUG_XTBIN
+ printf("Key Press Got!\n");
+#endif
+ break;
+ default:
+ break;
+ } /* End of switch(event->type) */
+}
+
+static void
+send_xembed_message (XtClient *xtclient,
+ long message,
+ long detail,
+ long data1,
+ long data2,
+ long time)
+{
+ XEvent xevent;
+ Window w=XtWindow(xtclient->top_widget);
+ Display* dpy=xtclient->xtdisplay;
+ int errorcode;
+
+ memset(&xevent,0,sizeof(xevent));
+ xevent.xclient.window = w;
+ xevent.xclient.type = ClientMessage;
+ xevent.xclient.message_type = XInternAtom(dpy,"_XEMBED",False);
+ xevent.xclient.format = 32;
+ xevent.xclient.data.l[0] = time;
+ xevent.xclient.data.l[1] = message;
+ xevent.xclient.data.l[2] = detail;
+ xevent.xclient.data.l[3] = data1;
+ xevent.xclient.data.l[4] = data2;
+
+ trap_errors ();
+ XSendEvent (dpy, w, False, NoEventMask, &xevent);
+ XSync (dpy,False);
+
+ if((errorcode = untrap_error())) {
+#ifdef DEBUG_XTBIN
+ printf("send_xembed_message error(%d)!!!\n",errorcode);
+#endif
+ }
+}
+
+static int
+error_handler(Display *display, XErrorEvent *error)
+{
+ trapped_error_code = error->error_code;
+ return 0;
+}
+
+static void
+trap_errors(void)
+{
+ trapped_error_code =0;
+ old_error_handler = XSetErrorHandler(error_handler);
+}
+
+static int
+untrap_error(void)
+{
+ XSetErrorHandler(old_error_handler);
+ if(trapped_error_code) {
+#ifdef DEBUG_XTBIN
+ printf("Get X Window Error = %d\n", trapped_error_code);
+#endif
+ }
+ return trapped_error_code;
+}
+
+void
+xt_client_focus_listener( Widget w, XtPointer user_data, XEvent *event)
+{
+ Display *dpy = XtDisplay(w);
+ XtClient *xtclient = user_data;
+ Window win = XtWindow(w);
+
+ switch(event->type)
+ {
+ case CreateNotify:
+ if(event->xcreatewindow.parent == win) {
+ Widget child=XtWindowToWidget( dpy, event->xcreatewindow.window);
+ if (child)
+ xt_add_focus_listener_tree(child, user_data);
+ }
+ break;
+ case DestroyNotify:
+ xt_remove_focus_listener( w, user_data);
+ break;
+ case ReparentNotify:
+ if(event->xreparent.parent == win) {
+ /* I am the new parent */
+ Widget child=XtWindowToWidget(dpy, event->xreparent.window);
+ if (child)
+ xt_add_focus_listener_tree( child, user_data);
+ }
+ else if(event->xreparent.window == win) {
+ /* I am the new child */
+ }
+ else {
+ /* I am the old parent */
+ }
+ break;
+ case ButtonRelease:
+#if 0
+ XSetInputFocus(dpy, XtWindow(xtclient->child_widget), RevertToParent, event->xbutton.time);
+#endif
+ send_xembed_message ( xtclient,
+ XEMBED_REQUEST_FOCUS, 0, 0, 0, 0);
+ break;
+ default:
+ break;
+ } /* End of switch(event->type) */
+}
+
+static void
+xt_add_focus_listener( Widget w, XtPointer user_data)
+{
+ XtClient *xtclient = user_data;
+
+ trap_errors ();
+ XtAddEventHandler(w,
+ SubstructureNotifyMask | ButtonReleaseMask,
+ FALSE,
+ (XtEventHandler)xt_client_focus_listener,
+ xtclient);
+ untrap_error();
+}
+
+static void
+xt_remove_focus_listener(Widget w, XtPointer user_data)
+{
+ trap_errors ();
+ XtRemoveEventHandler(w, SubstructureNotifyMask | ButtonReleaseMask, FALSE,
+ (XtEventHandler)xt_client_focus_listener, user_data);
+
+ untrap_error();
+}
+
+static void
+xt_add_focus_listener_tree ( Widget treeroot, XtPointer user_data)
+{
+ Window win = XtWindow(treeroot);
+ Window *children;
+ Window root, parent;
+ Display *dpy = XtDisplay(treeroot);
+ unsigned int i, nchildren;
+
+ /* ensure we don't add more than once */
+ xt_remove_focus_listener( treeroot, user_data);
+ xt_add_focus_listener( treeroot, user_data);
+ trap_errors();
+ if(!XQueryTree(dpy, win, &root, &parent, &children, &nchildren)) {
+ untrap_error();
+ return;
+ }
+
+ if(untrap_error())
+ return;
+
+ for(i=0; i<nchildren; ++i) {
+ Widget child = XtWindowToWidget(dpy, children[i]);
+ if (child)
+ xt_add_focus_listener_tree( child, user_data);
+ }
+ XFree((void*)children);
+
+ return;
+}
+
diff --git a/widget/gtkxtbin/gtk2xtbin.h b/widget/gtkxtbin/gtk2xtbin.h
new file mode 100644
index 000000000..60b3a0b34
--- /dev/null
+++ b/widget/gtkxtbin/gtk2xtbin.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set 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 __GTK_XTBIN_H__
+#define __GTK_XTBIN_H__
+
+#include <gtk/gtk.h>
+#include <X11/Intrinsic.h>
+#include <X11/Xutil.h>
+#include <X11/Xlib.h>
+#ifdef MOZILLA_CLIENT
+#include "mozilla/Types.h"
+#ifdef _IMPL_GTKXTBIN_API
+#define GTKXTBIN_API(type) MOZ_EXPORT type
+#else
+#define GTKXTBIN_API(type) MOZ_IMPORT_API type
+#endif
+#else
+#define GTKXTBIN_API(type) type
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct _XtClient XtClient;
+
+struct _XtClient {
+ Display *xtdisplay;
+ Widget top_widget; /* The toplevel widget */
+ Widget child_widget; /* The embedded widget */
+ Visual *xtvisual;
+ int xtdepth;
+ Colormap xtcolormap;
+ Window oldwindow;
+};
+
+#if (GTK_MAJOR_VERSION == 2)
+typedef struct _GtkXtBin GtkXtBin;
+typedef struct _GtkXtBinClass GtkXtBinClass;
+
+#define GTK_TYPE_XTBIN (gtk_xtbin_get_type ())
+#define GTK_XTBIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GTK_TYPE_XTBIN, GtkXtBin))
+#define GTK_XTBIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GTK_TYPE_XTBIN, GtkXtBinClass))
+#define GTK_IS_XTBIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GTK_TYPE_XTBIN))
+#define GTK_IS_XTBIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GTK_TYPE_XTBIN))
+
+struct _GtkXtBin
+{
+ GtkSocket gsocket;
+ GdkWindow *parent_window;
+ Display *xtdisplay; /* Xt Toolkit Display */
+
+ Window xtwindow; /* Xt Toolkit XWindow */
+ XtClient xtclient; /* Xt Client for XEmbed */
+};
+
+struct _GtkXtBinClass
+{
+ GtkSocketClass parent_class;
+};
+
+GTKXTBIN_API(GType) gtk_xtbin_get_type (void);
+GTKXTBIN_API(GtkWidget *) gtk_xtbin_new (GdkWindow *parent_window, String *f);
+#endif
+
+typedef struct _XtTMRec {
+ XtTranslations translations; /* private to Translation Manager */
+ XtBoundActions proc_table; /* procedure bindings for actions */
+ struct _XtStateRec *current_state; /* Translation Manager state ptr */
+ unsigned long lastEventTime;
+} XtTMRec, *XtTM;
+
+typedef struct _CorePart {
+ Widget self; /* pointer to widget itself */
+ WidgetClass widget_class; /* pointer to Widget's ClassRec */
+ Widget parent; /* parent widget */
+ XrmName xrm_name; /* widget resource name quarkified */
+ Boolean being_destroyed; /* marked for destroy */
+ XtCallbackList destroy_callbacks; /* who to call when widget destroyed */
+ XtPointer constraints; /* constraint record */
+ Position x, y; /* window position */
+ Dimension width, height; /* window dimensions */
+ Dimension border_width; /* window border width */
+ Boolean managed; /* is widget geometry managed? */
+ Boolean sensitive; /* is widget sensitive to user events*/
+ Boolean ancestor_sensitive; /* are all ancestors sensitive? */
+ XtEventTable event_table; /* private to event dispatcher */
+ XtTMRec tm; /* translation management */
+ XtTranslations accelerators; /* accelerator translations */
+ Pixel border_pixel; /* window border pixel */
+ Pixmap border_pixmap; /* window border pixmap or NULL */
+ WidgetList popup_list; /* list of popups */
+ Cardinal num_popups; /* how many popups */
+ String name; /* widget resource name */
+ Screen *screen; /* window's screen */
+ Colormap colormap; /* colormap */
+ Window window; /* window ID */
+ Cardinal depth; /* number of planes in window */
+ Pixel background_pixel; /* window background pixel */
+ Pixmap background_pixmap; /* window background pixmap or NULL */
+ Boolean visible; /* is window mapped and not occluded?*/
+ Boolean mapped_when_managed;/* map window if it's managed? */
+} CorePart;
+
+typedef struct _WidgetRec {
+ CorePart core;
+ } WidgetRec, CoreRec;
+
+/* Exported functions, used by Xt plugins */
+void xt_client_create(XtClient * xtclient, Window embeder, int height, int width);
+void xt_client_unrealize(XtClient* xtclient);
+void xt_client_destroy(XtClient* xtclient);
+void xt_client_init(XtClient * xtclient, Visual *xtvisual, Colormap xtcolormap, int xtdepth);
+void xt_client_xloop_create(void);
+void xt_client_xloop_destroy(void);
+Display * xt_client_get_display(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* __GTK_XTBIN_H__ */
+
diff --git a/widget/gtkxtbin/moz.build b/widget/gtkxtbin/moz.build
new file mode 100644
index 000000000..070aa5777
--- /dev/null
+++ b/widget/gtkxtbin/moz.build
@@ -0,0 +1,19 @@
+# -*- 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/.
+
+EXPORTS += [
+ 'gtk2xtbin.h',
+]
+
+SOURCES += [
+ 'gtk2xtbin.c',
+]
+
+FINAL_LIBRARY = 'xul'
+
+DEFINES['_IMPL_GTKXTBIN_API'] = True
+
+CFLAGS += CONFIG['MOZ_GTK2_CFLAGS']
diff --git a/widget/gtkxtbin/xembed.h b/widget/gtkxtbin/xembed.h
new file mode 100644
index 000000000..605c128aa
--- /dev/null
+++ b/widget/gtkxtbin/xembed.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set 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/. */
+
+/* XEMBED messages */
+#define XEMBED_EMBEDDED_NOTIFY 0
+#define XEMBED_WINDOW_ACTIVATE 1
+#define XEMBED_WINDOW_DEACTIVATE 2
+#define XEMBED_REQUEST_FOCUS 3
+#define XEMBED_FOCUS_IN 4
+#define XEMBED_FOCUS_OUT 5
+#define XEMBED_FOCUS_NEXT 6
+#define XEMBED_FOCUS_PREV 7
+#define XEMBED_GRAB_KEY 8
+#define XEMBED_UNGRAB_KEY 9
+#define XEMBED_MODALITY_ON 10
+#define XEMBED_MODALITY_OFF 11
+
+/* Non standard messages*/
+#define XEMBED_GTK_GRAB_KEY 108
+#define XEMBED_GTK_UNGRAB_KEY 109
+
+/* Details for XEMBED_FOCUS_IN: */
+#define XEMBED_FOCUS_CURRENT 0
+#define XEMBED_FOCUS_FIRST 1
+#define XEMBED_FOCUS_LAST 2
+
+/* Flags for _XEMBED_INFO */
+#define XEMBED_MAPPED (1 << 0)
diff --git a/widget/moz.build b/widget/moz.build
new file mode 100644
index 000000000..09192179f
--- /dev/null
+++ b/widget/moz.build
@@ -0,0 +1,286 @@
+# -*- 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/.
+
+toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+if toolkit in ('cocoa', 'android', 'gonk', 'uikit'):
+ DIRS += [toolkit]
+if toolkit in ('android', 'gonk', 'gtk2', 'gtk3'):
+ EXPORTS += ['nsIPrintDialogService.h']
+
+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',
+ 'nsIMacWebAppUtils.idl',
+ 'nsIStandaloneNativeMenu.idl',
+ 'nsISystemStatusBar.idl',
+ 'nsITaskbarProgress.idl',
+ ]
+ EXPORTS += [
+ 'nsINativeMenuService.h',
+ 'nsIPrintDialogService.h',
+ ]
+
+TEST_DIRS += ['tests']
+
+# 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 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ DIRS += ['gtk']
+
+ if CONFIG['MOZ_X11']:
+ DIRS += ['gtkxtbin']
+
+XPIDL_SOURCES += [
+ 'nsIAppShell.idl',
+ 'nsIBaseWindow.idl',
+ 'nsIBidiKeyboard.idl',
+ 'nsIClipboard.idl',
+ 'nsIClipboardDragDropHookList.idl',
+ 'nsIClipboardDragDropHooks.idl',
+ 'nsIClipboardHelper.idl',
+ 'nsIClipboardOwner.idl',
+ 'nsIColorPicker.idl',
+ 'nsIDatePicker.idl',
+ 'nsIDisplayInfo.idl',
+ 'nsIDragService.idl',
+ 'nsIDragSession.idl',
+ 'nsIFilePicker.idl',
+ 'nsIFormatConverter.idl',
+ 'nsIGfxInfo.idl',
+ 'nsIGfxInfoDebug.idl',
+ 'nsIIdleService.idl',
+ 'nsIIdleServiceInternal.idl',
+ 'nsIPrinterEnumerator.idl',
+ 'nsIPrintSession.idl',
+ 'nsIPrintSettings.idl',
+ 'nsIPrintSettingsService.idl',
+ 'nsIScreen.idl',
+ 'nsIScreenManager.idl',
+ 'nsISound.idl',
+ 'nsITransferable.idl',
+]
+
+XPIDL_MODULE = 'widget'
+
+EXPORTS += [
+ 'GfxDriverInfo.h',
+ 'GfxInfoBase.h',
+ 'GfxInfoCollector.h',
+ 'InputData.h',
+ 'nsBaseScreen.h',
+ 'nsBaseWidget.h',
+ 'nsIDeviceContextSpec.h',
+ 'nsIKeyEventInPluginCallback.h',
+ 'nsIPluginWidget.h',
+ 'nsIRollupListener.h',
+ 'nsIWidget.h',
+ 'nsIWidgetListener.h',
+ 'nsWidgetInitData.h',
+ 'nsWidgetsCID.h',
+ 'PluginWidgetProxy.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',
+ 'IMEData.h',
+ 'InProcessCompositorWidget.h',
+ 'PuppetBidiKeyboard.h',
+ 'WidgetMessageUtils.h',
+ 'WindowSurface.h'
+]
+
+UNIFIED_SOURCES += [
+ 'CompositorWidget.cpp',
+ 'ContentCache.cpp',
+ 'GfxDriverInfo.cpp',
+ 'GfxInfoBase.cpp',
+ 'GfxInfoCollector.cpp',
+ 'GfxInfoWebGL.cpp',
+ 'InProcessCompositorWidget.cpp',
+ 'InputData.cpp',
+ 'nsBaseAppShell.cpp',
+ 'nsBaseScreen.cpp',
+ 'nsClipboardHelper.cpp',
+ 'nsClipboardProxy.cpp',
+ 'nsColorPickerProxy.cpp',
+ 'nsContentProcessWidgetFactory.cpp',
+ 'nsDatePickerProxy.cpp',
+ 'nsDragServiceProxy.cpp',
+ 'nsFilePickerProxy.cpp',
+ 'nsHTMLFormatConverter.cpp',
+ 'nsIdleService.cpp',
+ 'nsIWidgetListener.cpp',
+ 'nsPrimitiveHelpers.cpp',
+ 'nsPrintSettingsImpl.cpp',
+ 'nsScreenManagerProxy.cpp',
+ 'nsTransferable.cpp',
+ 'nsXPLookAndFeel.cpp',
+ 'PuppetBidiKeyboard.cpp',
+ 'PuppetWidget.cpp',
+ 'ScreenProxy.cpp',
+ 'SharedWidgetUtils.cpp',
+ 'TextEventDispatcher.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',
+ 'nsPrintOptionsImpl.h',
+ ]
+ UNIFIED_SOURCES += [
+ 'nsDeviceContextSpecProxy.cpp',
+ 'nsPrintOptionsImpl.cpp',
+ 'nsPrintSession.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
+# PluginWidgetProxy includes MacOS system headers which define a Point struct
+# that conflicts with mozilla::gfx::Point
+SOURCES += [
+ 'nsBaseDragService.cpp',
+ 'nsBaseWidget.cpp',
+ 'PluginWidgetProxy.cpp',
+]
+
+if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']:
+ EXPORTS.mozilla += [
+ 'WidgetTraceEvent.h',
+ ]
+
+EXPORTS.ipc = ['nsGUIEventIPC.h']
+
+if CONFIG['MOZ_X11']:
+ DIRS += ['x11']
+ UNIFIED_SOURCES += [
+ 'GfxInfoX11.cpp'
+ ]
+ SOURCES += [
+ 'nsShmImage.cpp',
+ 'WindowSurfaceX11SHM.cpp',
+ ]
+
+if toolkit in ('cocoa', 'windows'):
+ UNIFIED_SOURCES += [
+ 'nsBaseClipboard.cpp',
+ ]
+
+if toolkit in {'gtk2', 'gtk3', 'cocoa', 'windows',
+ 'android', 'gonk', 'uikit'}:
+ UNIFIED_SOURCES += [
+ 'nsBaseFilePicker.cpp',
+ ]
+
+if toolkit in ('gtk2', 'gtk3', 'windows', 'cocoa'):
+ UNIFIED_SOURCES += [
+ 'nsNativeTheme.cpp',
+ ]
+if toolkit == 'gtk3':
+ XPIDL_SOURCES += [
+ 'nsIApplicationChooser.idl',
+ ]
+
+if not CONFIG['MOZ_B2G']:
+ DEFINES['MOZ_CROSS_PROCESS_IME'] = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/ipc',
+ '/layout/base',
+ '/layout/forms',
+ '/layout/generic',
+ '/layout/xul',
+ '/view',
+ '/widget',
+]
+
+if toolkit == 'windows':
+ IPDL_SOURCES = [
+ 'windows/PCompositorWidget.ipdl',
+ 'windows/PlatformWidgetTypes.ipdlh',
+ ]
+elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT'] and CONFIG['MOZ_X11']:
+ IPDL_SOURCES = [
+ 'gtk/PCompositorWidget.ipdl',
+ 'gtk/PlatformWidgetTypes.ipdlh',
+ ]
+else:
+ IPDL_SOURCES = [
+ 'PCompositorWidget.ipdl',
+ 'PlatformWidgetTypes.ipdlh',
+ ]
+
+widget_dir = toolkit
+if widget_dir in ('gtk3', 'gtk2'):
+ # gtk3 shares includes with gtk2
+ widget_dir = 'gtk'
+
+LOCAL_INCLUDES += [
+ '/widget/%s' % widget_dir,
+]
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['MOZ_ENABLE_D3D10_LAYER']:
+ DEFINES['MOZ_ENABLE_D3D10_LAYER'] = True
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
diff --git a/widget/nsAppShellSingleton.h b/widget/nsAppShellSingleton.h
new file mode 100644
index 000000000..0b736c8dc
--- /dev/null
+++ b/widget/nsAppShellSingleton.h
@@ -0,0 +1,69 @@
+/* -*- 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;
+ rv = static_cast<nsAppShell*>(sAppShell)->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(sAppShell);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+static void
+nsAppShellShutdown()
+{
+ NS_RELEASE(sAppShell);
+}
+
+static 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/nsBaseAppShell.cpp b/widget/nsBaseAppShell.cpp
new file mode 100644
index 000000000..1557498b7
--- /dev/null
+++ b/widget/nsBaseAppShell.cpp
@@ -0,0 +1,349 @@
+/* -*- 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"
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#endif
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.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)
+{
+}
+
+nsBaseAppShell::~nsBaseAppShell()
+{
+}
+
+nsresult
+nsBaseAppShell::Init()
+{
+ // Configure ourselves as an observer for the current thread:
+
+ 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(nullptr);
+}
+
+
+// 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(nsIThreadInternal *thr)
+{
+ 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(thr); // 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();
+
+ return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL));
+}
+
+void
+nsBaseAppShell::IncrementEventloopNestingLevel()
+{
+ ++mEventloopNestingLevel;
+#if defined(MOZ_CRASHREPORTER)
+ CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
+#endif
+}
+
+void
+nsBaseAppShell::DecrementEventloopNestingLevel()
+{
+ --mEventloopNestingLevel;
+#if defined(MOZ_CRASHREPORTER)
+ CrashReporter::SetEventloopNestingLevel(mEventloopNestingLevel);
+#endif
+}
+
+// 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 000000000..4ee9e3d2e
--- /dev/null
+++ b/widget/nsBaseAppShell.h
@@ -0,0 +1,135 @@
+/* -*- 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 000000000..850216ba4
--- /dev/null
+++ b/widget/nsBaseClipboard.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+#include "nsISupportsPrimitives.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 char** aFlavorList,
+ uint32_t aLength,
+ 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 000000000..f2e916f76
--- /dev/null
+++ b/widget/nsBaseClipboard.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+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 000000000..10d3163b3
--- /dev/null
+++ b/widget/nsBaseDragService.cpp
@@ -0,0 +1,828 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIServiceManager.h"
+#include "nsITransferable.h"
+#include "nsSize.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIFrame.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIPresShell.h"
+#include "nsViewManager.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMDragEvent.h"
+#include "nsISelection.h"
+#include "nsISelectionPrivate.h"
+#include "nsPresContext.h"
+#include "nsIDOMDataTransfer.h"
+#include "nsIImageLoadingContent.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "ImageRegion.h"
+#include "nsRegion.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
+#include "SVGImageContext.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/DataTransferItemList.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/Unused.h"
+#include "nsFrameLoader.h"
+#include "TabParent.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),
+ mHasImage(false), mUserCancelled(false),
+ mDragEventDispatchedToChildProcess(false),
+ mDragAction(DRAGDROP_ACTION_NONE),
+ mDragActionFromChildProcess(DRAGDROP_ACTION_UNINITIALIZED), mTargetSize(0,0),
+ mContentPolicyType(nsIContentPolicy::TYPE_OTHER),
+ mSuppressLevel(0), mInputSource(nsIDOMMouseEvent::MOZ_SOURCE_MOUSE)
+{
+}
+
+nsBaseDragService::~nsBaseDragService()
+{
+}
+
+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::SetTargetSize(nsSize aDragTargetSize)
+{
+ mTargetSize = aDragTargetSize;
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::GetTargetSize(nsSize * aDragTargetSize)
+{
+ *aDragTargetSize = mTargetSize;
+ 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(nsIDOMDocument** 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(nsIDOMNode** aSourceNode)
+{
+ *aSourceNode = mSourceNode.get();
+ NS_IF_ADDREF(*aSourceNode);
+
+ 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::GetDataTransfer(nsIDOMDataTransfer** aDataTransfer)
+{
+ *aDataTransfer = mDataTransfer;
+ NS_IF_ADDREF(*aDataTransfer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::SetDataTransfer(nsIDOMDataTransfer* aDataTransfer)
+{
+ mDataTransfer = aDataTransfer;
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsBaseDragService::InvokeDragSession(nsIDOMNode *aDOMNode,
+ nsIArray* aTransferableArray,
+ nsIScriptableRegion* aDragRgn,
+ uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType =
+ nsIContentPolicy::TYPE_OTHER)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+
+ NS_ENSURE_TRUE(aDOMNode, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
+
+ // stash the document of the dom node
+ aDOMNode->GetOwnerDocument(getter_AddRefs(mSourceDocument));
+ 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.
+ nsIPresShell::ClearMouseCapture(nullptr);
+
+ nsresult rv = InvokeDragSessionImpl(aTransferableArray,
+ aDragRgn, aActionType);
+
+ if (NS_FAILED(rv)) {
+ mSourceNode = nullptr;
+ mSourceDocument = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode* aDOMNode,
+ nsIArray* aTransferableArray,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType,
+ nsIDOMNode* aImage,
+ int32_t aImageX, int32_t aImageY,
+ nsIDOMDragEvent* aDragEvent,
+ nsIDOMDataTransfer* 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);
+
+ mDataTransfer = aDataTransfer;
+ mSelection = nullptr;
+ mHasImage = true;
+ mDragPopup = nullptr;
+ mImage = aImage;
+ mImageOffset = CSSIntPoint(aImageX, aImageY);
+
+ aDragEvent->GetScreenX(&mScreenPosition.x);
+ aDragEvent->GetScreenY(&mScreenPosition.y);
+ aDragEvent->GetMozInputSource(&mInputSource);
+
+ nsresult rv = InvokeDragSession(aDOMNode, aTransferableArray,
+ aRegion, aActionType,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+ if (NS_FAILED(rv)) {
+ mImage = nullptr;
+ mHasImage = false;
+ mDataTransfer = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::InvokeDragSessionWithSelection(nsISelection* aSelection,
+ nsIArray* aTransferableArray,
+ uint32_t aActionType,
+ nsIDOMDragEvent* aDragEvent,
+ nsIDOMDataTransfer* 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);
+
+ mDataTransfer = aDataTransfer;
+ mSelection = aSelection;
+ mHasImage = true;
+ mDragPopup = nullptr;
+ mImage = nullptr;
+ mImageOffset = CSSIntPoint();
+
+ aDragEvent->GetScreenX(&mScreenPosition.x);
+ aDragEvent->GetScreenY(&mScreenPosition.y);
+ aDragEvent->GetMozInputSource(&mInputSource);
+
+ // just get the focused node from the selection
+ // XXXndeakin this should actually be the deepest node that contains both
+ // endpoints of the selection
+ nsCOMPtr<nsIDOMNode> node;
+ aSelection->GetFocusNode(getter_AddRefs(node));
+
+ nsresult rv = InvokeDragSession(node, aTransferableArray,
+ nullptr, aActionType,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv)) {
+ mHasImage = false;
+ mSelection = nullptr;
+ mDataTransfer = nullptr;
+ }
+
+ return rv;
+}
+
+//-------------------------------------------------------------------------
+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;
+}
+
+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)
+{
+ if (!mDoingDrag) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aDoneDrag && !mSuppressLevel) {
+ FireDragEventAtSource(eDragEnd);
+ }
+
+ 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);
+ }
+ 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;
+ mCanDrop = false;
+
+ // release the source we've been holding on to.
+ mSourceDocument = nullptr;
+ mSourceNode = nullptr;
+ mSelection = nullptr;
+ mDataTransfer = nullptr;
+ mHasImage = false;
+ mUserCancelled = false;
+ mDragPopup = nullptr;
+ mImage = nullptr;
+ mImageOffset = CSSIntPoint();
+ mScreenPosition = CSSIntPoint();
+ mEndDragPoint = LayoutDeviceIntPoint(0, 0);
+ mInputSource = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
+
+ return NS_OK;
+}
+
+void
+nsBaseDragService::DiscardInternalTransferData()
+{
+ if (mDataTransfer && mSourceNode) {
+ MOZ_ASSERT(!!DataTransfer::Cast(mDataTransfer));
+
+ DataTransferItemList* items = DataTransfer::Cast(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)
+{
+ if (mSourceNode && !mSuppressLevel) {
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(mSourceDocument);
+ if (doc) {
+ nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
+ if (presShell) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetDragEvent event(true, aEventMessage, nullptr);
+ event.inputSource = mInputSource;
+ if (aEventMessage == eDragEnd) {
+ event.mRefPoint = mEndDragPoint;
+ event.mUserCancelled = mUserCancelled;
+ }
+
+ // 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->GetType() == nsGkAtoms::menuPopupFrame) {
+ CSSIntPoint cssPos = RoundedToInt(LayoutDeviceIntPoint(aX, aY) /
+ frame->PresContext()->CSSToDevPixelScale()) - mImageOffset;
+ (static_cast<nsMenuPopupFrame *>(frame))->MoveTo(cssPos, true);
+ }
+ }
+
+ return NS_OK;
+}
+
+static nsIPresShell*
+GetPresShellForContent(nsIDOMNode* aDOMNode)
+{
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMNode);
+ if (!content)
+ return nullptr;
+
+ nsCOMPtr<nsIDocument> document = content->GetUncomposedDoc();
+ if (document) {
+ document->FlushPendingNotifications(Flush_Display);
+
+ return document->GetShell();
+ }
+
+ return nullptr;
+}
+
+nsresult
+nsBaseDragService::DrawDrag(nsIDOMNode* aDOMNode,
+ nsIScriptableRegion* aRegion,
+ CSSIntPoint aScreenPosition,
+ LayoutDeviceIntRect* aScreenDragRect,
+ RefPtr<SourceSurface>* aSurface,
+ nsPresContext** aPresContext)
+{
+ *aSurface = nullptr;
+ *aPresContext = nullptr;
+
+ // use a default size, in case of an error.
+ aScreenDragRect->MoveTo(aScreenPosition.x - mImageOffset.x,
+ aScreenPosition.y - mImageOffset.y);
+ aScreenDragRect->SizeTo(1, 1);
+
+ // if a drag image was specified, use that, otherwise, use the source node
+ nsCOMPtr<nsIDOMNode> 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
+ nsIPresShell* presShell = GetPresShellForContent(dragNode);
+ if (!presShell && mImage)
+ presShell = GetPresShellForContent(aDOMNode);
+ if (!presShell)
+ return NS_ERROR_FAILURE;
+
+ *aPresContext = presShell->GetPresContext();
+
+ nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(dragNode);
+ if (flo) {
+ RefPtr<nsFrameLoader> fl = flo->GetFrameLoader();
+ if (fl) {
+ mozilla::dom::TabParent* tp =
+ static_cast<mozilla::dom::TabParent*>(fl->GetRemoteBrowser());
+ if (tp && tp->TakeDragVisualization(*aSurface, aScreenDragRect)) {
+ if (mImage) {
+ // Just clear the surface if chrome has overridden it with an image.
+ *aSurface = 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->x = screenPoint.x;
+ aScreenDragRect->y = 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) {
+ // if a region was specified, set the screen rectangle to the area that
+ // the region occupies
+ nsIntRect dragRect;
+ if (aRegion) {
+ // the region's coordinates are relative to the root frame
+ aRegion->GetBoundingBox(&dragRect.x, &dragRect.y, &dragRect.width, &dragRect.height);
+
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ nsIntRect screenRect = rootFrame->GetScreenRect();
+ dragRect.MoveBy(screenRect.TopLeft());
+ }
+ 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) {
+ dragRect = frame->GetScreenRect();
+ }
+ }
+
+ dragRect = ToAppUnits(dragRect, nsPresContext::AppUnitsPerCSSPixel()).
+ ToOutsidePixels((*aPresContext)->AppUnitsPerDevPixel());
+ aScreenDragRect->SizeTo(dragRect.width, dragRect.height);
+ return NS_OK;
+ }
+
+ // draw the image for selections
+ if (mSelection) {
+ LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft());
+ *aSurface = presShell->RenderSelection(mSelection, pnt, aScreenDragRect,
+ mImage ? 0 : nsIPresShell::RENDER_AUTO_SCALE);
+ 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::FromContentOrNull(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->GetType() == nsGkAtoms::menuPopupFrame) {
+ mDragPopup = content;
+ }
+ }
+
+ if (!mDragPopup) {
+ // otherwise, just draw the node
+ nsIntRegion clipRegion;
+ uint32_t renderFlags = mImage ? 0 : nsIPresShell::RENDER_AUTO_SCALE;
+ if (aRegion) {
+ aRegion->GetRegion(&clipRegion);
+ }
+
+ if (renderFlags) {
+ nsCOMPtr<nsIDOMNode> child;
+ nsCOMPtr<nsIDOMNodeList> childList;
+ uint32_t length;
+ uint32_t count = 0;
+ nsAutoString childNodeName;
+
+ if (NS_SUCCEEDED(dragNode->GetChildNodes(getter_AddRefs(childList))) &&
+ NS_SUCCEEDED(childList->GetLength(&length))) {
+ // check every childnode for being a img-tag
+ while (count < length) {
+ if (NS_FAILED(childList->Item(count, getter_AddRefs(child))) ||
+ NS_FAILED(child->GetNodeName(childNodeName))) {
+ break;
+ }
+ // here the node is checked for being a img-tag
+ if (childNodeName.LowerCaseEqualsLiteral("img")) {
+ // if the dragnnode contains a image, set RENDER_IS_IMAGE flag
+ renderFlags = renderFlags | nsIPresShell::RENDER_IS_IMAGE;
+ break;
+ }
+ count++;
+ }
+ }
+ }
+ LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft());
+ *aSurface = presShell->RenderNode(dragNode, aRegion ? &clipRegion : nullptr,
+ pnt, aScreenDragRect,
+ renderFlags);
+ }
+
+ // If an image was specified, reset the position from the offset that was supplied.
+ if (mImage) {
+ aScreenDragRect->x = screenPoint.x;
+ aScreenDragRect->y = 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->width = aPresContext->CSSPixelsToDevPixels(imageWidth);
+ aScreenDragRect->height = 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->width = sz.width;
+ aScreenDragRect->height = 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;
+
+ DrawResult res =
+ imgContainer->Draw(ctx, destSize, ImageRegion::Create(destSize),
+ imgIContainer::FRAME_CURRENT,
+ SamplingFilter::GOOD, /* no SVGImageContext */ Nothing(),
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (res == DrawResult::BAD_IMAGE || res == DrawResult::BAD_ARGS) {
+ 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);
+ ++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::DragEventDispatchedToChildProcess()
+{
+ mDragEventDispatchedToChildProcess = true;
+ return NS_OK;
+}
+
+bool
+nsBaseDragService::MaybeAddChildProcess(mozilla::dom::ContentParent* aChild)
+{
+ if (!mChildProcesses.Contains(aChild)) {
+ mChildProcesses.AppendElement(aChild);
+ return true;
+ }
+ return false;
+}
diff --git a/widget/nsBaseDragService.h b/widget/nsBaseDragService.h
new file mode 100644
index 000000000..300ea693a
--- /dev/null
+++ b/widget/nsBaseDragService.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsITransferable.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMDataTransfer.h"
+#include "nsCOMPtr.h"
+#include "nsRect.h"
+#include "nsPoint.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "nsTArray.h"
+#include "Units.h"
+
+// translucency level for drag images
+#define DRAG_TRANSLUCENCY 0.65
+
+class nsIContent;
+class nsIDOMNode;
+class nsPresContext;
+class nsIImageLoadingContent;
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+} // namespace gfx
+} // 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.
+ */
+ virtual nsresult InvokeDragSessionImpl(nsIArray* aTransferableArray,
+ nsIScriptableRegion* aDragRgn,
+ 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(nsIDOMNode* aDOMNode,
+ nsIScriptableRegion* 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;
+ // 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;
+
+ nsSize mTargetSize;
+ nsCOMPtr<nsIDOMNode> mSourceNode;
+ nsCOMPtr<nsIDOMDocument> mSourceDocument; // the document at the drag source. will be null
+ // if it came from outside the app.
+ nsContentPolicyType mContentPolicyType; // the contentpolicy type passed to the channel
+ // when initiating the drag session
+ nsCOMPtr<nsIDOMDataTransfer> mDataTransfer;
+
+ // used to determine the image to appear on the cursor while dragging
+ nsCOMPtr<nsIDOMNode> mImage;
+ // offset of cursor within the image
+ mozilla::CSSIntPoint mImageOffset;
+
+ // set if a selection is being dragged
+ nsCOMPtr<nsISelection> mSelection;
+
+ // 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 nsIDOMMouseEvent.
+ uint16_t mInputSource;
+
+ nsTArray<RefPtr<mozilla::dom::ContentParent>> mChildProcesses;
+};
+
+#endif // nsBaseDragService_h__
diff --git a/widget/nsBaseFilePicker.cpp b/widget/nsBaseFilePicker.cpp
new file mode 100644
index 000000000..d65ffb651
--- /dev/null
+++ b/widget/nsBaseFilePicker.cpp
@@ -0,0 +1,393 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+
+#include "nsIStringBundle.h"
+#include "nsXPIDLString.h"
+#include "nsIServiceManager.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 "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)
+{
+ if (aIsDirectory) {
+#ifdef DEBUG
+ bool isDir;
+ aFile->IsDirectory(&isDir);
+ MOZ_ASSERT(isDir);
+#endif
+
+ RefPtr<Directory> directory = Directory::Create(aWindow, aFile);
+ MOZ_ASSERT(directory);
+
+ directory.forget(aResult);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMBlob> blob = File::CreateFromFile(aWindow, aFile);
+ blob.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 AsyncShowFilePicker : public mozilla::Runnable
+{
+public:
+ AsyncShowFilePicker(nsIFilePicker *aFilePicker,
+ nsIFilePickerShownCallback *aCallback) :
+ 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<nsIFilePicker> mFilePicker;
+ RefPtr<nsIFilePickerShownCallback> mCallback;
+};
+
+class nsBaseFilePickerEnumerator : public nsISimpleEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsBaseFilePickerEnumerator(nsPIDOMWindowOuter* aParent,
+ nsISimpleEnumerator* iterator,
+ int16_t aMode)
+ : mIterator(iterator)
+ , mParent(aParent->GetCurrentInnerWindow())
+ , mMode(aMode)
+ {}
+
+ 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;
+ }
+
+ return LocalFileToDirectoryOrBlob(mParent,
+ mMode == nsIFilePicker::modeGetFolder,
+ localFile,
+ aResult);
+ }
+
+ NS_IMETHOD
+ HasMoreElements(bool* aResult) override
+ {
+ return mIterator->HasMoreElements(aResult);
+ }
+
+protected:
+ virtual ~nsBaseFilePickerEnumerator()
+ {}
+
+private:
+ nsCOMPtr<nsISimpleEnumerator> mIterator;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ int16_t mMode;
+};
+
+NS_IMPL_ISUPPORTS(nsBaseFilePickerEnumerator, nsISimpleEnumerator)
+
+nsBaseFilePicker::nsBaseFilePicker()
+ : mAddToRecentDocs(true)
+ , mMode(nsIFilePicker::modeOpen)
+{
+
+}
+
+nsBaseFilePicker::~nsBaseFilePicker()
+{
+
+}
+
+NS_IMETHODIMP nsBaseFilePicker::Init(mozIDOMWindowProxy* aParent,
+ const nsAString& aTitle,
+ int16_t aMode)
+{
+ NS_PRECONDITION(aParent, "Null parent passed to filepicker, no file "
+ "picker for you!");
+
+ mParent = nsPIDOMWindowOuter::From(aParent);
+
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(mParent->GetOuterWindow());
+ 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;
+
+ nsXPIDLString title;
+ nsXPIDLString filter;
+
+ if (aFilterMask & filterAll) {
+ titleBundle->GetStringFromName(u"allTitle", getter_Copies(title));
+ filterBundle->GetStringFromName(u"allFilter", getter_Copies(filter));
+ AppendFilter(title,filter);
+ }
+ if (aFilterMask & filterHTML) {
+ titleBundle->GetStringFromName(u"htmlTitle", getter_Copies(title));
+ filterBundle->GetStringFromName(u"htmlFilter", getter_Copies(filter));
+ AppendFilter(title,filter);
+ }
+ if (aFilterMask & filterText) {
+ titleBundle->GetStringFromName(u"textTitle", getter_Copies(title));
+ filterBundle->GetStringFromName(u"textFilter", getter_Copies(filter));
+ AppendFilter(title,filter);
+ }
+ if (aFilterMask & filterImages) {
+ titleBundle->GetStringFromName(u"imageTitle", getter_Copies(title));
+ filterBundle->GetStringFromName(u"imageFilter", getter_Copies(filter));
+ AppendFilter(title,filter);
+ }
+ if (aFilterMask & filterAudio) {
+ titleBundle->GetStringFromName(u"audioTitle", getter_Copies(title));
+ filterBundle->GetStringFromName(u"audioFilter", getter_Copies(filter));
+ AppendFilter(title,filter);
+ }
+ if (aFilterMask & filterVideo) {
+ titleBundle->GetStringFromName(u"videoTitle", getter_Copies(title));
+ filterBundle->GetStringFromName(u"videoFilter", getter_Copies(filter));
+ AppendFilter(title,filter);
+ }
+ if (aFilterMask & filterXML) {
+ titleBundle->GetStringFromName(u"xmlTitle", getter_Copies(title));
+ filterBundle->GetStringFromName(u"xmlFilter", getter_Copies(filter));
+ AppendFilter(title,filter);
+ }
+ if (aFilterMask & filterXUL) {
+ titleBundle->GetStringFromName(u"xulTitle", getter_Copies(title));
+ filterBundle->GetStringFromName(u"xulFilter", getter_Copies(filter));
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterApps) {
+ titleBundle->GetStringFromName(u"appsTitle", getter_Copies(title));
+ // Pass the magic string "..apps" to the platform filepicker, which it
+ // should recognize and do the correct platform behavior for.
+ AppendFilter(title, NS_LITERAL_STRING("..apps"));
+ }
+ 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);
+}
+
+// Set the display directory
+NS_IMETHODIMP nsBaseFilePicker::SetDisplayDirectory(nsIFile *aDirectory)
+{
+ if (!aDirectory) {
+ mDisplayDirectory = nullptr;
+ return NS_OK;
+ }
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = aDirectory->Clone(getter_AddRefs(directory));
+ if (NS_FAILED(rv))
+ return rv;
+ mDisplayDirectory = do_QueryInterface(directory, &rv);
+ return rv;
+}
+
+// Get the display directory
+NS_IMETHODIMP nsBaseFilePicker::GetDisplayDirectory(nsIFile **aDirectory)
+{
+ *aDirectory = nullptr;
+ 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;
+}
+
+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;
+
+ 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 000000000..56ca5acc8
--- /dev/null
+++ b/widget/nsBaseFilePicker.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 nsBaseFilePicker_h__
+#define nsBaseFilePicker_h__
+
+#include "nsISupports.h"
+#include "nsIFilePicker.h"
+#include "nsISimpleEnumerator.h"
+#include "nsArrayEnumerator.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class nsPIDOMWindowOuter;
+class nsIWidget;
+
+class nsBaseFilePicker : public nsIFilePicker
+{
+public:
+ nsBaseFilePicker();
+ virtual ~nsBaseFilePicker();
+
+ NS_IMETHOD Init(mozIDOMWindowProxy* aParent,
+ const nsAString& aTitle,
+ int16_t aMode);
+
+ NS_IMETHOD Open(nsIFilePickerShownCallback *aCallback);
+ NS_IMETHOD AppendFilters(int32_t filterMask);
+ NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex);
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex);
+ NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles);
+ NS_IMETHOD GetDisplayDirectory(nsIFile * *aDisplayDirectory);
+ NS_IMETHOD SetDisplayDirectory(nsIFile * aDisplayDirectory);
+ NS_IMETHOD GetAddToRecentDocs(bool *aFlag);
+ NS_IMETHOD SetAddToRecentDocs(bool aFlag);
+ NS_IMETHOD GetMode(int16_t *aMode);
+ NS_IMETHOD SetOkButtonLabel(const nsAString& aLabel);
+ NS_IMETHOD GetOkButtonLabel(nsAString& aLabel);
+
+ NS_IMETHOD GetDomFileOrDirectory(nsISupports** aValue);
+ NS_IMETHOD GetDomFileOrDirectoryEnumerator(nsISimpleEnumerator** aValue);
+
+protected:
+
+ virtual void InitNative(nsIWidget *aParent, const nsAString& aTitle) = 0;
+
+ bool mAddToRecentDocs;
+ nsCOMPtr<nsIFile> mDisplayDirectory;
+
+ nsCOMPtr<nsPIDOMWindowOuter> mParent;
+ int16_t mMode;
+ nsString mOkButtonLabel;
+};
+
+#endif // nsBaseFilePicker_h__
diff --git a/widget/nsBaseScreen.cpp b/widget/nsBaseScreen.cpp
new file mode 100644
index 000000000..da523853a
--- /dev/null
+++ b/widget/nsBaseScreen.cpp
@@ -0,0 +1,90 @@
+/* -*- 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()
+{
+ for (uint32_t i = 0; i < nsIScreen::BRIGHTNESS_LEVELS; i++)
+ mBrightnessLocks[i] = 0;
+}
+
+nsBaseScreen::~nsBaseScreen() { }
+
+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::LockMinimumBrightness(uint32_t aBrightness)
+{
+ MOZ_ASSERT(aBrightness < nsIScreen::BRIGHTNESS_LEVELS,
+ "Invalid brightness level to lock");
+ mBrightnessLocks[aBrightness]++;
+ MOZ_ASSERT(mBrightnessLocks[aBrightness] > 0,
+ "Overflow after locking brightness level");
+
+ CheckMinimumBrightness();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseScreen::UnlockMinimumBrightness(uint32_t aBrightness)
+{
+ MOZ_ASSERT(aBrightness < nsIScreen::BRIGHTNESS_LEVELS,
+ "Invalid brightness level to lock");
+ MOZ_ASSERT(mBrightnessLocks[aBrightness] > 0,
+ "Unlocking a brightness level with no corresponding lock");
+ mBrightnessLocks[aBrightness]--;
+
+ CheckMinimumBrightness();
+
+ return NS_OK;
+}
+
+void
+nsBaseScreen::CheckMinimumBrightness()
+{
+ uint32_t brightness = nsIScreen::BRIGHTNESS_LEVELS;
+ for (int32_t i = nsIScreen::BRIGHTNESS_LEVELS - 1; i >=0; i--) {
+ if (mBrightnessLocks[i] > 0) {
+ brightness = i;
+ break;
+ }
+ }
+
+ ApplyMinimumBrightness(brightness);
+}
+
+NS_IMETHODIMP
+nsBaseScreen::GetContentsScaleFactor(double* aContentsScaleFactor)
+{
+ *aContentsScaleFactor = 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseScreen::GetDefaultCSSScaleFactor(double* aScaleFactor)
+{
+ *aScaleFactor = 1.0;
+ return NS_OK;
+}
diff --git a/widget/nsBaseScreen.h b/widget/nsBaseScreen.h
new file mode 100644
index 000000000..b07de8a9c
--- /dev/null
+++ b/widget/nsBaseScreen.h
@@ -0,0 +1,81 @@
+/* -*- 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;
+
+ /**
+ * Simple management of screen brightness locks. This abstract base class
+ * allows all widget implementations to share brightness locking code.
+ */
+ NS_IMETHOD LockMinimumBrightness(uint32_t aBrightness) override;
+ NS_IMETHOD UnlockMinimumBrightness(uint32_t aBrightness) override;
+
+ NS_IMETHOD GetRotation(uint32_t* aRotation) override {
+ *aRotation = nsIScreen::ROTATION_0_DEG;
+ return NS_OK;
+ }
+ NS_IMETHOD SetRotation(uint32_t aRotation) override { return NS_ERROR_NOT_AVAILABLE; }
+
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor) override;
+
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor) override;
+
+protected:
+ virtual ~nsBaseScreen();
+
+ /**
+ * Manually set the current level of brightness locking. This is called after
+ * we determine, based on the current active locks, what the strongest
+ * lock is. You should normally not call this function - it will be
+ * called automatically by this class.
+ *
+ * Each widget implementation should implement this in a way that
+ * makes sense there. This is normally the only function that
+ * contains widget-specific code.
+ *
+ * The default implementation does nothing.
+ *
+ * @param aBrightness The current brightness level to set. If this is
+ * nsIScreen::BRIGHTNESS_LEVELS
+ * (an impossible value for a brightness level to be),
+ * then that signifies that there is no current
+ * minimum brightness level, and the screen can shut off.
+ */
+ virtual void ApplyMinimumBrightness(uint32_t aBrightness) { }
+
+private:
+ /**
+ * Checks what the minimum brightness value is, and calls
+ * ApplyMinimumBrightness.
+ */
+ void CheckMinimumBrightness();
+
+ uint32_t mBrightnessLocks[nsIScreen::BRIGHTNESS_LEVELS];
+};
+
+#endif // nsBaseScreen_h
diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp
new file mode 100644
index 000000000..b820fed3c
--- /dev/null
+++ b/widget/nsBaseWidget.cpp
@@ -0,0 +1,3326 @@
+/* -*- 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/ArrayUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEventDispatcherListener.h"
+
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "nsBaseWidget.h"
+#include "nsDeviceContext.h"
+#include "nsCOMPtr.h"
+#include "nsGfxCIID.h"
+#include "nsWidgetsCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIKeyEventInPluginCallback.h"
+#include "nsIScreenManager.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsIServiceManager.h"
+#include "mozilla/Preferences.h"
+#include "BasicLayers.h"
+#include "ClientLayerManager.h"
+#include "mozilla/layers/Compositor.h"
+#include "nsIXULRuntime.h"
+#include "nsIXULWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsXULPopupManager.h"
+#include "nsIWidgetListener.h"
+#include "nsIGfxInfo.h"
+#include "npapi.h"
+#include "X11UndefineNone.h"
+#include "base/thread.h"
+#include "prdtoa.h"
+#include "prenv.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "gfxPrefs.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/MouseEvents.h"
+#include "GLConsts.h"
+#include "mozilla/Unused.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/layers/APZEventState.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/ChromeProcessController.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/Move.h"
+#include "mozilla/Services.h"
+#include "mozilla/Sprintf.h"
+#include "nsRefPtrHashtable.h"
+#include "TouchEvents.h"
+#include "WritingModes.h"
+#include "InputData.h"
+#include "FrameLayerBuilder.h"
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+#include "gfxConfig.h"
+#include "mozilla/layers/CompositorSession.h"
+#include "VRManagerChild.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;
+using base::Thread;
+
+nsIContent* nsBaseWidget::mLastRollup = nullptr;
+// Global user preference for disabling native theme. Used
+// in NativeWindowTheme.
+bool gDisableNativeTheme = false;
+
+// 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;
+
+namespace mozilla {
+namespace widget {
+
+void
+IMENotification::SelectionChangeDataBase::SetWritingMode(
+ const WritingMode& aWritingMode)
+{
+ mWritingMode = aWritingMode.mWritingMode;
+}
+
+WritingMode
+IMENotification::SelectionChangeDataBase::GetWritingMode() const
+{
+ return WritingMode(mWritingMode);
+}
+
+} // namespace widget
+} // namespace mozilla
+
+nsAutoRollup::nsAutoRollup()
+{
+ // remember if mLastRollup was null, and only clear it upon destruction
+ // if so. This prevents recursive usage of nsAutoRollup from clearing
+ // mLastRollup when it shouldn't.
+ wasClear = !nsBaseWidget::mLastRollup;
+}
+
+nsAutoRollup::~nsAutoRollup()
+{
+ if (nsBaseWidget::mLastRollup && wasClear) {
+ NS_RELEASE(nsBaseWidget::mLastRollup);
+ }
+}
+
+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)
+, mPopupLevel(ePopupLevelTop)
+, mPopupType(ePopupTypeAny)
+, mCompositorWidgetDelegate(nullptr)
+, mUpdateCursor(true)
+, mUseAttachedEvents(false)
+, mIMEHasFocus(false)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
+, mAccessibilityInUseFlag(false)
+#endif
+{
+#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 && !strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ RefPtr<nsBaseWidget> widget(mWidget);
+ widget->Shutdown();
+ }
+ return NS_OK;
+}
+
+void
+WidgetShutdownObserver::Register()
+{
+ if (!mRegistered) {
+ mRegistered = true;
+ nsContentUtils::RegisterShutdownObserver(this);
+ }
+}
+
+void
+WidgetShutdownObserver::Unregister()
+{
+ if (mRegistered) {
+ mWidget = nullptr;
+ nsContentUtils::UnregisterShutdownObserver(this);
+ mRegistered = false;
+ }
+}
+
+void
+nsBaseWidget::Shutdown()
+{
+ RevokeTransactionIdAllocator();
+ DestroyCompositor();
+ FreeShutdownObserver();
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+ if (sPluginWidgetList) {
+ delete sPluginWidgetList;
+ sPluginWidgetList = nullptr;
+ }
+#endif
+}
+
+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) {
+ 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;
+ mCompositorWidgetDelegate = nullptr;
+ mCompositorBridgeChild = nullptr;
+
+ // XXX CompositorBridgeChild and CompositorBridgeParent might be re-created in
+ // ClientLayerManager destructor. See bug 1133426.
+ RefPtr<CompositorSession> session = mCompositorSession.forget();
+ session->Shutdown();
+ }
+}
+
+// This prevents the layer manager from starting a new transaction during
+// shutdown.
+void
+nsBaseWidget::RevokeTransactionIdAllocator()
+{
+ if (!mLayerManager) {
+ return;
+ }
+
+ ClientLayerManager* clm = mLayerManager->AsClientLayerManager();
+ if (!clm) {
+ return;
+ }
+
+ clm->SetTransactionIdAllocator(nullptr);
+}
+
+void nsBaseWidget::ReleaseContentController()
+{
+ if (mRootContentController) {
+ mRootContentController->Destroy();
+ mRootContentController = nullptr;
+ }
+}
+
+void nsBaseWidget::DestroyLayerManager()
+{
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ mLayerManager = nullptr;
+ }
+ DestroyCompositor();
+}
+
+void
+nsBaseWidget::OnRenderingDeviceReset()
+{
+ if (!mLayerManager || !mCompositorSession) {
+ return;
+ }
+
+ nsTArray<LayersBackend> backendHints;
+ gfxPlatform::GetPlatform()->GetCompositorBackends(ComputeShouldAccelerate(), backendHints);
+
+ // If the existing compositor does not use acceleration, and this widget
+ // should not be accelerated, then there's no point in resetting.
+ //
+ // Note that if this widget should be accelerated, but instead has a basic
+ // compositor, we still reset just in case we're now in the position to get
+ // accelerated layers again.
+ RefPtr<ClientLayerManager> clm = mLayerManager->AsClientLayerManager();
+ if (!ComputeShouldAccelerate() &&
+ clm->GetTextureFactoryIdentifier().mParentBackend != LayersBackend::LAYERS_BASIC)
+ {
+ return;
+ }
+
+ // Recreate the compositor.
+ TextureFactoryIdentifier identifier;
+ if (!mCompositorSession->Reset(backendHints, &identifier)) {
+ // No action was taken, so we don't have to do anything.
+ return;
+ }
+
+ // Invalidate all layers.
+ FrameLayerBuilder::InvalidateAllLayers(mLayerManager);
+
+ // Update the texture factory identifier.
+ clm->UpdateTextureFactoryIdentifier(identifier);
+ ImageBridgeChild::IdentifyCompositorTextureHost(identifier);
+ gfx::VRManagerChild::IdentifyTextureHost(identifier);
+}
+
+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)
+{
+ static bool gDisableNativeThemeCached = false;
+ if (!gDisableNativeThemeCached) {
+ Preferences::AddBoolVarCache(&gDisableNativeTheme,
+ "mozilla.widget.disable-native-theme",
+ gDisableNativeTheme);
+ gDisableNativeThemeCached = true;
+ }
+
+ // keep a reference to the device context
+ if (nullptr != aInitData) {
+ mWindowType = aInitData->mWindowType;
+ mBorderStyle = aInitData->mBorderStyle;
+ mPopupLevel = aInitData->mPopupLevel;
+ mPopupType = aInitData->mPopupHint;
+ }
+
+ 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 {
+ static NS_DEFINE_IID(kCChildCID, NS_CHILD_CID);
+ widget = do_CreateInstance(kCChildCID);
+ }
+
+ 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
+}
+
+
+//-------------------------------------------------------------------------
+//
+// Set this nsBaseWidget's parent
+//
+//-------------------------------------------------------------------------
+NS_IMETHODIMP nsBaseWidget::SetParent(nsIWidget* aNewParent)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+//-------------------------------------------------------------------------
+//
+// 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 = DefaultScaleOverride();
+
+ if (devPixelsPerCSSPixel <= 0.0) {
+ devPixelsPerCSSPixel = GetDefaultScaleInternal();
+ }
+
+ return CSSToLayoutDeviceScale(devPixelsPerCSSPixel);
+}
+
+/* static */
+double nsIWidget::DefaultScaleOverride()
+{
+ // The number of device pixels per CSS pixel. A value <= 0 means choose
+ // automatically based on the DPI. A positive value is used as-is. This effectively
+ // controls the size of a CSS "px".
+ double devPixelsPerCSSPixel = -1.0;
+
+ nsAdoptingCString prefString = Preferences::GetCString("layout.css.devPixelsPerPx");
+ if (!prefString.IsEmpty()) {
+ devPixelsPerCSSPixel = PR_strtod(prefString, nullptr);
+ }
+
+ return 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.
+ nsBaseWidget* 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;
+}
+
+//-------------------------------------------------------------------------
+//
+// Get this component cursor
+//
+//-------------------------------------------------------------------------
+nsCursor nsBaseWidget::GetCursor()
+{
+ return mCursor;
+}
+
+NS_IMETHODIMP nsBaseWidget::SetCursor(nsCursor aCursor)
+{
+ mCursor = aCursor;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseWidget::SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-------------------------------------------------------------------------
+//
+// 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;
+}
+
+//-------------------------------------------------------------------------
+//
+// Hide window borders/decorations for this widget
+//
+//-------------------------------------------------------------------------
+NS_IMETHODIMP nsBaseWidget::HideWindowChrome(bool aShouldHide)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* 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));
+}
+
+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(mAPZC);
+
+ ConfigureAPZControllerThread();
+
+ mAPZC->SetDPI(GetDPI());
+
+ RefPtr<IAPZCTreeManager> treeManager = mAPZC; // for capture by the lambdas
+
+ ContentReceivedInputBlockCallback callback(
+ [treeManager](const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId,
+ bool aPreventDefault)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod
+ <uint64_t, bool>(treeManager,
+ &IAPZCTreeManager::ContentReceivedInputBlock,
+ aInputBlockId,
+ aPreventDefault));
+ });
+ mAPZEventState = new APZEventState(this, mozilla::Move(callback));
+
+ mSetAllowedTouchBehaviorCallback = [treeManager](uint64_t aInputBlockId,
+ const nsTArray<TouchBehaviorFlags>& aFlags)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod
+ <uint64_t,
+ StoreCopyPassByLRef<nsTArray<TouchBehaviorFlags>>>(treeManager,
+ &IAPZCTreeManager::SetAllowedTouchBehavior,
+ aInputBlockId, aFlags));
+ };
+
+ 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 (Preferences::GetInt("dom.w3c_touch_events.enabled", 0) ||
+ Preferences::GetBool("dom.w3c_pointer_events.enabled", false)) {
+ RegisterTouchWindow();
+ }
+}
+
+void nsBaseWidget::ConfigureAPZControllerThread()
+{
+ // By default the controller thread is the main thread.
+ APZThreadUtils::SetControllerThread(MessageLoop::current());
+}
+
+void
+nsBaseWidget::SetConfirmedTargetAPZC(uint64_t aInputBlockId,
+ const nsTArray<ScrollableLayerGuid>& aTargets) const
+{
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod
+ <uint64_t, StoreCopyPassByRRef<nsTArray<ScrollableLayerGuid>>>(mAPZC,
+ &IAPZCTreeManager::SetTargetAPZC,
+ aInputBlockId, aTargets));
+}
+
+void
+nsBaseWidget::UpdateZoomConstraints(const uint32_t& aPresShellId,
+ const FrameMetrics::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;
+ }
+ uint64_t layersId = mCompositorSession->RootLayerTreeId();
+ mAPZC->UpdateZoomConstraints(ScrollableLayerGuid(layersId, aPresShellId, aViewId),
+ aConstraints);
+}
+
+bool
+nsBaseWidget::AsyncPanZoomEnabled() const
+{
+ return !!mAPZC;
+}
+
+nsEventStatus
+nsBaseWidget::ProcessUntransformedAPZEvent(WidgetInputEvent* aEvent,
+ const ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId,
+ nsEventStatus aApzResponse)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ InputAPZContext context(aGuid, aInputBlockId, aApzResponse);
+
+ // If this is an event that the APZ has targeted to an APZC in the root
+ // process, apply that APZC's callback-transform before dispatching the
+ // event. If the event is instead targeted to an APZC in the child process,
+ // the transform will be applied in the child process before dispatching
+ // the event there (see e.g. TabChild::RecvRealTouchEvent()).
+ if (aGuid.mLayersId == mCompositorSession->RootLayerTreeId()) {
+ APZCCallbackHelper::ApplyCallbackTransform(*aEvent, aGuid,
+ GetDefaultScale());
+ }
+
+ // 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 && !context.WasRoutedToChildProcess() && aInputBlockId) {
+ // EventStateManager did not route the event into the child process.
+ // It's safe to communicate to APZ that the event has been processed.
+ // TODO: Eventually we'll be able to move the SendSetTargetAPZCNotification
+ // call into APZEventState::Process*Event() as well.
+ if (WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent()) {
+ if (touchEvent->mMessage == eTouchStart) {
+ if (gfxPrefs::TouchActionEnabled()) {
+ APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(this,
+ GetDocument(), *(original->AsTouchEvent()), aInputBlockId,
+ mSetAllowedTouchBehaviorCallback);
+ }
+ APZCCallbackHelper::SendSetTargetAPZCNotification(this, GetDocument(),
+ *(original->AsTouchEvent()), aGuid, aInputBlockId);
+ }
+ mAPZEventState->ProcessTouchEvent(*touchEvent, aGuid, aInputBlockId,
+ aApzResponse, status);
+ } else if (WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent()) {
+ MOZ_ASSERT(wheelEvent->mFlags.mHandledByAPZ);
+ APZCCallbackHelper::SendSetTargetAPZCNotification(this, GetDocument(),
+ *(original->AsWheelEvent()), aGuid, aInputBlockId);
+ if (wheelEvent->mCanTriggerSwipe) {
+ ReportSwipeStarted(aInputBlockId, wheelEvent->TriggersSwipe());
+ }
+ mAPZEventState->ProcessWheelEvent(*wheelEvent, aGuid, aInputBlockId);
+ } else if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
+ MOZ_ASSERT(mouseEvent->mFlags.mHandledByAPZ);
+ APZCCallbackHelper::SendSetTargetAPZCNotification(this, GetDocument(),
+ *(original->AsMouseEvent()), aGuid, aInputBlockId);
+ mAPZEventState->ProcessMouseEvent(*mouseEvent, aGuid, aInputBlockId);
+ }
+ }
+
+ return status;
+}
+
+class DispatchWheelEventOnMainThread : public Runnable
+{
+public:
+ DispatchWheelEventOnMainThread(const ScrollWheelInput& aWheelInput,
+ nsBaseWidget* aWidget,
+ nsEventStatus aAPZResult,
+ uint64_t aInputBlockId,
+ ScrollableLayerGuid aGuid)
+ : mWheelInput(aWheelInput)
+ , mWidget(aWidget)
+ , mAPZResult(aAPZResult)
+ , mInputBlockId(aInputBlockId)
+ , mGuid(aGuid)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ WidgetWheelEvent wheelEvent = mWheelInput.ToWidgetWheelEvent(mWidget);
+ mWidget->ProcessUntransformedAPZEvent(&wheelEvent, mGuid, mInputBlockId, mAPZResult);
+ return NS_OK;
+ }
+
+private:
+ ScrollWheelInput mWheelInput;
+ nsBaseWidget* mWidget;
+ nsEventStatus mAPZResult;
+ uint64_t mInputBlockId;
+ ScrollableLayerGuid mGuid;
+};
+
+class DispatchWheelInputOnControllerThread : public Runnable
+{
+public:
+ DispatchWheelInputOnControllerThread(const WidgetWheelEvent& aWheelEvent,
+ IAPZCTreeManager* aAPZC,
+ nsBaseWidget* aWidget)
+ : mMainMessageLoop(MessageLoop::current())
+ , mWheelInput(aWheelEvent)
+ , mAPZC(aAPZC)
+ , mWidget(aWidget)
+ , mInputBlockId(0)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsEventStatus result = mAPZC->ReceiveInputEvent(mWheelInput, &mGuid, &mInputBlockId);
+ if (result == nsEventStatus_eConsumeNoDefault) {
+ return NS_OK;
+ }
+ RefPtr<Runnable> r = new DispatchWheelEventOnMainThread(mWheelInput, mWidget, result, mInputBlockId, mGuid);
+ mMainMessageLoop->PostTask(r.forget());
+ return NS_OK;
+ }
+
+private:
+ MessageLoop* mMainMessageLoop;
+ ScrollWheelInput mWheelInput;
+ RefPtr<IAPZCTreeManager> mAPZC;
+ nsBaseWidget* mWidget;
+ uint64_t mInputBlockId;
+ ScrollableLayerGuid mGuid;
+};
+
+void
+nsBaseWidget::DispatchTouchInput(MultiTouchInput& aInput)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mAPZC) {
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ uint64_t inputBlockId = 0;
+ ScrollableLayerGuid guid;
+
+ nsEventStatus result = mAPZC->ReceiveInputEvent(aInput, &guid, &inputBlockId);
+ if (result == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ WidgetTouchEvent event = aInput.ToWidgetTouchEvent(this);
+ ProcessUntransformedAPZEvent(&event, guid, inputBlockId, result);
+ } else {
+ WidgetTouchEvent event = aInput.ToWidgetTouchEvent(this);
+
+ nsEventStatus status;
+ DispatchEvent(&event, status);
+ }
+}
+
+nsEventStatus
+nsBaseWidget::DispatchInputEvent(WidgetInputEvent* aEvent)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mAPZC) {
+ if (APZThreadUtils::IsControllerThread()) {
+ uint64_t inputBlockId = 0;
+ ScrollableLayerGuid guid;
+
+ nsEventStatus result = mAPZC->ReceiveInputEvent(*aEvent, &guid, &inputBlockId);
+ if (result == nsEventStatus_eConsumeNoDefault) {
+ return result;
+ }
+ return ProcessUntransformedAPZEvent(aEvent, guid, inputBlockId, result);
+ } else {
+ WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
+ if (wheelEvent) {
+ RefPtr<Runnable> r = new DispatchWheelInputOnControllerThread(*wheelEvent, mAPZC, this);
+ APZThreadUtils::RunOnControllerThread(r.forget());
+ return nsEventStatus_eConsumeDoDefault;
+ }
+ MOZ_CRASH();
+ }
+ }
+
+ nsEventStatus status;
+ DispatchEvent(aEvent, status);
+ return status;
+}
+
+void
+nsBaseWidget::DispatchEventToAPZOnly(mozilla::WidgetInputEvent* aEvent)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mAPZC) {
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ uint64_t inputBlockId = 0;
+ ScrollableLayerGuid guid;
+ mAPZC->ReceiveInputEvent(*aEvent, &guid, &inputBlockId);
+ }
+}
+
+nsIDocument*
+nsBaseWidget::GetDocument() const
+{
+ if (mWidgetListener) {
+ if (nsIPresShell* 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()) {
+ mCompositorVsyncDispatcher = new CompositorVsyncDispatcher();
+ }
+}
+
+CompositorVsyncDispatcher*
+nsBaseWidget::GetCompositorVsyncDispatcher()
+{
+ return mCompositorVsyncDispatcher;
+}
+
+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;
+ }
+
+ CreateCompositorVsyncDispatcher();
+
+ RefPtr<ClientLayerManager> lm = new ClientLayerManager(this);
+
+ bool useAPZ = UseAPZ();
+
+ gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get();
+ mCompositorSession = gpu->CreateTopLevelCompositor(
+ this,
+ lm,
+ GetDefaultScale(),
+ useAPZ,
+ UseExternalCompositingSurface(),
+ gfx::IntSize(aWidth, aHeight));
+ mCompositorBridgeChild = mCompositorSession->GetCompositorBridgeChild();
+ mCompositorWidgetDelegate = mCompositorSession->GetCompositorWidgetDelegate();
+
+ if (useAPZ) {
+ mAPZC = mCompositorSession->GetAPZCTreeManager();
+ ConfigureAPZCTreeManager();
+ } else {
+ mAPZC = nullptr;
+ }
+
+ if (mInitialZoomConstraints) {
+ UpdateZoomConstraints(mInitialZoomConstraints->mPresShellID,
+ mInitialZoomConstraints->mViewID,
+ Some(mInitialZoomConstraints->mConstraints));
+ mInitialZoomConstraints.reset();
+ }
+
+ ShadowLayerForwarder* lf = lm->AsShadowForwarder();
+ // As long as we are creating a ClientLayerManager above lf must be non-null.
+ MOZ_ASSERT(lf);
+
+ if (lf) {
+ TextureFactoryIdentifier textureFactoryIdentifier;
+ PLayerTransactionChild* shadowManager = nullptr;
+
+ nsTArray<LayersBackend> backendHints;
+ gfxPlatform::GetPlatform()->GetCompositorBackends(ComputeShouldAccelerate(), backendHints);
+
+ bool success = false;
+ if (!backendHints.IsEmpty()) {
+ shadowManager = mCompositorBridgeChild->SendPLayerTransactionConstructor(
+ backendHints, 0, &textureFactoryIdentifier, &success);
+ }
+
+ if (!success) {
+ NS_WARNING("Failed to create an OMT compositor.");
+ DestroyCompositor();
+ mLayerManager = nullptr;
+ return;
+ }
+
+ lf->SetShadowManager(shadowManager);
+ if (ClientLayerManager* clm = lm->AsClientLayerManager()) {
+ clm->UpdateTextureFactoryIdentifier(textureFactoryIdentifier);
+ }
+ // 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 = lm.forget();
+
+ // 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::NotifyRemoteCompositorSessionLost(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 TabChild
+ // 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;
+}
+
+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();
+}
+
+NS_IMETHODIMP nsBaseWidget::MoveClient(double aX, double aY)
+{
+ 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();
+ return Move(aX - desktopOffset.x, aY - desktopOffset.y);
+ } else {
+ return Move(aX - clientOffset.x, aY - clientOffset.y);
+ }
+}
+
+NS_IMETHODIMP nsBaseWidget::ResizeClient(double aWidth,
+ double aHeight,
+ bool aRepaint)
+{
+ NS_ASSERTION((aWidth >=0) , "Negative width passed to ResizeClient");
+ NS_ASSERTION((aHeight >=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();
+ return Resize(aWidth + desktopDelta.width, aHeight + desktopDelta.height,
+ aRepaint);
+ } else {
+ return Resize(mBounds.width + (aWidth - clientBounds.width),
+ mBounds.height + (aHeight - clientBounds.height), aRepaint);
+ }
+}
+
+NS_IMETHODIMP nsBaseWidget::ResizeClient(double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint)
+{
+ NS_ASSERTION((aWidth >=0) , "Negative width passed to ResizeClient");
+ NS_ASSERTION((aHeight >=0), "Negative height passed to ResizeClient");
+
+ LayoutDeviceIntRect clientBounds = GetClientBounds();
+ LayoutDeviceIntPoint clientOffset = GetClientOffset();
+
+ if (BoundsUseDesktopPixels()) {
+ DesktopToLayoutDeviceScale scale = GetDesktopToDeviceScale();
+ DesktopPoint desktopOffset = clientOffset / scale;
+ DesktopSize desktopDelta =
+ (LayoutDeviceIntSize(mBounds.width, mBounds.height) -
+ clientBounds.Size()) / scale;
+ return Resize(aX - desktopOffset.x, aY - desktopOffset.y,
+ aWidth + desktopDelta.width, aHeight + desktopDelta.height,
+ aRepaint);
+ } else {
+ return Resize(aX - clientOffset.x, aY - clientOffset.y,
+ aWidth + mBounds.width - clientBounds.width,
+ aHeight + 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);
+}
+
+NS_IMETHODIMP
+nsBaseWidget::SetNonClientMargins(LayoutDeviceIntMargin &margins)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t nsBaseWidget::GetMaxTouchPoints() const
+{
+ return 0;
+}
+
+NS_IMETHODIMP
+nsBaseWidget::GetAttention(int32_t aCycleCount) {
+ return NS_OK;
+}
+
+bool
+nsBaseWidget::HasPendingInputEvent()
+{
+ return false;
+}
+
+NS_IMETHODIMP
+nsBaseWidget::SetIcon(const nsAString&)
+{
+ return NS_OK;
+}
+
+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(NS_LITERAL_STRING("icons"));
+ aFile->Append(NS_LITERAL_STRING("default"));
+ 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);
+}
+
+NS_IMETHODIMP
+nsBaseWidget::BeginResizeDrag(WidgetGUIEvent* aEvent,
+ int32_t aHorizontal,
+ int32_t aVertical)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBaseWidget::BeginMoveDrag(WidgetMouseEvent* aEvent)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsBaseWidget::SetSizeConstraints(const SizeConstraints& aConstraints)
+{
+ mSizeConstraints = aConstraints;
+ // We can't ensure that the size is honored at this point because we're
+ // probably in the middle of a reflow.
+}
+
+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<nsIXULWindow> window = mWidgetListener->GetXULWindow();
+ nsCOMPtr<nsIBaseWindow> xulWindow(do_QueryInterface(window));
+ if (xulWindow) {
+ xulWindow->Destroy();
+ }
+}
+
+void
+nsBaseWidget::NotifySizeMoveDone()
+{
+ if (!mWidgetListener || mWidgetListener->GetXULWindow())
+ return;
+
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->WindowSizeMoveDone();
+ }
+}
+
+void
+nsBaseWidget::NotifyWindowMoved(int32_t aX, int32_t aY)
+{
+ if (mWidgetListener) {
+ mWidgetListener->WindowMoved(this, aX, aY);
+ }
+
+ if (mIMEHasFocus && GetIMEUpdatePreference().WantPositionChanged()) {
+ NotifyIME(IMENotification(IMEMessage::NOTIFY_IME_OF_POSITION_CHANGE));
+ }
+}
+
+void
+nsBaseWidget::NotifySysColorChanged()
+{
+ if (!mWidgetListener || mWidgetListener->GetXULWindow())
+ return;
+
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->SysColorChanged();
+ }
+}
+
+void
+nsBaseWidget::NotifyThemeChanged()
+{
+ if (!mWidgetListener || mWidgetListener->GetXULWindow())
+ return;
+
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->ThemeChanged();
+ }
+}
+
+void
+nsBaseWidget::NotifyUIStateChanged(UIStateChangeType aShowAccelerators,
+ UIStateChangeType aShowFocusRings)
+{
+ if (nsIDocument* doc = GetDocument()) {
+ nsPIDOMWindowOuter* win = doc->GetWindow();
+ if (win) {
+ win->SetKeyboardIndicators(aShowAccelerators, aShowFocusRings);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsBaseWidget::NotifyIME(const IMENotification& aIMENotification)
+{
+ switch (aIMENotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ // Currently, if native IME handler doesn't use TextEventDispatcher,
+ // the request may be notified to mTextEventDispatcher or native IME
+ // directly. Therefore, if mTextEventDispatcher has a composition,
+ // the request should be handled by the mTextEventDispatcher.
+ if (mTextEventDispatcher && mTextEventDispatcher->IsComposing()) {
+ return mTextEventDispatcher->NotifyIME(aIMENotification);
+ }
+ // Otherwise, it should be handled by native IME.
+ return NotifyIMEInternal(aIMENotification);
+ default: {
+ if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) {
+ mIMEHasFocus = true;
+ }
+ EnsureTextEventDispatcher();
+ // If the platform specific widget uses TextEventDispatcher for handling
+ // native IME and keyboard events, IME event handler should be notified
+ // of the notification via TextEventDispatcher. Otherwise, on the other
+ // platforms which have not used TextEventDispatcher yet, IME event
+ // handler should be notified by the old path (NotifyIMEInternal).
+ nsresult rv = mTextEventDispatcher->NotifyIME(aIMENotification);
+ nsresult rv2 = NotifyIMEInternal(aIMENotification);
+ if (aIMENotification.mMessage == NOTIFY_IME_OF_BLUR) {
+ mIMEHasFocus = false;
+ }
+ return rv2 == NS_ERROR_NOT_IMPLEMENTED ? rv : rv2;
+ }
+ }
+}
+
+void
+nsBaseWidget::EnsureTextEventDispatcher()
+{
+ if (mTextEventDispatcher) {
+ return;
+ }
+ mTextEventDispatcher = new TextEventDispatcher(this);
+}
+
+NS_IMETHODIMP_(nsIWidget::TextEventDispatcher*)
+nsBaseWidget::GetTextEventDispatcher()
+{
+ EnsureTextEventDispatcher();
+ return mTextEventDispatcher;
+}
+
+void*
+nsBaseWidget::GetPseudoIMEContext()
+{
+ TextEventDispatcher* dispatcher = GetTextEventDispatcher();
+ if (!dispatcher) {
+ return nullptr;
+ }
+ return dispatcher->GetPseudoIMEContext();
+}
+
+NS_IMETHODIMP_(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 FrameMetrics::ViewID& aViewId,
+ const CSSRect& aRect,
+ const uint32_t& aFlags)
+{
+ if (!mCompositorSession || !mAPZC) {
+ return;
+ }
+ uint64_t layerId = mCompositorSession->RootLayerTreeId();
+ mAPZC->ZoomToRect(ScrollableLayerGuid(layerId, aPresShellId, aViewId), aRect, aFlags);
+}
+
+#ifdef ACCESSIBILITY
+
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
+// defined in nsAppRunner.cpp
+extern const char* kAccessibilityLastRunDatePref;
+
+static inline uint32_t
+PRTimeToSeconds(PRTime t_usec)
+{
+ PRTime usec_per_sec = PR_USEC_PER_SEC;
+ return uint32_t(t_usec /= usec_per_sec);
+}
+#endif
+
+a11y::Accessible*
+nsBaseWidget::GetRootAccessible()
+{
+ NS_ENSURE_TRUE(mWidgetListener, nullptr);
+
+ nsIPresShell* 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) {
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
+ if (!mAccessibilityInUseFlag) {
+ mAccessibilityInUseFlag = true;
+ uint32_t now = PRTimeToSeconds(PR_Now());
+ Preferences::SetInt(kAccessibilityLastRunDatePref, now);
+ }
+#endif
+ return accService->GetRootDocumentAccessible(presShell, nsContentUtils::IsSafeToRunScript());
+ }
+
+ return nullptr;
+}
+
+#endif // ACCESSIBILITY
+
+void
+nsBaseWidget::StartAsyncScrollbarDrag(const AsyncDragMetrics& aDragMetrics)
+{
+ if (!AsyncPanZoomEnabled()) {
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess() && mCompositorSession);
+
+ int layersId = mCompositorSession->RootLayerTreeId();;
+ ScrollableLayerGuid guid(layersId, aDragMetrics.mPresShellId, aDragMetrics.mViewId);
+
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod
+ <ScrollableLayerGuid, AsyncDragMetrics>(mAPZC,
+ &IAPZCTreeManager::StartScrollbarDrag,
+ guid, aDragMetrics));
+}
+
+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();
+}
+
+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 = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ 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->InitWithFuncCallback(OnLongTapTimerCallback, this,
+ timeout,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+
+ // 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)
+{
+ nsIWidget *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::RegisterPluginWindowForRemoteUpdates()
+{
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
+ NS_NOTREACHED("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, this);
+#endif
+}
+
+void
+nsBaseWidget::UnregisterPluginWindowForRemoteUpdates()
+{
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
+ NS_NOTREACHED("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
+}
+
+// static
+nsIWidget*
+nsIWidget::LookupRegisteredPluginWindow(uintptr_t aWindowID)
+{
+#if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK)
+ NS_NOTREACHED("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)
+ NS_NOTREACHED("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()) {
+ 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->GetAsyncContainerID();
+}
+
+void
+nsBaseWidget::UpdateScrollCapture()
+{
+ // Don't capture if no container or no size.
+ if (!mScrollCaptureContainer || mBounds.width <= 0 || mBounds.height <= 0) {
+ 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::Color::FromABGR(kScrollCaptureFillColor)),
+ gfx::DrawOptions(1.f, gfx::CompositionOp::OP_SOURCE));
+ aSnapshotDrawTarget->Flush();
+}
+#endif
+
+NS_IMETHODIMP_(nsIWidget::NativeIMEContext)
+nsIWidget::GetNativeIMEContext()
+{
+ return NativeIMEContext(this);
+}
+
+nsresult
+nsIWidget::OnWindowedPluginKeyEvent(const NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+namespace mozilla {
+namespace widget {
+
+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 widget
+} // namespace mozilla
+
+#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(NS_LITERAL_STRING("UNKNOWN"));
+
+#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(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:
+ {
+ char buf[32];
+
+ SprintfLiteral(buf,"UNKNOWN: %d",aGuiEvent->mMessage);
+
+ CopyASCIItoUTF16(buf, eventName);
+ }
+ 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 }
+};
+
+//////////////////////////////////////////////////////////////
+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() {}
+
+ 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
+ Preferences::AddStrongObserver(obs, debug_PrefValues[i].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 000000000..bbc6b7238
--- /dev/null
+++ b/widget/nsBaseWidget.h
@@ -0,0 +1,753 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/layers/APZCCallbackHelper.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 nsAutoRollup;
+class gfxContext;
+
+namespace mozilla {
+class CompositorVsyncDispatcher;
+#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;
+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
+{
+ friend class nsAutoRollup;
+ friend class DispatchWheelEventOnMainThread;
+ 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;
+ NS_IMETHOD 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 nsCursor GetCursor() override;
+ NS_IMETHOD SetCursor(nsCursor aCursor) override;
+ NS_IMETHOD SetCursor(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(int32_t aStyle) override {}
+ virtual void SetShowsToolbarButton(bool aShow) override {}
+ virtual void SetShowsFullScreenButton(bool aShow) override {}
+ virtual void SetWindowAnimationType(WindowAnimationType aType) override {}
+ NS_IMETHOD 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 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 NotifyRemoteCompositorSessionLost(mozilla::layers::CompositorSession* aSession);
+
+ mozilla::CompositorVsyncDispatcher* GetCompositorVsyncDispatcher();
+ void CreateCompositorVsyncDispatcher();
+ virtual void CreateCompositor();
+ virtual void CreateCompositor(int aWidth, int aHeight);
+ 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);
+ }
+ virtual void ConstrainPosition(bool aAllowSlop,
+ int32_t *aX,
+ int32_t *aY) override {}
+ NS_IMETHOD MoveClient(double aX, double aY) override;
+ NS_IMETHOD ResizeClient(double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD ResizeClient(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
+ virtual LayoutDeviceIntRect GetBounds() override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ virtual MOZ_MUST_USE nsresult GetRestoredBounds(LayoutDeviceIntRect& aRect) override;
+ NS_IMETHOD SetNonClientMargins(LayoutDeviceIntMargin& aMargins) override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ virtual void EnableDragDrop(bool aEnable) override {};
+ NS_IMETHOD GetAttention(int32_t aCycleCount) override;
+ virtual bool HasPendingInputEvent() override;
+ NS_IMETHOD SetIcon(const nsAString &anIconSpec) override;
+ virtual void SetWindowTitlebarColor(nscolor aColor, bool aActive)
+ override {}
+ virtual void SetDrawsInTitlebar(bool aState) override {}
+ virtual bool ShowsResizeIndicator(LayoutDeviceIntRect* aResizerRect) override;
+ virtual void FreeNativeData(void * data, uint32_t aDataType) override {}
+ NS_IMETHOD BeginResizeDrag(mozilla::WidgetGUIEvent* aEvent,
+ int32_t aHorizontal,
+ int32_t aVertical) override;
+ NS_IMETHOD BeginMoveDrag(mozilla::WidgetMouseEvent* aEvent) override;
+ virtual nsresult ActivateNativeMenuItemAt(const nsAString& indexString) override { return NS_ERROR_NOT_IMPLEMENTED; }
+ virtual nsresult ForceUpdateNativeMenuAt(const nsAString& indexString) override { return NS_ERROR_NOT_IMPLEMENTED; }
+ NS_IMETHOD NotifyIME(const IMENotification& aIMENotification) override final;
+ NS_IMETHOD StartPluginIME(const mozilla::WidgetKeyboardEvent& aKeyboardEvent,
+ int32_t aPanelX, int32_t aPanelY,
+ nsString& aCommitted) override
+ { return NS_ERROR_NOT_IMPLEMENTED; }
+ virtual void SetPluginFocused(bool& aFocused) override {}
+ virtual void SetCandidateWindowForPlugin(
+ const mozilla::widget::CandidateWindowPosition&
+ aPosition) override
+ { }
+ virtual void DefaultProcOfPluginEvent(
+ const mozilla::WidgetPluginEvent& aEvent) override
+ { }
+ NS_IMETHOD AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent) override { return NS_ERROR_NOT_IMPLEMENTED; }
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) override { return false; }
+ bool ComputeShouldAccelerate();
+ virtual bool WidgetTypeSupportsAcceleration() { return true; }
+ virtual nsIMEUpdatePreference GetIMEUpdatePreference() override { return nsIMEUpdatePreference(); }
+ NS_IMETHOD 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;
+ NS_IMETHOD_(TextEventDispatcher*) GetTextEventDispatcher() override final;
+ NS_IMETHOD_(TextEventDispatcherListener*)
+ GetNativeTextEventDispatcherListener() override;
+ virtual void ZoomToRect(const uint32_t& aPresShellId,
+ const FrameMetrics::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 FrameMetrics::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 NotifySysColorChanged();
+ void NotifyThemeChanged();
+ void NotifyUIStateChanged(UIStateChangeType aShowAccelerators,
+ 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;
+
+ /**
+ * 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();
+
+#if defined(XP_WIN)
+ uint64_t CreateScrollCaptureContainer() 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 void DrawWindowUnderlay(mozilla::widget::WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+ {}
+ virtual void DrawWindowOverlay(mozilla::widget::WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+ {}
+ virtual already_AddRefed<DrawTarget> StartRemoteDrawing();
+ virtual already_AddRefed<DrawTarget>
+ StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode)
+ {
+ return StartRemoteDrawing();
+ }
+ virtual void EndRemoteDrawing()
+ {}
+ virtual void EndRemoteDrawingInRegion(DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aInvalidRegion)
+ {
+ EndRemoteDrawing();
+ }
+ virtual void CleanupRemoteDrawing()
+ {}
+ virtual void CleanupWindowEffects()
+ {}
+ virtual bool InitCompositor(mozilla::layers::Compositor* aCompositor) {
+ return true;
+ }
+ virtual uint32_t GetGLFrameBufferFormat();
+ virtual mozilla::layers::Composer2D* GetComposer2D() {
+ return nullptr;
+ }
+
+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 ScrollableLayerGuid& aGuid,
+ uint64_t aInputBlockId,
+ nsEventStatus aApzResponse);
+
+ const LayoutDeviceIntRegion RegionFromArray(const nsTArray<LayoutDeviceIntRect>& aRects);
+ void ArrayFromRegion(const LayoutDeviceIntRegion& aRegion,
+ nsTArray<LayoutDeviceIntRect>& aRects);
+
+ virtual nsIContent* GetLastRollup() override
+ {
+ return mLastRollup;
+ }
+
+ 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;
+ }
+
+ virtual nsresult NotifyIMEInternal(const IMENotification& aIMENotification)
+ { return NS_ERROR_NOT_IMPLEMENTED; }
+
+ /**
+ * 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()
+ {
+ static NS_DEFINE_IID(kCPopUpCID, NS_CHILD_CID);
+ nsCOMPtr<nsIWidget> widget = do_CreateInstance(kCPopUpCID);
+ return widget.forget();
+ }
+
+ LayerManager* CreateBasicLayerManager();
+
+ nsPopupType PopupType() const { return mPopupType; }
+
+ 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;
+
+ /**
+ * Notify the widget that this window is being used with OMTC.
+ */
+ virtual void WindowUsesOMTC() {}
+ virtual void RegisterTouchWindow() {}
+
+ nsIDocument* GetDocument() const;
+
+ void EnsureTextEventDispatcher();
+
+ // Notify the compositor that a device reset has occurred.
+ void OnRenderingDeviceReset();
+
+ bool UseAPZ();
+
+ /**
+ * 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);
+
+#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;
+ 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;
+ nsPopupLevel mPopupLevel;
+ nsPopupType mPopupType;
+ SizeConstraints mSizeConstraints;
+
+ CompositorWidgetDelegate* mCompositorWidgetDelegate;
+
+ bool mUpdateCursor;
+ bool mUseAttachedEvents;
+ bool mIMEHasFocus;
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
+ bool mAccessibilityInUseFlag;
+#endif
+ static nsIRollupListener* gRollupListener;
+
+ // the last rolled up popup. Only set this when an nsAutoRollup is in scope,
+ // so it can be cleared automatically.
+ static nsIContent* mLastRollup;
+
+ struct InitialZoomConstraints {
+ InitialZoomConstraints(const uint32_t& aPresShellID,
+ const FrameMetrics::ViewID& aViewID,
+ const ZoomConstraints& aConstraints)
+ : mPresShellID(aPresShellID), mViewID(aViewID), mConstraints(aConstraints)
+ {
+ }
+
+ uint32_t mPresShellID;
+ FrameMetrics::ViewID mViewID;
+ ZoomConstraints mConstraints;
+ };
+
+ mozilla::Maybe<InitialZoomConstraints> mInitialZoomConstraints;
+
+#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
+};
+
+// 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 mLastRollup a reference to the popup
+// that was closed during the Rollup call, and prevent this popup from
+// reopening while processing the mouse event.
+// mLastRollup should only be set while an nsAutoRollup is in scope;
+// when it goes out of scope mLastRollup is cleared automatically.
+// As mLastRollup is static, it can be retrieved by calling
+// nsIWidget::GetLastRollup on any widget.
+class nsAutoRollup
+{
+ bool wasClear;
+
+ public:
+
+ nsAutoRollup();
+ ~nsAutoRollup();
+};
+
+#endif // nsBaseWidget_h__
diff --git a/widget/nsClipboardHelper.cpp b/widget/nsClipboardHelper.cpp
new file mode 100644
index 000000000..16eed0d89
--- /dev/null
+++ b/widget/nsClipboardHelper.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIServiceManager.h"
+
+// helpers
+#include "nsIClipboard.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsITransferable.h"
+#include "nsReadableUtils.h"
+
+NS_IMPL_ISUPPORTS(nsClipboardHelper, nsIClipboardHelper)
+
+/*****************************************************************************
+ * nsClipboardHelper ctor / dtor
+ *****************************************************************************/
+
+nsClipboardHelper::nsClipboardHelper()
+{
+}
+
+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,
+ aString.Length() * 2);
+ 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 000000000..71c3aa60f
--- /dev/null
+++ b/widget/nsClipboardHelper.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 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 000000000..f7d863475
--- /dev/null
+++ b/widget/nsClipboardProxy.cpp
@@ -0,0 +1,169 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 = false;
+ aTransferable->GetIsPrivateData(&isPrivateData);
+ nsCOMPtr<nsIPrincipal> requestingPrincipal;
+ aTransferable->GetRequestingPrincipal(getter_AddRefs(requestingPrincipal));
+ child->SendSetClipboard(ipcDataTransfer, isPrivateData,
+ IPC::Principal(requestingPrincipal), aWhichClipboard);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboardProxy::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
+{
+ nsTArray<nsCString> types;
+
+ nsCOMPtr<nsIArray> flavorList;
+ aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t flavorCount = 0;
+ flavorList->GetLength(&flavorCount);
+ for (uint32_t j = 0; j < flavorCount; ++j) {
+ nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavorList, j);
+ if (flavor) {
+ nsAutoCString flavorStr;
+ flavor->GetData(flavorStr);
+ if (flavorStr.Length()) {
+ types.AppendElement(flavorStr);
+ }
+ }
+ }
+ }
+
+ 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);
+
+ nsString data = item.data().get_nsString();
+ rv = dataWrapper->SetData(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper,
+ data.Length() * sizeof(char16_t));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (item.data().type() == IPCDataTransferData::TShmem) {
+ // If this is an image, convert it into an nsIInputStream.
+ 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),
+ nsDependentCString(data.get<char>(), data.Size<char>()));
+
+ rv = aTransferable->SetTransferData(flavor.get(), stream, sizeof(nsISupports*));
+ 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(nsDependentCString(data.get<char>(), data.Size<char>()));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper,
+ data.Size<char>());
+ 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 char **aFlavorList,
+ uint32_t aLength, int32_t aWhichClipboard,
+ bool *aHasType)
+{
+ *aHasType = false;
+
+ nsTArray<nsCString> types;
+ nsCString* t = types.AppendElements(aLength);
+ for (uint32_t j = 0; j < aLength; ++j) {
+ t[j].Rebind(aFlavorList[j], nsCharTraits<char>::length(aFlavorList[j]));
+ }
+
+ ContentChild::GetSingleton()->SendClipboardHasType(types, 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 000000000..c24bcbee9
--- /dev/null
+++ b/widget/nsClipboardProxy.h
@@ -0,0 +1,46 @@
+/* -*- Mode: c++; c-basic-offset: 4; 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() {}
+
+ ClipboardCapabilities mClipboardCaps;
+};
+
+#endif
diff --git a/widget/nsColorPickerProxy.cpp b/widget/nsColorPickerProxy.cpp
new file mode 100644
index 000000000..9ded27ef3
--- /dev/null
+++ b/widget/nsColorPickerProxy.cpp
@@ -0,0 +1,58 @@
+/* -*- 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 "nsColorPickerProxy.h"
+
+#include "mozilla/dom/TabChild.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsColorPickerProxy, nsIColorPicker)
+
+NS_IMETHODIMP
+nsColorPickerProxy::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ const nsAString& aInitialColor)
+{
+ TabChild* tabChild = TabChild::GetFrom(aParent);
+ if (!tabChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ tabChild->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;
+}
+
+bool
+nsColorPickerProxy::RecvUpdate(const nsString& aColor)
+{
+ if (mCallback) {
+ mCallback->Update(aColor);
+ }
+ return true;
+}
+
+bool
+nsColorPickerProxy::Recv__delete__(const nsString& aColor)
+{
+ if (mCallback) {
+ mCallback->Done(aColor);
+ mCallback = nullptr;
+ }
+ return true;
+}
diff --git a/widget/nsColorPickerProxy.h b/widget/nsColorPickerProxy.h
new file mode 100644
index 000000000..0f3af3793
--- /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() {}
+
+ virtual bool RecvUpdate(const nsString& aColor) override;
+ virtual bool Recv__delete__(const nsString& aColor) override;
+
+private:
+ ~nsColorPickerProxy() {}
+
+ 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 000000000..f8eaee250
--- /dev/null
+++ b/widget/nsContentProcessWidgetFactory.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ModuleUtils.h"
+#include "nsWidgetsCID.h"
+#include "nsClipboardProxy.h"
+#include "nsColorPickerProxy.h"
+#include "nsDatePickerProxy.h"
+#include "nsDragServiceProxy.h"
+#include "nsFilePickerProxy.h"
+#include "nsScreenManagerProxy.h"
+#include "mozilla/widget/PuppetBidiKeyboard.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#ifndef MOZ_WIDGET_GONK
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsColorPickerProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDatePickerProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragServiceProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFilePickerProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerProxy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(PuppetBidiKeyboard)
+
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_DATEPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
+NS_DEFINE_NAMED_CID(PUPPETBIDIKEYBOARD_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_DATEPICKER_CID, false, nullptr, nsDatePickerProxyConstructor,
+ 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_SCREENMANAGER_CID, false, nullptr, nsScreenManagerProxyConstructor,
+ Module::CONTENT_PROCESS_ONLY },
+ { &kPUPPETBIDIKEYBOARD_CID, false, NULL, PuppetBidiKeyboardConstructor,
+ mozilla::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/datepicker;1", &kNS_DATEPICKER_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/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::CONTENT_PROCESS_ONLY },
+ { "@mozilla.org/widget/bidikeyboard;1", &kPUPPETBIDIKEYBOARD_CID,
+ Module::CONTENT_PROCESS_ONLY },
+ { nullptr }
+};
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts
+};
+
+NSMODULE_DEFN(nsContentProcessWidgetModule) = &kWidgetModule;
+
+#endif /* MOZ_WIDGET_GONK */
diff --git a/widget/nsDatePickerProxy.cpp b/widget/nsDatePickerProxy.cpp
new file mode 100644
index 000000000..e6b88f1be
--- /dev/null
+++ b/widget/nsDatePickerProxy.cpp
@@ -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 "nsDatePickerProxy.h"
+
+#include "mozilla/dom/TabChild.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsDatePickerProxy, nsIDatePicker)
+
+/* void init (in nsIDOMWindow parent, in AString title, in short mode); */
+NS_IMETHODIMP
+nsDatePickerProxy::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ const nsAString& aInitialDate)
+{
+ TabChild* tabChild = TabChild::GetFrom(aParent);
+ if (!tabChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ tabChild->SendPDatePickerConstructor(this,
+ nsString(aTitle),
+ nsString(aInitialDate));
+ NS_ADDREF_THIS(); //Released in DeallocPDatePickerChild
+ return NS_OK;
+}
+
+/* void open (in nsIDatePickerShownCallback aDatePickerShownCallback); */
+NS_IMETHODIMP
+nsDatePickerProxy::Open(nsIDatePickerShownCallback* aDatePickerShownCallback)
+{
+ NS_ENSURE_STATE(!mCallback);
+ mCallback = aDatePickerShownCallback;
+
+ SendOpen();
+ return NS_OK;
+}
+
+bool
+nsDatePickerProxy::RecvCancel()
+{
+ if (mCallback) {
+ mCallback->Cancel();
+ mCallback = nullptr;
+ }
+ return true;
+}
+
+bool
+nsDatePickerProxy::Recv__delete__(const nsString& aDate)
+{
+ if (mCallback) {
+ mCallback->Done(aDate);
+ mCallback = nullptr;
+ }
+ return true;
+}
diff --git a/widget/nsDatePickerProxy.h b/widget/nsDatePickerProxy.h
new file mode 100644
index 000000000..71475932c
--- /dev/null
+++ b/widget/nsDatePickerProxy.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 nsDatePickerProxy_h
+#define nsDatePickerProxy_h
+
+#include "nsIDatePicker.h"
+
+#include "mozilla/dom/PDatePickerChild.h"
+
+class nsDatePickerProxy final : public nsIDatePicker,
+ public mozilla::dom::PDatePickerChild
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDATEPICKER
+
+ nsDatePickerProxy() {}
+
+ virtual bool RecvCancel() override;
+ virtual bool Recv__delete__(const nsString& aDate) override;
+
+private:
+ ~nsDatePickerProxy() {}
+
+ nsCOMPtr<nsIDatePickerShownCallback> mCallback;
+ nsString mTitle;
+ nsString mInitialDate;
+};
+
+#endif // nsDatePickerProxy_h
diff --git a/widget/nsDeviceContextSpecProxy.cpp b/widget/nsDeviceContextSpecProxy.cpp
new file mode 100644
index 000000000..dafbf1549
--- /dev/null
+++ b/widget/nsDeviceContextSpecProxy.cpp
@@ -0,0 +1,232 @@
+/* -*- 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 "nsIUUIDGenerator.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;
+ }
+
+ rv = mPrintSession->GetRemotePrintJob(getter_AddRefs(mRemotePrintJob));
+ if (NS_FAILED(rv) || !mRemotePrintJob) {
+ NS_WARNING("We can't print via the parent without a RemotePrintJobChild.");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
+ getter_AddRefs(mRecordingDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mUuidGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<PrintTarget>
+nsDeviceContextSpecProxy::MakePrintTarget()
+{
+ MOZ_ASSERT(mRealDeviceContextSpec);
+
+ double width, height;
+ nsresult rv = mPrintSettings->GetEffectivePageSize(&width, &height);
+ if (NS_WARN_IF(NS_FAILED(rv)) || 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::Truncate(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 DrawTargetRecording.
+ // 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();
+}
+
+nsresult
+nsDeviceContextSpecProxy::CreateUniqueTempPath(nsACString& aFilePath)
+{
+ MOZ_ASSERT(mRecordingDir);
+ MOZ_ASSERT(mUuidGenerator);
+
+ nsID uuid;
+ nsresult rv = mUuidGenerator->GenerateUUIDInPlace(&uuid);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ char uuidChars[NSID_LENGTH];
+ uuid.ToProvidedString(uuidChars);
+ mRecordingFileName.AssignASCII(uuidChars);
+
+ nsCOMPtr<nsIFile> recordingFile;
+ rv = mRecordingDir->Clone(getter_AddRefs(recordingFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = recordingFile->AppendNative(mRecordingFileName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return recordingFile->GetNativePath(aFilePath);
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage)
+{
+ nsAutoCString recordingPath;
+ nsresult rv = CreateUniqueTempPath(recordingPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mRecorder = new mozilla::gfx::DrawEventRecorderFile(recordingPath.get());
+ return mRemotePrintJob->InitializePrint(nsString(aTitle),
+ nsString(aPrintToFileName),
+ aStartPage, aEndPage);
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::EndDocument()
+{
+ Unused << mRemotePrintJob->SendFinalizePrint();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::AbortDocument()
+{
+ Unused << mRemotePrintJob->SendAbortPrint(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::BeginPage()
+{
+ // Reopen the file, if necessary, ready for the next page.
+ if (!mRecorder->IsOpen()) {
+ nsAutoCString recordingPath;
+ nsresult rv = CreateUniqueTempPath(recordingPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mRecorder->OpenNew(recordingPath.get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecProxy::EndPage()
+{
+ // Send the page recording to the parent.
+ mRecorder->Close();
+ mRemotePrintJob->ProcessPage(mRecordingFileName);
+
+ return NS_OK;
+}
diff --git a/widget/nsDeviceContextSpecProxy.h b/widget/nsDeviceContextSpecProxy.h
new file mode 100644
index 000000000..adde3cc11
--- /dev/null
+++ b/widget/nsDeviceContextSpecProxy.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 nsDeviceContextSpecProxy_h
+#define nsDeviceContextSpecProxy_h
+
+#include "nsIDeviceContextSpec.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class nsIFile;
+class nsIPrintSession;
+class nsIUUIDGenerator;
+
+namespace mozilla {
+namespace gfx {
+class DrawEventRecorderFile;
+}
+
+namespace layout {
+class RemotePrintJobChild;
+}
+}
+
+class nsDeviceContextSpecProxy final : public nsIDeviceContextSpec
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPrintSettings,
+ bool aIsPrintPreview) final;
+
+ virtual already_AddRefed<PrintTarget> MakePrintTarget() final;
+
+ NS_IMETHOD GetDrawEventRecorder(mozilla::gfx::DrawEventRecorder** aDrawEventRecorder) final;
+
+ float GetDPI() final;
+
+ float GetPrintingScale() 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() {}
+
+ nsresult CreateUniqueTempPath(nsACString& aFilePath);
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+ nsCOMPtr<nsIPrintSession> mPrintSession;
+ nsCOMPtr<nsIDeviceContextSpec> mRealDeviceContextSpec;
+ RefPtr<mozilla::layout::RemotePrintJobChild> mRemotePrintJob;
+ RefPtr<mozilla::gfx::DrawEventRecorderFile> mRecorder;
+ nsCOMPtr<nsIFile> mRecordingDir;
+ nsCOMPtr<nsIUUIDGenerator> mUuidGenerator;
+ nsCString mRecordingFileName;
+};
+
+#endif // nsDeviceContextSpecProxy_h
diff --git a/widget/nsDragServiceProxy.cpp b/widget/nsDragServiceProxy.cpp
new file mode 100644
index 000000000..7005a3e0b
--- /dev/null
+++ b/widget/nsDragServiceProxy.cpp
@@ -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/. */
+
+#include "nsDragServiceProxy.h"
+#include "nsIDocument.h"
+#include "nsISupportsPrimitives.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+
+using mozilla::ipc::Shmem;
+using mozilla::dom::TabChild;
+using mozilla::dom::OptionalShmem;
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsDragServiceProxy, nsBaseDragService)
+
+nsDragServiceProxy::nsDragServiceProxy()
+{
+}
+
+nsDragServiceProxy::~nsDragServiceProxy()
+{
+}
+
+nsresult
+nsDragServiceProxy::InvokeDragSessionImpl(nsIArray* aArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType)
+{
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(mSourceDocument);
+ NS_ENSURE_STATE(doc->GetDocShell());
+ TabChild* child = TabChild::GetFrom(doc->GetDocShell());
+ NS_ENSURE_STATE(child);
+ nsTArray<mozilla::dom::IPCDataTransfer> dataTransfers;
+ nsContentUtils::TransferablesToIPCTransferables(aArrayTransferables,
+ dataTransfers,
+ false,
+ child->Manager(),
+ nullptr);
+
+ LayoutDeviceIntRect dragRect;
+ if (mHasImage || mSelection) {
+ nsPresContext* pc;
+ RefPtr<mozilla::gfx::SourceSurface> surface;
+ DrawDrag(mSourceNode, aRegion, mScreenPosition, &dragRect, &surface, &pc);
+
+ if (surface) {
+ RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
+ surface->GetDataSurface();
+ if (dataSurface) {
+ size_t length;
+ int32_t stride;
+ Shmem surfaceData;
+ nsContentUtils::GetSurfaceData(dataSurface, &length, &stride, child,
+ &surfaceData);
+ // 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, surfaceData,
+ stride, static_cast<uint8_t>(dataSurface->GetFormat()),
+ dragRect);
+ StartDragSession();
+ return NS_OK;
+ }
+ }
+ }
+
+ mozilla::Unused << child->SendInvokeDragSession(dataTransfers, aActionType,
+ mozilla::void_t(), 0, 0, dragRect);
+ StartDragSession();
+ return NS_OK;
+}
diff --git a/widget/nsDragServiceProxy.h b/widget/nsDragServiceProxy.h
new file mode 100644
index 000000000..fcde3d25d
--- /dev/null
+++ b/widget/nsDragServiceProxy.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef NSDRAGSERVICEPROXY_H
+#define NSDRAGSERVICEPROXY_H
+
+#include "nsBaseDragService.h"
+
+class nsDragServiceProxy : public nsBaseDragService
+{
+public:
+ nsDragServiceProxy();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsBaseDragService
+ virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables,
+ nsIScriptableRegion* 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 000000000..8828c416e
--- /dev/null
+++ b/widget/nsFilePickerProxy.cpp
@@ -0,0 +1,273 @@
+/* -*- 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 "mozilla/dom/Directory.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsFilePickerProxy, nsIFilePicker)
+
+nsFilePickerProxy::nsFilePickerProxy()
+ : mSelectedType(0)
+{
+}
+
+nsFilePickerProxy::~nsFilePickerProxy()
+{
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ int16_t aMode)
+{
+ TabChild* tabChild = TabChild::GetFrom(aParent);
+ if (!tabChild) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mParent = nsPIDOMWindowOuter::From(aParent);
+
+ mMode = aMode;
+
+ NS_ADDREF_THIS();
+ tabChild->SendPFilePickerConstructor(this, nsString(aTitle), aMode);
+ 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::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;
+}
+
+NS_IMETHODIMP
+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);
+ }
+
+ SendOpen(mSelectedType, mAddToRecentDocs, mDefault, mDefaultExtension,
+ mFilters, mFilterNames, displayDirectory, mOkButtonLabel);
+
+ return NS_OK;
+}
+
+bool
+nsFilePickerProxy::Recv__delete__(const MaybeInputData& aData,
+ const int16_t& aResult)
+{
+ if (aData.type() == MaybeInputData::TInputBlobs) {
+ const InfallibleTArray<PBlobChild*>& blobs = aData.get_InputBlobs().blobsChild();
+ for (uint32_t i = 0; i < blobs.Length(); ++i) {
+ BlobChild* actor = static_cast<BlobChild*>(blobs[i]);
+ RefPtr<BlobImpl> blobImpl = actor->GetBlobImpl();
+ NS_ENSURE_TRUE(blobImpl, true);
+
+ if (!blobImpl->IsFile()) {
+ return true;
+ }
+
+ nsPIDOMWindowInner* inner =
+ mParent ? mParent->GetCurrentInnerWindow() : nullptr;
+ RefPtr<File> file = File::Create(inner, blobImpl);
+ MOZ_ASSERT(file);
+
+ OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
+ element->SetAsFile() = file;
+ }
+ } else if (aData.type() == MaybeInputData::TInputDirectory) {
+ nsCOMPtr<nsIFile> file;
+ NS_ConvertUTF16toUTF8 path(aData.get_InputDirectory().directoryPath());
+ nsresult rv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return true;
+ }
+
+ RefPtr<Directory> directory =
+ Directory::Create(mParent->GetCurrentInnerWindow(), file);
+ MOZ_ASSERT(directory);
+
+ OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
+ element->SetAsDirectory() = directory;
+ }
+
+ if (mCallback) {
+ mCallback->Done(aResult);
+ mCallback = nullptr;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetDomFileOrDirectory(nsISupports** aValue)
+{
+ *aValue = nullptr;
+ if (mFilesOrDirectories.IsEmpty()) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mFilesOrDirectories.Length() == 1);
+
+ if (mFilesOrDirectories[0].IsFile()) {
+ nsCOMPtr<nsIDOMBlob> blob = mFilesOrDirectories[0].GetAsFile().get();
+ 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 nsISimpleEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit
+ SimpleEnumerator(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
+ : mFilesOrDirectories(aFilesOrDirectories)
+ , 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<nsIDOMBlob> blob = mFilesOrDirectories[index].GetAsFile().get();
+ blob.forget(aValue);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mFilesOrDirectories[index].IsDirectory());
+ RefPtr<Directory> directory = mFilesOrDirectories[index].GetAsDirectory();
+ directory.forget(aValue);
+ return NS_OK;
+ }
+
+private:
+ ~SimpleEnumerator()
+ {}
+
+ nsTArray<mozilla::dom::OwningFileOrDirectory> mFilesOrDirectories;
+ uint32_t mIndex;
+};
+
+NS_IMPL_ISUPPORTS(SimpleEnumerator, nsISimpleEnumerator)
+
+} // namespace
+
+NS_IMETHODIMP
+nsFilePickerProxy::GetDomFileOrDirectoryEnumerator(nsISimpleEnumerator** aDomfiles)
+{
+ RefPtr<SimpleEnumerator> enumerator =
+ new SimpleEnumerator(mFilesOrDirectories);
+ enumerator.forget(aDomfiles);
+ return NS_OK;
+}
diff --git a/widget/nsFilePickerProxy.h b/widget/nsFilePickerProxy.h
new file mode 100644
index 000000000..fbffb93de
--- /dev/null
+++ b/widget/nsFilePickerProxy.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef 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 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 Show(int16_t* aReturn) override;
+ NS_IMETHOD Open(nsIFilePickerShownCallback* aCallback) override;
+
+ // PFilePickerChild
+ virtual bool
+ Recv__delete__(const MaybeInputData& aData, const int16_t& aResult) override;
+
+private:
+ ~nsFilePickerProxy();
+ void InitNative(nsIWidget*, const nsAString&) override;
+
+ nsTArray<mozilla::dom::OwningFileOrDirectory> mFilesOrDirectories;
+ nsCOMPtr<nsIFilePickerShownCallback> mCallback;
+
+ int16_t mSelectedType;
+ nsString mFile;
+ nsString mDefault;
+ nsString mDefaultExtension;
+
+ InfallibleTArray<nsString> mFilters;
+ InfallibleTArray<nsString> mFilterNames;
+};
+
+#endif // NSFILEPICKERPROXY_H
diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h
new file mode 100644
index 000000000..7a9d870d9
--- /dev/null
+++ b/widget/nsGUIEventIPC.h
@@ -0,0 +1,1348 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/IPCMessageUtils.h"
+#include "mozilla/ContentCache.h"
+#include "mozilla/GfxMessageUtils.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "InputData.h"
+
+namespace IPC
+{
+
+template<>
+struct ParamTraits<mozilla::EventMessage>
+{
+ typedef mozilla::EventMessage paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<const mozilla::EventMessageType&>(aParam));
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ mozilla::EventMessageType eventMessage = 0;
+ bool ret = ReadParam(aMsg, aIter, &eventMessage);
+ *aResult = static_cast<paramType>(eventMessage);
+ return ret;
+ }
+};
+
+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)
+ {
+ WriteParam(aMsg,
+ static_cast<mozilla::EventClassIDType>(aParam.mClass));
+ WriteParam(aMsg, aParam.mMessage);
+ WriteParam(aMsg, aParam.mRefPoint);
+ WriteParam(aMsg, aParam.mTime);
+ WriteParam(aMsg, aParam.mTimeStamp);
+ WriteParam(aMsg, aParam.mFlags);
+ }
+
+ 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->mTime) &&
+ ReadParam(aMsg, aIter, &aResult->mTimeStamp) &&
+ ReadParam(aMsg, aIter, &aResult->mFlags);
+ aResult->mClass = static_cast<mozilla::EventClassID>(eventClassID);
+ 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<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<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<mozilla::WidgetInputEvent>(aParam));
+ WriteParam(aMsg, aParam.button);
+ WriteParam(aMsg, aParam.buttons);
+ WriteParam(aMsg, aParam.pressure);
+ WriteParam(aMsg, aParam.hitCluster);
+ WriteParam(aMsg, aParam.inputSource);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ return ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetInputEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->button) &&
+ ReadParam(aMsg, aIter, &aResult->buttons) &&
+ ReadParam(aMsg, aIter, &aResult->pressure) &&
+ ReadParam(aMsg, aIter, &aResult->hitCluster) &&
+ ReadParam(aMsg, aIter, &aResult->inputSource);
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::WidgetWheelEvent>
+{
+ typedef mozilla::WidgetWheelEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<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);
+ }
+
+ 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);
+ aResult->mScrollType =
+ static_cast<mozilla::WidgetWheelEvent::ScrollType>(scrollType);
+ return rv;
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::WidgetMouseEvent>
+{
+ typedef mozilla::WidgetMouseEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<mozilla::WidgetMouseEventBase>(aParam));
+ WriteParam(aMsg, aParam.mIgnoreRootScrollFrame);
+ WriteParam(aMsg, static_cast<paramType::ReasonType>(aParam.mReason));
+ WriteParam(aMsg, static_cast<paramType::ContextMenuTriggerType>(
+ aParam.mContextMenuTrigger));
+ WriteParam(aMsg, static_cast<paramType::ExitFromType>(aParam.mExitFrom));
+ WriteParam(aMsg, aParam.mClickCount);
+ WriteParam(aMsg, aParam.pointerId);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ bool rv;
+ paramType::ReasonType reason = 0;
+ paramType::ContextMenuTriggerType contextMenuTrigger = 0;
+ paramType::ExitFromType exitFrom = 0;
+ rv = ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetMouseEventBase*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mIgnoreRootScrollFrame) &&
+ ReadParam(aMsg, aIter, &reason) &&
+ ReadParam(aMsg, aIter, &contextMenuTrigger) &&
+ ReadParam(aMsg, aIter, &exitFrom) &&
+ ReadParam(aMsg, aIter, &aResult->mClickCount) &&
+ ReadParam(aMsg, aIter, &aResult->pointerId);
+ aResult->mReason = static_cast<paramType::Reason>(reason);
+ aResult->mContextMenuTrigger =
+ static_cast<paramType::ContextMenuTrigger>(contextMenuTrigger);
+ aResult->mExitFrom = static_cast<paramType::ExitFrom>(exitFrom);
+ return rv;
+ }
+};
+
+
+template<>
+struct ParamTraits<mozilla::WidgetDragEvent>
+{
+ typedef mozilla::WidgetDragEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<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<mozilla::WidgetMouseEvent>(aParam));
+ WriteParam(aMsg, aParam.mWidth);
+ WriteParam(aMsg, aParam.mHeight);
+ WriteParam(aMsg, aParam.tiltX);
+ WriteParam(aMsg, aParam.tiltY);
+ 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->tiltX) &&
+ ReadParam(aMsg, aIter, &aResult->tiltY) &&
+ 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::WidgetKeyboardEvent>
+{
+ typedef mozilla::WidgetKeyboardEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<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.mIsChar);
+ WriteParam(aMsg, aParam.mIsRepeat);
+ WriteParam(aMsg, aParam.mIsReserved);
+ WriteParam(aMsg, aParam.mAccessKeyForwardedToChild);
+ WriteParam(aMsg, aParam.mLocation);
+ WriteParam(aMsg, aParam.mUniqueId);
+ WriteParam(aMsg, aParam.mIsSynthesizedByTIP);
+ WriteParam(aMsg,
+ static_cast<paramType::InputMethodAppStateType>
+ (aParam.mInputMethodAppState));
+#ifdef XP_MACOSX
+ WriteParam(aMsg, aParam.mNativeKeyCode);
+ WriteParam(aMsg, aParam.mNativeModifierFlags);
+ WriteParam(aMsg, aParam.mNativeCharacters);
+ WriteParam(aMsg, aParam.mNativeCharactersIgnoringModifiers);
+ WriteParam(aMsg, aParam.mPluginTextEventString);
+#endif
+ // An OS-specific native event might be attached in |mNativeKeyEvent|, but
+ // that cannot be copied across process boundaries.
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ mozilla::KeyNameIndexType keyNameIndex = 0;
+ mozilla::CodeNameIndexType codeNameIndex = 0;
+ paramType::InputMethodAppStateType inputMethodAppState = 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->mIsChar) &&
+ ReadParam(aMsg, aIter, &aResult->mIsRepeat) &&
+ ReadParam(aMsg, aIter, &aResult->mIsReserved) &&
+ ReadParam(aMsg, aIter, &aResult->mAccessKeyForwardedToChild) &&
+ ReadParam(aMsg, aIter, &aResult->mLocation) &&
+ ReadParam(aMsg, aIter, &aResult->mUniqueId) &&
+ ReadParam(aMsg, aIter, &aResult->mIsSynthesizedByTIP) &&
+ ReadParam(aMsg, aIter, &inputMethodAppState)
+#ifdef XP_MACOSX
+ && ReadParam(aMsg, aIter, &aResult->mNativeKeyCode)
+ && ReadParam(aMsg, aIter, &aResult->mNativeModifierFlags)
+ && ReadParam(aMsg, aIter, &aResult->mNativeCharacters)
+ && ReadParam(aMsg, aIter, &aResult->mNativeCharactersIgnoringModifiers)
+ && ReadParam(aMsg, aIter, &aResult->mPluginTextEventString)
+#endif
+ )
+ {
+ aResult->mKeyNameIndex = static_cast<mozilla::KeyNameIndex>(keyNameIndex);
+ aResult->mCodeNameIndex =
+ static_cast<mozilla::CodeNameIndex>(codeNameIndex);
+ aResult->mNativeKeyEvent = nullptr;
+ aResult->mInputMethodAppState =
+ static_cast<paramType::InputMethodAppState>(inputMethodAppState);
+ return true;
+ }
+ return false;
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::InternalBeforeAfterKeyboardEvent>
+{
+ typedef mozilla::InternalBeforeAfterKeyboardEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<mozilla::WidgetKeyboardEvent>(aParam));
+ WriteParam(aMsg, aParam.mEmbeddedCancelled.IsNull());
+ WriteParam(aMsg, aParam.mEmbeddedCancelled.Value());
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ bool isNull;
+ bool value;
+ bool rv =
+ ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetKeyboardEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &isNull) &&
+ ReadParam(aMsg, aIter, &value);
+
+ aResult->mEmbeddedCancelled = Nullable<bool>();
+ if (rv && !isNull) {
+ aResult->mEmbeddedCancelled.SetValue(value);
+ }
+
+ return rv;
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::TextRangeStyle>
+{
+ typedef mozilla::TextRangeStyle paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mDefinedStyles);
+ WriteParam(aMsg, 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)
+ {
+ return ReadParam(aMsg, aIter, &aResult->mDefinedStyles) &&
+ ReadParam(aMsg, aIter, &aResult->mLineStyle) &&
+ ReadParam(aMsg, aIter, &aResult->mIsBoldLine) &&
+ ReadParam(aMsg, aIter, &aResult->mForegroundColor) &&
+ ReadParam(aMsg, aIter, &aResult->mBackgroundColor) &&
+ ReadParam(aMsg, aIter, &aResult->mUnderlineColor);
+ }
+};
+
+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<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::WidgetQueryContentEvent::Input>
+{
+ typedef mozilla::WidgetQueryContentEvent::Input paramType;
+ typedef mozilla::WidgetQueryContentEvent event;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mOffset);
+ WriteParam(aMsg, aParam.mLength);
+ WriteParam(aMsg, mozilla::ToRawSelectionType(aParam.mSelectionType));
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ mozilla::RawSelectionType rawSelectionType = 0;
+ bool ok = ReadParam(aMsg, aIter, &aResult->mOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mLength) &&
+ ReadParam(aMsg, aIter, &rawSelectionType);
+ aResult->mSelectionType = mozilla::ToSelectionType(rawSelectionType);
+ return ok;
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::WidgetQueryContentEvent>
+{
+ typedef mozilla::WidgetQueryContentEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<mozilla::WidgetGUIEvent>(aParam));
+ WriteParam(aMsg, aParam.mSucceeded);
+ WriteParam(aMsg, aParam.mUseNativeLineBreak);
+ WriteParam(aMsg, aParam.mWithFontRanges);
+ WriteParam(aMsg, aParam.mInput);
+ WriteParam(aMsg, aParam.mReply.mOffset);
+ WriteParam(aMsg, aParam.mReply.mTentativeCaretOffset);
+ WriteParam(aMsg, aParam.mReply.mString);
+ WriteParam(aMsg, aParam.mReply.mRect);
+ WriteParam(aMsg, aParam.mReply.mReversed);
+ WriteParam(aMsg, aParam.mReply.mHasSelection);
+ WriteParam(aMsg, aParam.mReply.mWidgetIsHit);
+ WriteParam(aMsg, aParam.mReply.mFontRanges);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ return ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetGUIEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mSucceeded) &&
+ ReadParam(aMsg, aIter, &aResult->mUseNativeLineBreak) &&
+ ReadParam(aMsg, aIter, &aResult->mWithFontRanges) &&
+ ReadParam(aMsg, aIter, &aResult->mInput) &&
+ ReadParam(aMsg, aIter, &aResult->mReply.mOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mReply.mTentativeCaretOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mReply.mString) &&
+ ReadParam(aMsg, aIter, &aResult->mReply.mRect) &&
+ ReadParam(aMsg, aIter, &aResult->mReply.mReversed) &&
+ ReadParam(aMsg, aIter, &aResult->mReply.mHasSelection) &&
+ ReadParam(aMsg, aIter, &aResult->mReply.mWidgetIsHit) &&
+ ReadParam(aMsg, aIter, &aResult->mReply.mFontRanges);
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::WidgetSelectionEvent>
+{
+ typedef mozilla::WidgetSelectionEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<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<nsIMEUpdatePreference>
+{
+ typedef nsIMEUpdatePreference 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::WidgetPluginEvent>
+{
+ typedef mozilla::WidgetPluginEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<mozilla::WidgetGUIEvent>(aParam));
+ WriteParam(aMsg, aParam.mRetargetToFocusedDocument);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ return ReadParam(aMsg, aIter,
+ static_cast<mozilla::WidgetGUIEvent*>(aResult)) &&
+ ReadParam(aMsg, aIter, &aResult->mRetargetToFocusedDocument);
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::WritingMode>
+{
+ typedef mozilla::WritingMode paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.mWritingMode);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ return ReadParam(aMsg, aIter, &aResult->mWritingMode);
+ }
+};
+
+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.mAnchor);
+ WriteParam(aMsg, aParam.mSelection.mFocus);
+ WriteParam(aMsg, aParam.mSelection.mWritingMode);
+ WriteParam(aMsg, aParam.mSelection.mAnchorCharRects[0]);
+ WriteParam(aMsg, aParam.mSelection.mAnchorCharRects[1]);
+ WriteParam(aMsg, aParam.mSelection.mFocusCharRects[0]);
+ WriteParam(aMsg, aParam.mSelection.mFocusCharRects[1]);
+ WriteParam(aMsg, aParam.mSelection.mRect);
+ WriteParam(aMsg, aParam.mFirstCharRect);
+ WriteParam(aMsg, aParam.mCaret.mOffset);
+ WriteParam(aMsg, aParam.mCaret.mRect);
+ WriteParam(aMsg, aParam.mTextRectArray.mStart);
+ WriteParam(aMsg, aParam.mTextRectArray.mRects);
+ 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.mAnchor) &&
+ ReadParam(aMsg, aIter, &aResult->mSelection.mFocus) &&
+ ReadParam(aMsg, aIter, &aResult->mSelection.mWritingMode) &&
+ ReadParam(aMsg, aIter, &aResult->mSelection.mAnchorCharRects[0]) &&
+ ReadParam(aMsg, aIter, &aResult->mSelection.mAnchorCharRects[1]) &&
+ ReadParam(aMsg, aIter, &aResult->mSelection.mFocusCharRects[0]) &&
+ ReadParam(aMsg, aIter, &aResult->mSelection.mFocusCharRects[1]) &&
+ ReadParam(aMsg, aIter, &aResult->mSelection.mRect) &&
+ ReadParam(aMsg, aIter, &aResult->mFirstCharRect) &&
+ ReadParam(aMsg, aIter, &aResult->mCaret.mOffset) &&
+ ReadParam(aMsg, aIter, &aResult->mCaret.mRect) &&
+ ReadParam(aMsg, aIter, &aResult->mTextRectArray.mStart) &&
+ ReadParam(aMsg, aIter, &aResult->mTextRectArray.mRects) &&
+ 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 ContiguousEnumSerializer<
+ mozilla::InputType,
+ mozilla::InputType::MULTITOUCH_INPUT,
+ mozilla::InputType::SENTINEL_INPUT>
+{};
+
+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);
+ }
+
+ 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);
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::SingleTouchData>
+{
+ typedef mozilla::SingleTouchData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ 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->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 ContiguousEnumSerializer<
+ mozilla::MultiTouchInput::MultiTouchType,
+ mozilla::MultiTouchInput::MultiTouchType::MULTITOUCH_START,
+ mozilla::MultiTouchInput::MultiTouchType::MULTITOUCH_SENTINEL>
+{};
+
+template<>
+struct ParamTraits<mozilla::MultiTouchInput>
+{
+ typedef mozilla::MultiTouchInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<mozilla::InputData>(aParam));
+ WriteParam(aMsg, aParam.mType);
+ WriteParam(aMsg, aParam.mTouches);
+ 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->mTouches) &&
+ ReadParam(aMsg, aIter, &aResult->mHandledByAPZ);
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::MouseInput::MouseType>
+ : public ContiguousEnumSerializer<
+ mozilla::MouseInput::MouseType,
+ mozilla::MouseInput::MouseType::MOUSE_NONE,
+ mozilla::MouseInput::MouseType::MOUSE_SENTINEL>
+{};
+
+template<>
+struct ParamTraits<mozilla::MouseInput::ButtonType>
+ : public ContiguousEnumSerializer<
+ mozilla::MouseInput::ButtonType,
+ mozilla::MouseInput::ButtonType::LEFT_BUTTON,
+ mozilla::MouseInput::ButtonType::BUTTON_SENTINEL>
+{};
+
+template<>
+struct ParamTraits<mozilla::MouseInput>
+{
+ typedef mozilla::MouseInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<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 ContiguousEnumSerializer<
+ mozilla::PanGestureInput::PanGestureType,
+ mozilla::PanGestureInput::PanGestureType::PANGESTURE_MAYSTART,
+ mozilla::PanGestureInput::PanGestureType::PANGESTURE_SENTINEL>
+{};
+
+template<>
+struct ParamTraits<mozilla::PanGestureInput>
+{
+ typedef mozilla::PanGestureInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<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.mHandledByAPZ);
+ WriteParam(aMsg, aParam.mFollowedByMomentum);
+ WriteParam(aMsg, aParam.mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection);
+ }
+
+ 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->mHandledByAPZ) &&
+ ReadParam(aMsg, aIter, &aResult->mFollowedByMomentum) &&
+ ReadParam(aMsg, aIter, &aResult->mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection);
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::PinchGestureInput::PinchGestureType>
+ : public ContiguousEnumSerializer<
+ mozilla::PinchGestureInput::PinchGestureType,
+ mozilla::PinchGestureInput::PinchGestureType::PINCHGESTURE_START,
+ mozilla::PinchGestureInput::PinchGestureType::PINCHGESTURE_SENTINEL>
+{};
+
+template<>
+struct ParamTraits<mozilla::PinchGestureInput>
+{
+ typedef mozilla::PinchGestureInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<mozilla::InputData>(aParam));
+ WriteParam(aMsg, aParam.mType);
+ WriteParam(aMsg, aParam.mFocusPoint);
+ WriteParam(aMsg, aParam.mLocalFocusPoint);
+ WriteParam(aMsg, aParam.mCurrentSpan);
+ WriteParam(aMsg, aParam.mPreviousSpan);
+ }
+
+ 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->mFocusPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mLocalFocusPoint) &&
+ ReadParam(aMsg, aIter, &aResult->mCurrentSpan) &&
+ ReadParam(aMsg, aIter, &aResult->mPreviousSpan);
+ }
+};
+
+template<>
+struct ParamTraits<mozilla::TapGestureInput::TapGestureType>
+ : public ContiguousEnumSerializer<
+ mozilla::TapGestureInput::TapGestureType,
+ mozilla::TapGestureInput::TapGestureType::TAPGESTURE_LONG,
+ mozilla::TapGestureInput::TapGestureType::TAPGESTURE_SENTINEL>
+{};
+
+template<>
+struct ParamTraits<mozilla::TapGestureInput>
+{
+ typedef mozilla::TapGestureInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<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 ContiguousEnumSerializer<
+ mozilla::ScrollWheelInput::ScrollDeltaType,
+ mozilla::ScrollWheelInput::ScrollDeltaType::SCROLLDELTA_LINE,
+ mozilla::ScrollWheelInput::ScrollDeltaType::SCROLLDELTA_SENTINEL>
+{};
+
+template<>
+struct ParamTraits<mozilla::ScrollWheelInput::ScrollMode>
+ : public ContiguousEnumSerializer<
+ mozilla::ScrollWheelInput::ScrollMode,
+ mozilla::ScrollWheelInput::ScrollMode::SCROLLMODE_INSTANT,
+ mozilla::ScrollWheelInput::ScrollMode::SCROLLMODE_SENTINEL>
+{};
+
+template<>
+struct ParamTraits<mozilla::ScrollWheelInput>
+{
+ typedef mozilla::ScrollWheelInput paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, static_cast<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);
+ }
+
+ 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);
+ }
+};
+
+} // namespace IPC
+
+#endif // nsGUIEventIPC_h__
diff --git a/widget/nsHTMLFormatConverter.cpp b/widget/nsHTMLFormatConverter.cpp
new file mode 100644
index 000000000..6c8191927
--- /dev/null
+++ b/widget/nsHTMLFormatConverter.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+
+#include "nsITransferable.h" // for mime defs, this is BAD
+
+// HTML convertor stuff
+#include "nsPrimitiveHelpers.h"
+#include "nsIDocumentEncoder.h"
+#include "nsContentUtils.h"
+
+nsHTMLFormatConverter::nsHTMLFormatConverter()
+{
+}
+
+nsHTMLFormatConverter::~nsHTMLFormatConverter()
+{
+}
+
+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.
+//
+// Flavors (strings) are wrapped in a primitive object so that JavaScript can
+// access them easily via XPConnect.
+//
+NS_IMETHODIMP
+nsHTMLFormatConverter::GetInputDataFlavors(nsIArray **_retval)
+{
+ if ( !_retval )
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+ nsresult rv = AddFlavorToList ( array, kHTMLMime );
+
+ array.forget(_retval);
+ return rv;
+
+} // GetInputDataFlavors
+
+
+//
+// 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.
+//
+// Flavors (strings) are wrapped in a primitive object so that JavaScript can
+// access them easily via XPConnect.
+//
+NS_IMETHODIMP
+nsHTMLFormatConverter::GetOutputDataFlavors(nsIArray **_retval)
+{
+ if ( !_retval )
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+ nsresult rv = AddFlavorToList ( array, kHTMLMime );
+ if ( NS_FAILED(rv) )
+ return rv;
+ rv = AddFlavorToList ( array, kUnicodeMime );
+ if ( NS_FAILED(rv) )
+ return rv;
+
+ array.forget(_retval);
+ return rv;
+
+} // GetOutputDataFlavors
+
+
+//
+// AddFlavorToList
+//
+// Convenience routine for adding a flavor wrapped in an nsISupportsCString object
+// to a list
+//
+nsresult
+nsHTMLFormatConverter :: AddFlavorToList ( nsCOMPtr<nsIMutableArray>& inList, const char* inFlavor )
+{
+ nsresult rv;
+
+ nsCOMPtr<nsISupportsCString> dataFlavor =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ if ( dataFlavor ) {
+ dataFlavor->SetData ( nsDependentCString(inFlavor) );
+ // add to list as an nsISupports so the correct interface gets the addref
+ // in AppendElement()
+ nsCOMPtr<nsISupports> genericFlavor ( do_QueryInterface(dataFlavor) );
+ inList->AppendElement ( genericFlavor, /*weak =*/ false);
+ }
+ return rv;
+
+} // AddFlavorToList
+
+
+//
+// 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, uint32_t aDataLen,
+ const char *aToDataFlavor, nsISupports **aToData, uint32_t *aDataToLen)
+{
+ if ( !aToData || !aDataToLen )
+ return NS_ERROR_INVALID_ARG;
+
+ nsresult rv = NS_OK;
+ *aToData = nullptr;
+ *aDataToLen = 0;
+
+ 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.get(), dataStr.get(), dataLen, aToData );
+ if ( *aToData )
+ *aDataToLen = dataLen;
+ } else {
+ nsAutoString outStr;
+ res = ConvertFromHTMLToUnicode(dataStr, outStr);
+ if (NS_SUCCEEDED(res)) {
+ int32_t dataLen = outStr.Length() * 2;
+ nsPrimitiveHelpers::CreatePrimitiveForData ( toFlavor.get(), outStr.get(), dataLen, aToData );
+ if ( *aToData )
+ *aDataToLen = dataLen;
+ }
+ }
+ } // 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.get(), outStr.get(), dataLen, aToData );
+ if ( *aToData )
+ *aDataToLen = dataLen;
+ }
+ } // 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 000000000..8356b7313
--- /dev/null
+++ b/widget/nsHTMLFormatConverter.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 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();
+
+ nsresult AddFlavorToList ( nsCOMPtr<nsIMutableArray>& inList, const char* inFlavor ) ;
+
+ 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 000000000..c21c4d107
--- /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 000000000..05d15f7ac
--- /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 000000000..7da3fe1dc
--- /dev/null
+++ b/widget/nsIBaseWindow.idl
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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. Most likely
+ due to you calling it after create() has 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);
+
+ /*
+ Tells the window that intialization and setup is complete. When this is
+ called the window can actually create itself based on the setup
+ information handed to it.
+
+ @return NS_OK - Creation was successfull.
+ NS_ERROR_UNEXPECTED - This call was unexpected at this time.
+ Perhaps create() had already been called or not all
+ required initialization had been done.
+ */
+ void create();
+
+ /*
+ 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 a DOMString.
+
+ @return DOMString in hex format with "0x" prepended, or empty string if
+ mainWidget undefined
+
+ @throws NS_ERROR_NOT_IMPLEMENTED for non-XULWindows
+ */
+ readonly attribute DOMString 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 wstring title;
+};
diff --git a/widget/nsIBidiKeyboard.idl b/widget/nsIBidiKeyboard.idl
new file mode 100644
index 000000000..fc68d1ae8
--- /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 000000000..aaf97a5ec
--- /dev/null
+++ b/widget/nsIClipboard.idl
@@ -0,0 +1,92 @@
+/* -*- 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 aLength The length of the aFlavorList.
+ * @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 ( [array, size_is(aLength)] in string aFlavorList,
+ in unsigned long aLength,
+ 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/nsIClipboardDragDropHookList.idl b/widget/nsIClipboardDragDropHookList.idl
new file mode 100644
index 000000000..1f6231b82
--- /dev/null
+++ b/widget/nsIClipboardDragDropHookList.idl
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 nsIClipboardDragDropHooks;
+interface nsISimpleEnumerator;
+
+
+/**
+ * Please note that the following api is not intended for embedders;
+ * it is intended as an internal (to gecko). Embedders can indirectly
+ * call these by sending commands (see description in
+ * nsIClipboardDragDropHooks.idl).
+ *
+ * Internal gecko usage is accomplished by calling get_Interface on a
+ * docshell.
+ */
+
+
+// 876A2015-6B66-11D7-8F18-0003938A9D96
+[scriptable,uuid(876A2015-6B66-11D7-8F18-0003938A9D96)]
+interface nsIClipboardDragDropHookList : nsISupports
+{
+ /**
+ * Add a hook to list.
+ * @param aHooks implementation of hooks
+ */
+ void addClipboardDragDropHooks(in nsIClipboardDragDropHooks aHooks);
+
+ /**
+ * Remove a hook from list (note if this implementation is not present
+ * in the list then removal will be ignored).
+ * @param aHooks implementation of hooks
+ */
+ void removeClipboardDragDropHooks(in nsIClipboardDragDropHooks aHooks);
+
+ /**
+ * Gets an enumerator for all hooks which have been added.
+ * @return nsISimpleEnumerator for nsIClipboardDragDropHooks
+ */
+ nsISimpleEnumerator getHookEnumerator();
+};
+
diff --git a/widget/nsIClipboardDragDropHooks.idl b/widget/nsIClipboardDragDropHooks.idl
new file mode 100644
index 000000000..0810ff70f
--- /dev/null
+++ b/widget/nsIClipboardDragDropHooks.idl
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 nsITransferable;
+interface nsIDragSession;
+interface nsIDOMEvent;
+
+
+/**
+ * Interfaces for overriding the built-in drag, drop, copy, and paste
+ * implementations in the content area and editors. Use this to do things
+ * such as prevent a drag from starting, adding or removing
+ * data and flavors, or preventing the drop.
+ *
+ * Embedders who want to have these hooks made available should implement
+ * nsIClipboardDragDropHooks and use the command manager to send the
+ * appropriate commands with these parameters/settings:
+ * command: cmd_clipboardDragDropHook
+ *
+ * params value type possible values
+ * "addhook" isupports nsIClipboardDragDropHooks as nsISupports
+ * "removehook" isupports nsIClipboardDragDropHooks as nsISupports
+ *
+ * Notes:
+ * * Overrides/hooks need to be added to each window (as appropriate).
+ * Adding them to the first window does not enable them for every window.
+ * * If more than one implementation is set for a window, the hooks will be
+ * called in the order they are added.
+ * * Adding the same hook to the same window will not add a second call.
+ * Each hook can only be called once per user action/api.
+ * * Not all hooks are guaranteed to be called. If there are multiple hooks
+ * set for a window, any of them has an opportunity to cancel the action
+ * so no further processing will occur.
+ * * If any errors occur (without setting the boolean result) the default
+ * action will occur.
+ * * AllowDrop will be called MANY times during drag so ensure that it is
+ * efficient.
+ */
+
+
+[scriptable,uuid(e03e6c5e-0d84-4c0b-8739-e6b8d51922de)]
+interface nsIClipboardDragDropHooks : nsISupports
+{
+ /**
+ * Prevents the drag from starting
+ *
+ * @param event DOM event (drag gesture)
+ *
+ * @return TRUE drag can proceed
+ * @return FALSE drag is cancelled, does not go to OS
+ */
+ boolean allowStartDrag(in nsIDOMEvent event);
+
+ /**
+ * Tells gecko whether a drop is allowed on this content area
+ *
+ * @param event DOM event (drag over)
+ * @param session the drag session from which client can get
+ * the flavors present or the actual data
+ *
+ * @return TRUE indicates to OS that if a drop does happen on this
+ * browser, it will be accepted.
+ * @return FALSE indicates to OS drop is not allowed. On win32, this
+ * will change the cursor to "reject".
+ */
+ boolean allowDrop(in nsIDOMEvent event, in nsIDragSession session);
+
+ /**
+ * Alter the flavors or data presented to the OS
+ * Used for drag and copy actions
+ * Because this can be called many times, it is highly recommended
+ * that the implementation be very efficient so user feedback is
+ * not negatively impacted.
+ *
+ * @param event DOM event (drag drop); null if triggered by copy.
+ * @param trans the transferable holding the list of flavors
+ * and the data for each flavor
+ *
+ * @return TRUE copy/drag can proceed
+ * @return FALSE copy/drag is cancelled, does not go to OS
+ */
+ boolean onCopyOrDrag(in nsIDOMEvent aEvent, in nsITransferable trans);
+
+ /**
+ * Provide an alternative action to the built-in behavior when
+ * something is dropped on the browser or in an editor
+ *
+ * @param event DOM event (drag drop); null if triggered by paste.
+ * @param trans the transferable holding the list of flavors
+ * and the data for each flavor
+ *
+ * @return TRUE action was handled, do not perform built-in
+ * behavior
+ * @return FALSE action was not overridden, do built-in behavior
+ */
+ boolean onPasteOrDrop(in nsIDOMEvent event, in nsITransferable trans);
+};
+
diff --git a/widget/nsIClipboardHelper.idl b/widget/nsIClipboardHelper.idl
new file mode 100644
index 000000000..427105abb
--- /dev/null
+++ b/widget/nsIClipboardHelper.idl
@@ -0,0 +1,38 @@
+/* -*- 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
+%}
+
+interface nsIDOMDocument;
+
+/**
+ * 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 000000000..00a2258aa
--- /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 000000000..24b128e1b
--- /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/nsIDatePicker.idl b/widget/nsIDatePicker.idl
new file mode 100644
index 000000000..d6be60c95
--- /dev/null
+++ b/widget/nsIDatePicker.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"
+
+interface mozIDOMWindowProxy;
+
+[scriptable, uuid(13388a28-1b0b-4218-a31b-588f7a4ec26c)]
+interface nsIDatePickerShownCallback : nsISupports
+{
+ /**
+ * Callback called when the user selects cancel in the date picker
+ * This callback can not be called after done() is called.
+ */
+ void cancel();
+
+ /**
+ * Callback called when the user has finished selecting the date
+ *
+ * @param date The new selected date value following the format "YYYY-MM-DD"
+ */
+ void done(in AString date);
+};
+
+[scriptable, uuid(7becfc64-966b-4d53-87d2-9161f36bd3b3)]
+interface nsIDatePicker : nsISupports
+{
+ /**
+ * Initialize the date picker widget. The date picker will not be shown until
+ * open() is called.
+ * If the initialDate parameter does not follow the format "YYYY-MM-DD" then
+ * the behavior will be unspecified.
+ *
+ * @param parent nsIDOMWindow parent. This dialog will be dependent
+ * on this parent. parent may be null.
+ * @param title The title for the date picker widget.
+ * @param initialDate The date to show when the widget is opened. The
+ * parameter has to follow the format "YYYY-MM-DD"
+ */
+ void init(in mozIDOMWindowProxy parent, in AString title, in AString initialDate);
+
+ /**
+ * Opens the date dialog asynchrounously.
+ * The results are provided via the callback object.
+ */
+ void open(in nsIDatePickerShownCallback callback);
+};
diff --git a/widget/nsIDeviceContextSpec.h b/widget/nsIDeviceContextSpec.h
new file mode 100644
index 000000000..fc0fd2c12
--- /dev/null
+++ b/widget/nsIDeviceContextSpec.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsISupports.h"
+
+class nsIWidget;
+class nsIPrintSettings;
+
+namespace mozilla {
+namespace gfx{
+class DrawEventRecorder;
+class PrintTarget;
+}
+}
+
+#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 72.0f; }
+
+ /**
+ * Override to return something other than the default.
+ *
+ * @return the printing scale to be applied to the context for printing.
+ */
+ virtual float GetPrintingScale() { return 1.0f; }
+
+ 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 000000000..69d5064e4
--- /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 000000000..c7779bc5c
--- /dev/null
+++ b/widget/nsIDragService.idl
@@ -0,0 +1,152 @@
+/* -*- 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 "nsIScriptableRegion.idl"
+#include "nsIContentPolicyBase.idl"
+
+interface nsIDOMNode;
+interface nsIDOMDragEvent;
+interface nsIDOMDataTransfer;
+interface nsISelection;
+
+%{C++
+#include "mozilla/EventForwards.h"
+
+namespace mozilla {
+namespace dom {
+class ContentParent;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ptr] native ContentParentPtr(mozilla::dom::ContentParent);
+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 aTransferables - an array of transferables to be dragged
+ * @param aRegion - a region containing rectangles for cursor feedback,
+ * in window coordinates.
+ * @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)
+ */
+ void invokeDragSession (in nsIDOMNode aDOMNode,
+ in nsIArray aTransferables,
+ in nsIScriptableRegion aRegion,
+ 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.
+ */
+ void invokeDragSessionWithImage(in nsIDOMNode aDOMNode,
+ in nsIArray aTransferableArray,
+ in nsIScriptableRegion aRegion,
+ in unsigned long aActionType,
+ in nsIDOMNode aImage,
+ in long aImageX,
+ in long aImageY,
+ in nsIDOMDragEvent aDragEvent,
+ in nsIDOMDataTransfer 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.
+ */
+ void invokeDragSessionWithSelection(in nsISelection aSelection,
+ in nsIArray aTransferableArray,
+ in unsigned long aActionType,
+ in nsIDOMDragEvent aDragEvent,
+ in nsIDOMDataTransfer 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 ( ) ;
+
+ /**
+ * 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.
+ */
+ void endDragSession ( in boolean aDoneDrag ) ;
+
+ /**
+ * Fire a drag event at the source of the drag
+ */
+ [noscript] void fireDragEventAtSource(in EventMessage aEventMessage);
+
+ /**
+ * Increase/decrease dragging suppress level by one.
+ * If level is greater than one, dragging is disabled.
+ */
+ 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);
+};
+
+
+%{ C++
+
+%}
diff --git a/widget/nsIDragSession.idl b/widget/nsIDragSession.idl
new file mode 100644
index 000000000..6b9f603a1
--- /dev/null
+++ b/widget/nsIDragSession.idl
@@ -0,0 +1,97 @@
+/* -*- 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"
+%}
+
+native nsSize (nsSize);
+
+
+interface nsIDOMDocument;
+interface nsIDOMNode;
+interface nsIDOMDataTransfer;
+
+[scriptable, 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;
+
+ /**
+ * Sets the current width and height of the drag target area.
+ * It will contain the current size of the Frame that the drag is currently in
+ */
+ [noscript] attribute nsSize targetSize;
+
+ /**
+ * 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.
+ */
+ readonly attribute nsIDOMDocument 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 nsIDOMNode sourceNode;
+
+ /**
+ * The data transfer object for the current drag.
+ */
+ attribute nsIDOMDataTransfer dataTransfer;
+
+ /**
+ * 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();
+};
+
+
+%{ C++
+
+%}
diff --git a/widget/nsIFilePicker.idl b/widget/nsIFilePicker.idl
new file mode 100644
index 000000000..62efe93ec
--- /dev/null
+++ b/widget/nsIFilePicker.idl
@@ -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/. */
+
+#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
+ 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
+
+ /**
+ * 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);
+
+ /**
+ * 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
+ *
+ * @param displayDirectory the name of the directory
+ *
+ */
+ attribute nsIFile displayDirectory;
+
+
+ /**
+ * 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;
+
+ /**
+ * Show File Dialog. The dialog is displayed modally.
+ *
+ * @return returnOK if the user selects OK, returnCancel if the user selects cancel
+ *
+ */
+ [deprecated] short show();
+
+
+ /**
+ * 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;
+};
diff --git a/widget/nsIFormatConverter.idl b/widget/nsIFormatConverter.idl
new file mode 100644
index 000000000..1a82a1cba
--- /dev/null
+++ b/widget/nsIFormatConverter.idl
@@ -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 "nsISupports.idl"
+#include "nsIArray.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).
+ */
+ nsIArray 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
+ */
+ nsIArray 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 unsigned long aDataLen,
+ in string aToDataFlavor, out nsISupports aToData, out unsigned long aDataToLen ) ;
+
+};
+
+
+%{ C++
+
+%}
diff --git a/widget/nsIGfxInfo.idl b/widget/nsIGfxInfo.idl
new file mode 100644
index 000000000..b68bd21ee
--- /dev/null
+++ b/widget/nsIGfxInfo.idl
@@ -0,0 +1,249 @@
+/* -*- 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, uuid(1accd618-4c80-4703-9d29-ecf257d397c8)]
+interface nsIGfxInfo : nsISupports
+{
+ /*
+ * These are win32-specific
+ */
+ readonly attribute boolean D2DEnabled;
+ readonly attribute boolean DWriteEnabled;
+ readonly attribute DOMString DWriteVersion;
+ readonly attribute DOMString cleartypeParameters;
+
+ /*
+ * These are valid across all platforms.
+ */
+ readonly attribute DOMString ContentBackend;
+
+ // XXX: Switch to a list of devices, rather than explicitly numbering them.
+
+ /**
+ * The name of the display adapter.
+ */
+ readonly attribute DOMString adapterDescription;
+ readonly attribute DOMString adapterDescription2;
+
+ readonly attribute DOMString adapterDriver;
+ readonly attribute DOMString adapterDriver2;
+
+ /* These types are inspired by DXGI_ADAPTER_DESC */
+ readonly attribute DOMString adapterVendorID;
+ readonly attribute DOMString adapterVendorID2;
+
+ readonly attribute DOMString adapterDeviceID;
+ readonly attribute DOMString adapterDeviceID2;
+
+ readonly attribute DOMString adapterSubsysID;
+ readonly attribute DOMString adapterSubsysID2;
+
+ /**
+ * The amount of RAM in MB in the display adapter.
+ */
+ readonly attribute DOMString adapterRAM;
+ readonly attribute DOMString adapterRAM2;
+
+ readonly attribute DOMString adapterDriverVersion;
+ readonly attribute DOMString adapterDriverVersion2;
+
+ readonly attribute DOMString adapterDriverDate;
+ readonly attribute DOMString adapterDriverDate2;
+
+ readonly attribute boolean isGPU2Active;
+
+ /**
+ * 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();
+
+ void getFailures(
+ out unsigned long failureCount,
+ [optional, array, size_is(failureCount)] out long indices,
+ [retval, array, size_is(failureCount)] out string failures);
+
+ [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 */
+ /* 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;
+ /* Whether WebGL antialiasing is supported. */
+ const long FEATURE_WEBGL_MSAA = 8;
+ /* Whether Stagefright is supported, starting in 17. */
+ const long FEATURE_STAGEFRIGHT = 9;
+ /* Whether Webrtc Hardware acceleration is supported, starting in 31. */
+ const long FEATURE_WEBRTC_HW_ACCELERATION = 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. */
+ const long FEATURE_VP8_HW_DECODE = 17;
+ /* Whether hardware VP9 decoding is supported, starting in 48. */
+ const long FEATURE_VP9_HW_DECODE = 18;
+ /* Whether NV_dx_interop2 is supported, starting in 50. */
+ const long FEATURE_DX_INTEROP2 = 19;
+
+ /*
+ * 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;
+
+ /**
+ * 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.
+ */
+ DOMString getFeatureSuggestedDriverVersion(in long aFeature);
+
+ /**
+ * WebGL info; valid params are "full-renderer", "vendor", "renderer", "version",
+ * "shading_language_version", "extensions". These return info from
+ * underlying GL impl that's used to implement WebGL.
+ */
+ DOMString getWebGLParameter(in DOMString aParam);
+
+ // only useful on X11
+ [noscript, notxpcom] void GetData();
+
+ [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.
+ // // "blacklisted" - Blocked due to a blacklist restriction.
+ // // "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", "d3d9video", "glcontext",
+ // // or "d3d11video".
+ // "type": "<identifier>",
+ //
+ // // Preference that must be deleted/reset to retrigger the guard.
+ // "prefName": "<preference>",
+ // }
+ // ]
+ [implicit_jscontext]
+ jsval getActiveCrashGuards();
+};
+
diff --git a/widget/nsIGfxInfoDebug.idl b/widget/nsIGfxInfoDebug.idl
new file mode 100644
index 000000000..fab30c4e9
--- /dev/null
+++ b/widget/nsIGfxInfoDebug.idl
@@ -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/. */
+
+#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 DOMString aVendorID);
+ void spoofDeviceID(in DOMString aDeviceID);
+
+ void spoofDriverVersion(in DOMString aDriverVersion);
+
+ void spoofOSVersion(in unsigned long aVersion);
+};
diff --git a/widget/nsIIdleService.idl b/widget/nsIIdleService.idl
new file mode 100644
index 000000000..d2db6ad14
--- /dev/null
+++ b/widget/nsIIdleService.idl
@@ -0,0 +1,78 @@
+/* -*- 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 nsIIdleService : 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
+ * nsIIdleService 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);
+};
+
+%{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/nsIIdleServiceInternal.idl b/widget/nsIIdleServiceInternal.idl
new file mode 100644
index 000000000..350766ec3
--- /dev/null
+++ b/widget/nsIIdleServiceInternal.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 "nsIIdleService.idl"
+
+[scriptable, uuid(7b89a2e7-ed12-42e0-b86d-4984239abd7b)]
+interface nsIIdleServiceInternal : nsIIdleService
+{
+ /**
+ * "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/nsIJumpListBuilder.idl b/widget/nsIJumpListBuilder.idl
new file mode 100644
index 000000000..35846f005
--- /dev/null
+++ b/widget/nsIJumpListBuilder.idl
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIMutableArray;
+
+[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 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.
+ *
+ * @param removedItems
+ * A list of items that were removed by the user since the last commit.
+ *
+ * @returns true if the operation completed successfully.
+ */
+ boolean initListBuild(in nsIMutableArray removedItems);
+
+ /**
+ * 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.
+ *
+ * @returns true if the operation completed successfully.
+ */
+ boolean commitListBuild();
+
+ /**
+ * 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 000000000..e7fcbb3fb
--- /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 000000000..8b5e85e97
--- /dev/null
+++ b/widget/nsIKeyEventInPluginCallback.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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 000000000..5783e9c0b
--- /dev/null
+++ b/widget/nsIMacDockSupport.idl
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIStandaloneNativeMenu;
+
+/**
+ * Allow applications to interface with the Mac OS X Dock.
+ *
+ * Applications may indicate progress on their Dock icon. Only one such
+ * progress indicator is available to the entire application.
+ */
+
+[scriptable, uuid(8BE66B0C-5F71-4B74-98CF-6C2551B999B1)]
+interface nsIMacDockSupport : nsISupports
+{
+ /**
+ * Menu to use for application-specific dock menu items.
+ */
+ attribute nsIStandaloneNativeMenu dockMenu;
+
+ /**
+ * Activate the application. This should be used by an application to
+ * activate itself when a dock menu is selected as selection of a dock menu
+ * item does not automatically activate the application.
+ *
+ * @param aIgnoreOtherApplications If false, the application is activated
+ * only if no other application is currently active. If true, the
+ * application activates regardless.
+ */
+ void activateApplication(in boolean aIgnoreOtherApplications);
+
+ /**
+ * Text used to badge the dock tile.
+ */
+ attribute AString badgeText;
+};
diff --git a/widget/nsIMacWebAppUtils.idl b/widget/nsIMacWebAppUtils.idl
new file mode 100644
index 000000000..4d570a8bf
--- /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 000000000..405763fe3
--- /dev/null
+++ b/widget/nsINativeMenuService.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 nsINativeMenuService_h_
+#define nsINativeMenuService_h_
+
+#include "nsISupports.h"
+
+class nsIWidget;
+class nsIContent;
+
+// {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, nsIContent* aMenuBarNode)=0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsINativeMenuService, NS_INATIVEMENUSERVICE_IID)
+
+#endif // nsINativeMenuService_h_
diff --git a/widget/nsIPluginWidget.h b/widget/nsIPluginWidget.h
new file mode 100644
index 000000000..3ddf79d39
--- /dev/null
+++ b/widget/nsIPluginWidget.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/. */
+
+#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 000000000..ef9402835
--- /dev/null
+++ b/widget/nsIPrintDialogService.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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;
+class nsIWebBrowserPrint;
+
+/*
+ * 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.
+ * @param aWebBrowserPrint A nsIWebBrowserPrint object that can be used for
+ * retreiving the title of the printed document.
+ * @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,
+ nsIWebBrowserPrint *aWebBrowserPrint) = 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 000000000..ca7541371
--- /dev/null
+++ b/widget/nsIPrintSession.idl
@@ -0,0 +1,40 @@
+/* -*- 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.
+ */
+ [noscript] attribute RemotePrintJobChildPtr remotePrintJob;
+};
diff --git a/widget/nsIPrintSettings.idl b/widget/nsIPrintSettings.idl
new file mode 100644
index 000000000..5c391c1ac
--- /dev/null
+++ b/widget/nsIPrintSettings.idl
@@ -0,0 +1,305 @@
+/* -*- 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
+ */
+ [ref] native nsNativeIntMarginRef(nsIntMargin);
+ [ref] native IntegerArray(nsTArray<int32_t>);
+
+interface nsIPrintSession;
+
+/**
+ * Simplified graphics interface for JS rendering.
+ */
+[scriptable, uuid(ecc5cbad-57fc-4731-b0bd-09e865bd62ad)]
+
+interface nsIPrintSettings : nsISupports
+{
+ /**
+ * PrintSettings to be Saved Navigation Constants
+ */
+ const unsigned long kInitSaveOddEvenPages = 0x00000001;
+ 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 */
+ const unsigned long kInitSavePaperData = 0x00002000;
+ 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;
+
+ /* Print Option Flags for Bit Field*/
+ const long kPrintOddPages = 0x00000001;
+ const long kPrintEvenPages = 0x00000002;
+ const long kEnableSelectionRB = 0x00000004;
+
+ /* Print Range Enums */
+ const long kRangeAllPages = 0;
+ const long kRangeSpecifiedPageRange = 1;
+ const long kRangeSelection = 2;
+ const long kRangeFocusFrame = 3;
+
+ /* Justification Enums */
+ const long kJustLeft = 0;
+ const long kJustCenter = 1;
+ const long kJustRight = 2;
+
+ /**
+ * FrameSet Default Type Constants
+ */
+ const short kUseInternalDefault = 0;
+ const short kUseSettingWhenPossible = 1;
+
+ /**
+ * Page Size Type Constants
+ */
+ const short kPaperSizeNativeData = 0;
+ const short kPaperSizeDefined = 1;
+
+ /**
+ * Page Size Unit Constants
+ */
+ const short kPaperSizeInches = 0;
+ const short kPaperSizeMillimeters = 1;
+
+ /**
+ * Orientation Constants
+ */
+ const short kPortraitOrientation = 0;
+ const short kLandscapeOrientation = 1;
+
+ /**
+ * Print Frame Constants
+ */
+ const short kNoFrames = 0;
+ const short kFramesAsIs = 1;
+ const short kSelectedFrame = 2;
+ const short kEachFrameSep = 3;
+
+ /**
+ * How to Enable Frame Set Printing Constants
+ */
+ const short kFrameEnableNone = 0;
+ const short kFrameEnableAll = 1;
+ const short kFrameEnableAsIsAndEach = 2;
+
+ /**
+ * Output file format
+ */
+ const short kOutputFormatNative = 0;
+ const short kOutputFormatPS = 1;
+ const short kOutputFormatPDF = 2;
+
+ /**
+ * Set PrintOptions
+ */
+ void SetPrintOptions(in int32_t aType, in boolean aTurnOnOff);
+
+ /**
+ * Get PrintOptions
+ */
+ boolean GetPrintOptions(in int32_t aType);
+
+ /**
+ * Get PrintOptions Bit field
+ */
+ int32_t GetPrintOptionsBits();
+
+ /**
+ * Set PrintOptions Bit field
+ */
+ void SetPrintOptionsBits(in int32_t bits);
+
+ /**
+ * Get the page size in twips, considering the
+ * orientation (portrait or landscape).
+ */
+ void GetEffectivePageSize(out double aWidth, out double aHeight);
+
+ /**
+ * 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 */
+
+ attribute long startPageRange;
+ attribute long endPageRange;
+
+ /**
+ * The edge measurements define the positioning of the headers
+ * and footers on the page. They're measured as an offset from
+ * 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.
+ * They're treated as an offset from 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, creating
+ * an invisible border from which the edge and margin attributes are measured.
+ */
+ attribute double unwriteableMarginTop; /* these are in inches */
+ attribute double unwriteableMarginLeft;
+ attribute double unwriteableMarginBottom;
+ attribute double unwriteableMarginRight;
+
+ attribute double scaling; /* values 0.0 - 1.0 */
+ attribute boolean printBGColors; /* Print Background Colors */
+ attribute boolean printBGImages; /* Print Background Images */
+
+ attribute short printRange;
+
+ attribute wstring title;
+ attribute wstring docURL;
+
+ attribute wstring headerStrLeft;
+ attribute wstring headerStrCenter;
+ attribute wstring headerStrRight;
+
+ attribute wstring footerStrLeft;
+ attribute wstring footerStrCenter;
+ attribute wstring footerStrRight;
+
+ attribute short howToEnableFrameUI; /* indicates how to enable the frameset UI */
+ attribute boolean isCancelled; /* indicates whether the print job has been cancelled */
+ attribute short printFrameTypeUsage; /* indicates whether to use the interal value or not */
+ attribute short printFrameType;
+ 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 wstring paperName; /* name of paper */
+ attribute short paperData; /* native data value */
+ 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;
+ attribute boolean printInColor; /* a false means grayscale */
+ attribute long orientation; /* see orientation consts */
+ attribute long numCopies;
+
+ attribute wstring printerName; /* name of destination printer */
+
+ attribute boolean printToFile;
+ attribute wstring 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);
+ /* Purposely made this an "in" arg */
+ [noscript] void GetMarginInTwips(in nsNativeIntMarginRef aMargin);
+ [noscript] void GetEdgeInTwips(in nsNativeIntMarginRef aEdge);
+
+ /**
+ * 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] void GetUnwriteableMarginInTwips(in nsNativeIntMarginRef aEdge);
+
+ /**
+ * 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 cleared.
+ */
+ [noscript] void GetPageRanges(in IntegerArray aPages);
+};
diff --git a/widget/nsIPrintSettingsService.idl b/widget/nsIPrintSettingsService.idl
new file mode 100644
index 000000000..198581b34
--- /dev/null
+++ b/widget/nsIPrintSettingsService.idl
@@ -0,0 +1,157 @@
+/* -*- 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 a "global" PrintSettings object
+ * Creates a new the first time, if one doesn't exist.
+ *
+ * Then returns the same object each time after that.
+ *
+ * Initializes the globalPrintSettings from the default printer
+ */
+ readonly attribute nsIPrintSettings globalPrintSettings;
+
+ /**
+ * Returns a new, unique PrintSettings object each time.
+ *
+ * For example, if each browser was to have its own unique
+ * PrintSettings, then each browser window would call this to
+ * create its own unique PrintSettings object.
+ *
+ * If each browse window was to use the same PrintSettings object
+ * then it should use "globalPrintSettings"
+ *
+ * Initializes the newPrintSettings from the unprefixed printer
+ * (Note: this may not happen if there is an OS specific implementation.)
+ *
+ */
+ readonly attribute nsIPrintSettings newPrintSettings;
+
+ /**
+ * The name of the last printer used, or else the system default printer.
+ */
+ readonly attribute wstring defaultPrinterName;
+
+ /**
+ * 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 wstring 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, howToEnableFrameUI, isCancelled, printFrameTypeUsage
+ * printFrameType, 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, howToEnableFrameUI, isCancelled, printFrameTypeUsage
+ * printFrameType, printSilent, shrinkToFit, numCopies
+ *
+ */
+ void savePrintSettingsToPrefs(in nsIPrintSettings aPrintSettings, in boolean aUsePrinterNamePrefix, in unsigned long aFlags);
+
+ /**
+ * Given some nsIPrintSettings and (optionally) an nsIWebBrowserPrint,
+ * 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 aWBP (optional)
+ * The nsIWebBrowserPrint for the print job.
+ * @param data
+ * Pointer to a pre-existing PrintData to populate.
+ *
+ * @return nsresult
+ */
+ [noscript]
+ void SerializeToPrintData(in nsIPrintSettings aPrintSettings,
+ in nsIWebBrowserPrint aWebBrowserPrint,
+ 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 000000000..ae840b085
--- /dev/null
+++ b/widget/nsIPrintSettingsWin.idl
@@ -0,0 +1,69 @@
+/* -*- 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
+ */
+[scriptable, 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 wstring deviceName;
+ [noscript] attribute wstring driverName;
+
+ [noscript] attribute nsDevMode devMode;
+
+ /**
+ * On Windows we use the printable width and height for the printing surface.
+ * We don't want to have to create native print device contexts in the content
+ * process, so we need to store these in the settings.
+ * Storing in Inches is most convenient as they are retrieved from the device
+ * using fields which are in pixels and pixels per inch.
+ * Note these are stored in portrait format to ensure that we can take account
+ * of our own changes to the orientation print setting.
+ */
+ [noscript] attribute double printableWidthInInches;
+ [noscript] attribute double printableHeightInInches;
+
+ /**
+ * 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/nsIPrinterEnumerator.idl b/widget/nsIPrinterEnumerator.idl
new file mode 100644
index 000000000..d9d00be34
--- /dev/null
+++ b/widget/nsIPrinterEnumerator.idl
@@ -0,0 +1,37 @@
+/* -*- 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 "nsIPrintSettings.idl"
+
+interface nsIStringEnumerator;
+
+[scriptable, uuid(5e738fff-404c-4c94-9189-e8f2cce93e94)]
+
+interface nsIPrinterEnumerator : nsISupports
+{
+ /**
+ * The name of the system default printer. This name should also be
+ * present in printerNameList below. This is not necessarily gecko's
+ * default printer; see nsIPrintSettingsService.defaultPrinterName
+ * for that.
+ */
+ readonly attribute wstring defaultPrinterName;
+
+ /**
+ * 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 wstring aPrinterName, in nsIPrintSettings aPrintSettings);
+
+ /**
+ * The list of printer names
+ */
+ readonly attribute nsIStringEnumerator printerNameList;
+};
+
diff --git a/widget/nsIRollupListener.h b/widget/nsIRollupListener.h
new file mode 100644
index 000000000..83c2dd142
--- /dev/null
+++ b/widget/nsIRollupListener.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/. */
+
+#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.
+ */
+ 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 000000000..b1529daf7
--- /dev/null
+++ b/widget/nsIScreen.idl
@@ -0,0 +1,109 @@
+/* -*- 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
+{
+ /**
+ * Levels of brightness for the screen, from off to full brightness.
+ */
+ const unsigned long BRIGHTNESS_DIM = 0;
+ const unsigned long BRIGHTNESS_FULL = 1;
+
+ /* The number of different brightness levels */
+ const unsigned long BRIGHTNESS_LEVELS = 2;
+
+ /**
+ * Allowable screen rotations, when the underlying widget toolkit
+ * supports rotating the screen.
+ *
+ * ROTATION_0_DEG is the default, unrotated configuration.
+ */
+ const unsigned long ROTATION_0_DEG = 0;
+ const unsigned long ROTATION_90_DEG = 1;
+ const unsigned long ROTATION_180_DEG = 2;
+ const unsigned long ROTATION_270_DEG = 3;
+
+ /**
+ * A unique identifier for this device, useful for requerying
+ * for it via nsIScreenManager.
+ */
+ readonly attribute unsigned long id;
+
+ /**
+ * 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);
+
+ /**
+ * Locks the minimum brightness of the screen, forcing it to be at
+ * least as bright as a certain brightness level. Each call to this
+ * function must eventually be followed by a corresponding call to
+ * unlockMinimumBrightness, with the same brightness level.
+ *
+ * @param brightness A brightness level, one of the above constants.
+ */
+ void lockMinimumBrightness(in unsigned long brightness);
+
+ /**
+ * Releases a lock on the screen brightness. This must be called
+ * (eventually) after a corresponding call to lockMinimumBrightness.
+ *
+ * @param brightness A brightness level, one of the above constants.
+ */
+ void unlockMinimumBrightness(in unsigned long brightness);
+
+ readonly attribute long pixelDepth;
+ readonly attribute long colorDepth;
+ /**
+ * Get/set the screen rotation, on platforms that support changing
+ * screen rotation.
+ */
+ attribute unsigned long rotation;
+
+ /**
+ * 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 (as in Win8.1
+ * per-monitor dpi support).
+ */
+ 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;
+};
diff --git a/widget/nsIScreenManager.idl b/widget/nsIScreenManager.idl
new file mode 100644
index 000000000..400b5d33b
--- /dev/null
+++ b/widget/nsIScreenManager.idl
@@ -0,0 +1,64 @@
+/* -*- 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 ) ;
+
+ //
+ // Returns the screen corresponding to the id. If no such screen exists,
+ // this will throw NS_ERROR_FAILURE. The id is a unique numeric value
+ // assigned to each screen, and is an attribute available on the nsIScreen
+ // interface.
+ nsIScreen screenForId ( in unsigned long id ) ;
+
+ // The screen with the menubar/taskbar. This shouldn't be needed very
+ // often.
+ readonly attribute nsIScreen primaryScreen;
+
+ // Holds the number of screens that are available
+ readonly attribute unsigned long numberOfScreens;
+
+ // The default DPI scaling factor of the screen environment (number of
+ // screen pixels corresponding to 1 CSS px, at the default zoom level).
+ //
+ // This is currently fixed at 1.0 on most platforms, but varies on Windows
+ // if the "logical DPI" scaling option in the Display control panel is set
+ // to a value other than 100% (e.g. 125% or 150% are increasingly common
+ // defaults on laptops with high-dpi screens). See bug 851520.
+ //
+ // NOTE that on OS X, this does -not- reflect the "backing scale factor"
+ // used to support Retina displays, which is a per-display property,
+ // not a system-wide scaling factor. The default ratio of CSS pixels to
+ // Cocoa points remains 1:1, even on a Retina screen where one Cocoa point
+ // corresponds to two device pixels. (This is exposed via other APIs:
+ // see window.devicePixelRatio).
+ //
+ // NOTE also that on Linux, this does -not- currently reflect changes
+ // to the system-wide (X11 or Gtk2) DPI value, as Firefox does not yet
+ // honor these settings. See bug 798362 and bug 712898.
+ readonly attribute float systemDefaultScale;
+
+ // Returns the nsIScreen instance for the given native widget pointer;
+ // the pointer is specific to the particular widget implementation,
+ // and is generally of the same type that NS_NATIVE_WINDOW is.
+ [noscript] nsIScreen screenForNativeWidget ( in voidPtr nativeWidget );
+};
+
+
+%{ C++
+
+%}
diff --git a/widget/nsISound.idl b/widget/nsISound.idl
new file mode 100644
index 000000000..6f9acc2ce
--- /dev/null
+++ b/widget/nsISound.idl
@@ -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 "nsISupports.idl"
+
+interface nsIURL;
+
+[scriptable, uuid(C3C28D92-A17F-43DF-976D-4EEAE6F995FC)]
+interface nsISound : nsISupports
+{
+ void play(in nsIURL aURL);
+ /**
+ * for playing system sounds
+ *
+ * NS_SYSSOUND_* params are obsolete. The new events will not be supported by
+ * this method. You should use playEventSound method instaed.
+ */
+ void playSystemSound(in AString soundAlias);
+ 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);
+};
+
+%{C++
+
+/**
+ * NS_SYSSOUND_* can be used for playSystemSound but they are obsolete.
+ * Use nsISound::playEventSound instead.
+ */
+#define NS_SYSSOUND_PREFIX NS_LITERAL_STRING("_moz_")
+#define NS_SYSSOUND_MAIL_BEEP NS_LITERAL_STRING("_moz_mailbeep")
+#define NS_SYSSOUND_ALERT_DIALOG NS_LITERAL_STRING("_moz_alertdialog")
+#define NS_SYSSOUND_CONFIRM_DIALOG NS_LITERAL_STRING("_moz_confirmdialog")
+#define NS_SYSSOUND_PROMPT_DIALOG NS_LITERAL_STRING("_moz_promptdialog")
+#define NS_SYSSOUND_SELECT_DIALOG NS_LITERAL_STRING("_moz_selectdialog")
+#define NS_SYSSOUND_MENU_EXECUTE NS_LITERAL_STRING("_moz_menucommand")
+#define NS_SYSSOUND_MENU_POPUP NS_LITERAL_STRING("_moz_menupopup")
+
+#define NS_IsMozAliasSound(aSoundAlias) \
+ StringBeginsWith(aSoundAlias, NS_SYSSOUND_PREFIX)
+
+%}
diff --git a/widget/nsIStandaloneNativeMenu.idl b/widget/nsIStandaloneNativeMenu.idl
new file mode 100644
index 000000000..4de2025c8
--- /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"
+
+interface nsIDOMElement;
+
+/**
+ * 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 nsIDOMElement aDOMElement);
+
+ /**
+ * 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 000000000..9db801519
--- /dev/null
+++ b/widget/nsISystemStatusBar.idl
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+
+/**
+ * Allow applications to interface with the Mac OS X system status bar.
+ */
+
+[scriptable, uuid(24493180-ee81-4b7c-8b17-9e69480b7b8a)]
+interface nsISystemStatusBar : nsISupports
+{
+ /**
+ * Add an item to the system status bar. Each item can only be present once,
+ * subsequent addItem calls with the same element will be ignored.
+ * The system status bar holds a strong reference to the added XUL menu
+ * element and the item will stay in the status bar until it is removed via
+ * a call to removeItem, or until the process shuts down.
+ * @param aDOMMenuElement A XUL menu element that contains a XUL menupopup
+ * with regular menu content. The menu's icon is put
+ * into the system status bar; clicking it will open
+ * a menu with the contents of the menupopup.
+ * The menu label is not shown.
+ */
+ void addItem(in nsIDOMElement aDOMMenuElement);
+
+ /**
+ * Remove a previously-added item from the menu bar. Calling this with an
+ * element that has not been added before will be silently ignored.
+ * @param aDOMMenuElement The XUL menu element that you called addItem with.
+ */
+ void removeItem(in nsIDOMElement aDOMMenuElement);
+};
diff --git a/widget/nsITaskbarOverlayIconController.idl b/widget/nsITaskbarOverlayIconController.idl
new file mode 100644
index 000000000..f11cf16ed
--- /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 000000000..b246586f8
--- /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 DOMString 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 000000000..800d0bc20
--- /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 DOMString 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 000000000..d3e178e6a
--- /dev/null
+++ b/widget/nsITaskbarPreviewController.idl
@@ -0,0 +1,112 @@
+/* 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 drawPreview for more information.
+ */
+ readonly attribute unsigned long width;
+
+ /**
+ * The height of the preview image. This value is allowed to change at any
+ * time. See drawPreview 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
+ * drawThumbnail for more information.
+ */
+ readonly attribute float thumbnailAspectRatio;
+
+ [deprecated]
+ boolean drawPreview(in nsISupports ctx);
+
+ [deprecated]
+ boolean drawThumbnail(in nsISupports ctx, in unsigned long width, in unsigned long height);
+
+ /**
+ * 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);
+
+ /**
+ * An asynchronous version of drawPreview and drawThumbnail apis
+ * implemented in nsITaskbarPreviewController.
+ *
+ * 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 000000000..d81179d31
--- /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 000000000..0b7ab03cc
--- /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, uuid(11E4C8BD-5C2D-4E1A-A9A1-79DD5B0FE544)]
+interface nsITaskbarTabPreview : nsITaskbarPreview
+{
+ /**
+ * The title displayed above the thumbnail
+ *
+ * Default: an empty string
+ */
+ attribute DOMString 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 000000000..f09946793
--- /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 drawPreview and drawThumbnail 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/nsITransferable.idl b/widget/nsITransferable.idl
new file mode 100644
index 000000000..b128586dd
--- /dev/null
+++ b/widget/nsITransferable.idl
@@ -0,0 +1,212 @@
+/* -*- 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 "nsIContentPolicyBase.idl"
+
+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"
+
+// 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
+ * @param aDataLen the length of the data
+ */
+ void getFlavorData(in nsITransferable aTransferable, in string aFlavor, out nsISupports aData, out unsigned long aDataLen);
+};
+
+
+[scriptable, uuid(97e0c418-1c1e-4106-bad1-9fcb11dff2fe)]
+interface nsITransferable : nsISupports
+{
+ const long kFlavorHasDataProvider = 0;
+
+ /**
+ * 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. For example, we try to delete data that you copy
+ * to the clipboard when you close a Private Browsing window.
+ *
+ * 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.QueryInterface(Ci.nsIInterfaceRequestor)
+ * .getInterface(Ci.nsIWebNavigation)
+ * .QueryInterface(Ci.nsILoadContext);
+ *
+ * In C++ callers, if you have the corresponding document, you can just call
+ * nsIDocument::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 (mime types as nsISupportsCString) that the transferable
+ * can export, either through intrinsic knowledge or output data converters.
+ *
+ * @param aDataFlavorList fills list with supported flavors. This is a copy of
+ * the internal list, so it may be edited w/out affecting the transferable.
+ */
+ nsIArray 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
+ * @param aDataLen the length of the data
+ */
+ void getTransferData ( in string aFlavor, out nsISupports aData, out unsigned long aDataLen ) ;
+
+ /**
+ * Returns the best flavor in the transferable, given those that have
+ * been added to it with |AddFlavor()|
+ *
+ * @param aFlavor (out parameter) the flavor of data that was retrieved
+ * @param aData the data. Some variant of class in nsISupportsPrimitives.idl
+ * @param aDataLen the length of the data
+ */
+ void getAnyTransferData ( out ACString aFlavor, out nsISupports aData,
+ out unsigned long aDataLen ) ;
+
+ /**
+ * Returns true if the data is large.
+ */
+ boolean isLargeDataSet ( ) ;
+
+
+ ///////////////////////////////
+ // Setter part of interface
+ ///////////////////////////////
+
+ /**
+ * Computes a list of flavors (mime types as nsISupportsCString) that the transferable can
+ * accept into it, either through intrinsic knowledge or input data converters.
+ *
+ * @param outFlavorList fills list with supported flavors. This is a copy of
+ * the internal list, so it may be edited w/out affecting the transferable.
+ */
+ nsIArray 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, in unsigned long aDataLen ) ;
+
+ /**
+ * 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.
+ */
+ [noscript] 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.
+ */
+ [noscript] attribute nsIPrincipal requestingPrincipal;
+ [noscript] attribute nsContentPolicyType contentPolicyType;
+
+};
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
new file mode 100644
index 000000000..2d14cc107
--- /dev/null
+++ b/widget/nsIWidget.h
@@ -0,0 +1,2043 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIWidget_h__
+#define nsIWidget_h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsISupports.h"
+#include "nsColor.h"
+#include "nsRect.h"
+#include "nsStringGlue.h"
+
+#include "nsCOMPtr.h"
+#include "nsWidgetInitData.h"
+#include "nsTArray.h"
+#include "nsITheme.h"
+#include "nsITimer.h"
+#include "nsRegionFwd.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/widget/IMEData.h"
+#include "nsDataHashtable.h"
+#include "nsIObserver.h"
+#include "nsIWidgetListener.h"
+#include "FrameMetrics.h"
+#include "Units.h"
+
+// forward declarations
+class nsIRollupListener;
+class imgIContainer;
+class nsIContent;
+class ViewWrapper;
+class nsIScreen;
+class nsIRunnable;
+class nsIKeyEventInPluginCallback;
+
+namespace mozilla {
+namespace dom {
+class TabChild;
+} // namespace dom
+namespace plugins {
+class PluginWidgetChild;
+} // namespace plugins
+namespace layers {
+class AsyncDragMetrics;
+class Composer2D;
+class Compositor;
+class CompositorBridgeChild;
+class LayerManager;
+class LayerManagerComposite;
+class PLayerTransactionChild;
+struct ScrollableLayerGuid;
+} // namespace layers
+namespace gfx {
+class DrawTarget;
+class SourceSurface;
+} // namespace gfx
+namespace widget {
+class TextEventDispatcher;
+class TextEventDispatcherListener;
+class CompositorWidget;
+class CompositorWidgetInitData;
+} // namespace widget
+} // 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
+#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
+#ifdef MOZ_X11
+#define NS_NATIVE_COMPOSITOR_DISPLAY 105
+#endif // MOZ_X11
+#endif
+#ifdef MOZ_WIDGET_ANDROID
+#define NS_JAVA_SURFACE 100
+#define NS_PRESENTATION_WINDOW 101
+#define NS_PRESENTATION_SURFACE 102
+#endif
+
+#define NS_IWIDGET_IID \
+{ 0x06396bf6, 0x2dd8, 0x45e5, \
+ { 0xac, 0x45, 0x75, 0x26, 0x53, 0xb1, 0xc9, 0x80 } }
+
+/*
+ * Window shadow styles
+ * Also used for the -moz-window-shadow CSS property
+ */
+
+#define NS_STYLE_WINDOW_SHADOW_NONE 0
+#define NS_STYLE_WINDOW_SHADOW_DEFAULT 1
+#define NS_STYLE_WINDOW_SHADOW_MENU 2
+#define NS_STYLE_WINDOW_SHADOW_TOOLTIP 3
+#define NS_STYLE_WINDOW_SHADOW_SHEET 4
+
+/**
+ * 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
+};
+
+/**
+ * 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 better be the last one in this list.
+ eCursorCount
+ };
+
+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"
+
+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(NS_MAXSIZE, NS_MAXSIZE)
+ {
+ }
+
+ SizeConstraints(mozilla::LayoutDeviceIntSize aMinSize,
+ mozilla::LayoutDeviceIntSize aMaxSize)
+ : mMinSize(aMinSize),
+ mMaxSize(aMaxSize)
+ {
+ }
+
+ 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::TabChild TabChild;
+
+ public:
+ typedef mozilla::layers::Composer2D Composer2D;
+ 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::ZoomConstraints ZoomConstraints;
+ typedef mozilla::widget::IMEMessage IMEMessage;
+ typedef mozilla::widget::IMENotification IMENotification;
+ 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::DesktopIntRect DesktopIntRect;
+ 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
+ *
+ */
+ virtual MOZ_MUST_USE 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.
+ */
+ virtual MOZ_MUST_USE 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
+ */
+ NS_IMETHOD 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 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 Gecko override of the system default scale, if any;
+ * returns <= 0.0 if the system scale should be used as-is.
+ * nsIWidget::GetDefaultScale() [above] takes this into account.
+ * It is exposed here so that code that wants to check for a
+ * default-scale override without having a widget on hand can
+ * easily access the same value.
+ * Note that any scale override is a browser-wide value, whereas
+ * the default GetDefaultScale value (when no override is present)
+ * may vary between widgets (or screens).
+ */
+ static double DefaultScaleOverride();
+
+ /**
+ * 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
+ *
+ */
+ NS_IMETHOD Show(bool aState) = 0;
+
+ /**
+ * 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
+ *
+ **/
+ NS_IMETHOD Move(double aX, double aY) = 0;
+
+ /**
+ * Reposition this widget so that the client area has the given offset.
+ *
+ * @param aX the new x 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)
+ * @param aY the new y 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)
+ *
+ **/
+ NS_IMETHOD MoveClient(double aX, double aY) = 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
+ *
+ */
+ NS_IMETHOD Resize(double aWidth,
+ double aHeight,
+ bool aRepaint) = 0;
+
+ /**
+ * 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
+ *
+ */
+ NS_IMETHOD Resize(double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint) = 0;
+
+ /**
+ * Resize the widget so that the inner client area has the given size.
+ *
+ * @param aWidth the new width of the client area.
+ * @param aHeight the new height of the client area.
+ * @param aRepaint whether the widget should be repainted
+ *
+ */
+ NS_IMETHOD ResizeClient(double aWidth,
+ double aHeight,
+ bool aRepaint) = 0;
+
+ /**
+ * Resize and reposition the widget so tht inner client area has the given
+ * offset and size.
+ *
+ * @param aX the new x 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)
+ * @param aY the new y 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)
+ * @param aWidth the new width of the client area.
+ * @param aHeight the new height of the client area.
+ * @param aRepaint whether the widget should be repainted
+ *
+ */
+ NS_IMETHOD ResizeClient(double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ 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;
+
+ /**
+ * Return size mode (minimized, maximized, normalized).
+ * Returns a value from nsSizeMode (see nsIWidgetListener.h)
+ */
+ virtual nsSizeMode SizeMode() = 0;
+
+ /**
+ * Enable or disable this Widget
+ *
+ * @param aState true to enable the Widget, false to disable it.
+ *
+ */
+ NS_IMETHOD Enable(bool aState) = 0;
+
+ /**
+ * Ask whether the widget is enabled
+ */
+ virtual bool IsEnabled() const = 0;
+
+ /**
+ * Request activation of this window or give focus to this widget.
+ *
+ * @param aRaise If true, this function requests activation of this
+ * widget's toplevel window.
+ * If false, the appropriate toplevel window (which in
+ * the case of popups may not be this widget's toplevel
+ * window) is already active.
+ */
+ NS_IMETHOD SetFocus(bool aRaise = false) = 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.
+ */
+ virtual MOZ_MUST_USE 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.
+ *
+ */
+ NS_IMETHOD 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) { }
+
+ /**
+ * Get the cursor for this widget.
+ *
+ * @return this widget's cursor.
+ */
+
+ virtual nsCursor GetCursor(void) = 0;
+
+ /**
+ * Set the cursor for this widget
+ *
+ * @param aCursor the new cursor for this widget
+ */
+
+ NS_IMETHOD SetCursor(nsCursor aCursor) = 0;
+
+ /**
+ * 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 an image as the cursor for this widget.
+ *
+ * @param aCursor the cursor to set
+ * @param aX the X coordinate of the hotspot (from left).
+ * @param aY the Y coordinate of the hotspot (from top).
+ * @retval NS_ERROR_NOT_IMPLEMENTED if setting images as cursors is not
+ * supported
+ */
+ NS_IMETHOD SetCursor(imgIContainer* aCursor,
+ 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;
+ nsTArray<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(int32_t aStyle) = 0;
+
+ /*
+ * 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 Mac OS X Lion, this method shows or hides the full screen button in
+ * the titlebar that handles native full screen mode.
+ *
+ * Ignored on child widgets, non-Mac platforms, & pre-Lion Mac.
+ */
+ virtual void SetShowsFullScreenButton(bool aShow) = 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.
+ *
+ */
+ NS_IMETHOD 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;
+
+ /**
+ * 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.
+ */
+ NS_IMETHOD 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 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 NS_THEME_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
+ */
+
+ NS_IMETHOD SetTitle(const nsAString& aTitle) = 0;
+
+ /**
+ * Set the widget's icon.
+ * Must be called after Create.
+ *
+ * @param anIconSpec string specifying the icon to use; convention is to pass
+ * a resource: URL from which a platform-dependent resource
+ * file name will be constructed
+ */
+
+ NS_IMETHOD SetIcon(const nsAString& anIconSpec) = 0;
+
+ /**
+ * Return this widget's origin in screen coordinates.
+ *
+ * @return screen coordinates stored in the x,y members
+ */
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() = 0;
+
+ /**
+ * 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
+ *
+ */
+ NS_IMETHOD 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<mozilla::layers::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;
+
+ /**
+ * 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.
+ */
+ NS_IMETHOD 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;
+
+ /**
+ * Set the background color of the window titlebar for this widget. On Mac,
+ * for example, this will remove the grey gradient and bottom border and
+ * instead show a single, solid color.
+ *
+ * Ignored on any platform that does not support it. Ignored by widgets that
+ * do not represent windows.
+ *
+ * @param aColor The color to set the title bar background to. Alpha values
+ * other than fully transparent (0) are respected if possible
+ * on the platform. An alpha of 0 will cause the window to
+ * draw with the default style for the platform.
+ *
+ * @param aActive Whether the color should be applied to active or inactive
+ * windows.
+ */
+ virtual void SetWindowTitlebarColor(nscolor aColor, bool aActive) = 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;
+
+ /**
+ * Return the popup that was last rolled up, or null if there isn't one.
+ */
+ virtual nsIContent* GetLastRollup() = 0;
+
+ /**
+ * Begin a window resizing drag, based on the event passed in.
+ */
+ NS_IMETHOD BeginResizeDrag(mozilla::WidgetGUIEvent* aEvent,
+ int32_t aHorizontal,
+ int32_t aVertical) = 0;
+
+ /**
+ * Begin a window moving drag, based on the event passed in.
+ */
+ NS_IMETHOD BeginMoveDrag(mozilla::WidgetMouseEvent* aEvent) = 0;
+
+ enum Modifiers {
+ CAPS_LOCK = 0x01, // when CapsLock is active
+ NUM_LOCK = 0x02, // when NumLock is active
+ SHIFT_L = 0x0100,
+ SHIFT_R = 0x0200,
+ CTRL_L = 0x0400,
+ CTRL_R = 0x0800,
+ ALT_L = 0x1000, // includes Option
+ ALT_R = 0x2000,
+ COMMAND_L = 0x4000,
+ COMMAND_R = 0x8000,
+ HELP = 0x10000,
+ FUNCTION = 0x100000,
+ 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,
+ * NSMouseMoved; 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;
+
+ // 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)
+ {}
+
+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);
+
+ mozilla::UniquePtr<LongTapInfo> mLongTapTouchPoint;
+ nsCOMPtr<nsITimer> mLongTapTimer;
+ static int32_t sPointerIdCounter;
+
+public:
+ /**
+ * 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.
+ */
+ NS_IMETHOD
+ 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.
+ */
+ NS_IMETHOD NotifyIME(const IMENotification& aIMENotification) = 0;
+
+ /**
+ * Start plugin IME. If this results in a string getting committed, the
+ * result is in aCommitted (otherwise aCommitted is empty).
+ *
+ * aKeyboardEvent The event with which plugin IME is to be started
+ * panelX and panelY Location in screen coordinates of the IME input panel
+ * (should be just under the plugin)
+ * aCommitted The string committed during IME -- otherwise empty
+ */
+ NS_IMETHOD StartPluginIME(const mozilla::WidgetKeyboardEvent& aKeyboardEvent,
+ int32_t aPanelX, int32_t aPanelY,
+ nsString& aCommitted) = 0;
+
+ /**
+ * Tells the widget whether or not a plugin (inside the widget) has the
+ * keyboard focus. Should be sent when the keyboard focus changes too or
+ * from a plugin.
+ *
+ * aFocused Whether or not a plugin is focused
+ */
+ virtual void SetPluginFocused(bool& aFocused) = 0;
+
+ /*
+ * Tell the plugin has focus. It is unnecessary to use IPC
+ */
+ bool PluginHasFocus()
+ {
+ return GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN;
+ }
+
+ /**
+ * Set IME candidate window position by windowless plugin.
+ */
+ virtual void SetCandidateWindowForPlugin(
+ const mozilla::widget::CandidateWindowPosition& aPosition) = 0;
+
+ /**
+ * Handle default action when PluginEvent isn't handled
+ */
+ virtual void DefaultProcOfPluginEvent(
+ const mozilla::WidgetPluginEvent& aEvent) = 0;
+
+ /*
+ * Notifies the input context changes.
+ */
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) = 0;
+
+ /*
+ * Get current input context.
+ */
+ NS_IMETHOD_(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.
+ */
+ NS_IMETHOD_(NativeIMEContext) GetNativeIMEContext();
+
+ /*
+ * 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).
+ */
+ NS_IMETHOD AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent) = 0;
+
+ /*
+ * Execute native key bindings for aType.
+ */
+ typedef void (*DoCommandCallback)(mozilla::Command, void*);
+ enum NativeKeyBindingsType
+ {
+ NativeKeyBindingsForSingleLineEditor,
+ NativeKeyBindingsForMultiLineEditor,
+ NativeKeyBindingsForRichTextEditor
+ };
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) = 0;
+
+ /*
+ * Retrieves preference for IME updates
+ */
+ virtual nsIMEUpdatePreference GetIMEUpdatePreference() = 0;
+
+ /*
+ * Call this method when a dialog is opened which has a default button.
+ * The button's rectangle should be supplied in aButtonRect.
+ */
+ NS_IMETHOD 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();
+ }
+
+ /**
+ * 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(TabChild* aTabChild);
+
+ /**
+ * 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(TabChild* aTabChild,
+ 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 TabChild, return that. Otherwise return
+ * null.
+ */
+ virtual TabChild* GetOwningTabChild() { 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; }
+
+ /**
+ * 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 FrameMetrics::ViewID& aViewId,
+ const mozilla::Maybe<ZoomConstraints>& aConstraints) {};
+
+ /**
+ * GetTextEventDispatcher() returns TextEventDispatcher belonging to the
+ * widget. Note that this never returns nullptr.
+ */
+ NS_IMETHOD_(TextEventDispatcher*) GetTextEventDispatcher() = 0;
+
+ /**
+ * GetNativeTextEventDispatcherListener() returns a
+ * TextEventDispatcherListener instance which is used when the widget
+ * instance handles native IME and/or keyboard events.
+ */
+ NS_IMETHOD_(TextEventDispatcherListener*)
+ GetNativeTextEventDispatcherListener() = 0;
+
+ virtual void ZoomToRect(const uint32_t& aPresShellId,
+ const FrameMetrics::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)
+ { }
+
+protected:
+ /**
+ * Like GetDefaultScale, but taking into account only the system settings
+ * and ignoring Gecko preferences.
+ */
+ virtual double GetDefaultScaleInternal() { return 1.0; }
+
+ // 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 000000000..0d18236cb
--- /dev/null
+++ b/widget/nsIWidgetListener.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 "nsIWidgetListener.h"
+
+#include "nsRegion.h"
+#include "nsView.h"
+#include "nsIPresShell.h"
+#include "nsIWidget.h"
+#include "nsIXULWindow.h"
+
+#include "mozilla/BasicEvents.h"
+
+using namespace mozilla;
+
+nsIXULWindow*
+nsIWidgetListener::GetXULWindow()
+{
+ return nullptr;
+}
+
+nsView*
+nsIWidgetListener::GetView()
+{
+ return nullptr;
+}
+
+nsIPresShell*
+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::UIResolutionChanged()
+{
+}
+
+void
+nsIWidgetListener::FullscreenChanged(bool aInFullscreen)
+{
+}
+
+bool
+nsIWidgetListener::ZLevelChanged(bool aImmediate,
+ nsWindowZ* aPlacement,
+ nsIWidget* aRequestBelow,
+ nsIWidget** aActualBelow)
+{
+ return false;
+}
+
+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(uint64_t aTransactionId,
+ const TimeStamp& aCompositeStart,
+ const TimeStamp& aCompositeEnd)
+{
+}
+
+void
+nsIWidgetListener::RequestRepaint()
+{
+}
+
+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 000000000..63d899be0
--- /dev/null
+++ b/widget/nsIWidgetListener.h
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIWidgetListener_h__
+#define nsIWidgetListener_h__
+
+#include <stdint.h>
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/TimeStamp.h"
+
+#include "nsRegionFwd.h"
+#include "Units.h"
+
+class nsView;
+class nsIPresShell;
+class nsIWidget;
+class nsIXULWindow;
+
+/**
+ * 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 nsIXULWindow, 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 nsIXULWindow* GetXULWindow();
+
+ /**
+ * If this listener is for an nsView, return it.
+ */
+ virtual nsView* GetView();
+
+ /**
+ * Return the presshell for this widget listener.
+ */
+ virtual nsIPresShell* 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();
+
+ /**
+ * 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 entered or left the fullscreen state.
+ */
+ virtual void FullscreenChanged(bool aInFullscreen);
+
+ /**
+ * 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ virtual void DidPaintWindow();
+
+ virtual void DidCompositeWindow(uint64_t aTransactionId,
+ const mozilla::TimeStamp& aCompositeStart,
+ const mozilla::TimeStamp& aCompositeEnd);
+
+ /**
+ * Request that layout schedules a repaint on the next refresh driver tick.
+ */
+ virtual void RequestRepaint();
+
+ /**
+ * Handle an event.
+ */
+ virtual nsEventStatus HandleEvent(mozilla::WidgetGUIEvent* aEvent,
+ bool aUseAttachedEvents);
+};
+
+#endif
diff --git a/widget/nsIWinTaskbar.idl b/widget/nsIWinTaskbar.idl
new file mode 100644
index 000000000..bcc2dd128
--- /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 000000000..331562a67
--- /dev/null
+++ b/widget/nsIWindowsUIUtils.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"
+
+
+[scriptable, uuid(aa8a0ecf-96a1-418c-b80e-f24ae18bbedc)]
+interface nsIWindowsUIUtils : nsISupports
+{
+ /**
+ * 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();
+};
+
diff --git a/widget/nsIdleService.cpp b/widget/nsIdleService.cpp
new file mode 100644
index 000000000..6a2833081
--- /dev/null
+++ b/widget/nsIdleService.cpp
@@ -0,0 +1,897 @@
+/* -*- 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 "nsIdleService.h"
+#include "nsString.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.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 PRLogModuleInfo *sLog = nullptr;
+
+#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);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIdleServiceDaily
+
+NS_IMPL_ISUPPORTS(nsIdleServiceDaily, nsIObserver, nsISupportsWeakReference)
+
+NS_IMETHODIMP
+nsIdleServiceDaily::Observe(nsISupports *,
+ const char *aTopic,
+ const char16_t *)
+{
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsIdleServiceDaily: 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,
+ ("nsIdleServiceDaily: 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,
+ ("nsIdleServiceDaily: 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,
+ ("nsIdleServiceDaily: Restarting daily timer"));
+
+ // Start timer for the next check in one day.
+ (void)mTimer->InitWithFuncCallback(DailyCallback,
+ this,
+ SECONDS_PER_DAY * PR_MSEC_PER_SEC,
+ nsITimer::TYPE_ONE_SHOT);
+
+ return NS_OK;
+}
+
+nsIdleServiceDaily::nsIdleServiceDaily(nsIIdleService* aIdleService)
+ : mIdleService(aIdleService)
+ , mTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
+ , mCategoryObservers(OBSERVER_TOPIC_IDLE_DAILY)
+ , mShutdownInProgress(false)
+ , mExpectedTriggerTime(0)
+ , mIdleDailyTriggerWait(DAILY_SIGNIFICANT_IDLE_SERVICE_SEC)
+{
+}
+
+void
+nsIdleServiceDaily::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 nowSec = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
+ int32_t lastDaily = Preferences::GetInt(PREF_LAST_DAILY, 0);
+ if (lastDaily < 0 || lastDaily > nowSec) {
+ // The time is bogus, use default.
+ lastDaily = 0;
+ }
+ int32_t secondsSinceLastDaily = nowSec - lastDaily;
+
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsIdleServiceDaily: 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,
+ ("nsIdleServiceDaily: 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,
+ ("nsIdleServiceDaily: 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,
+ ("nsIdleServiceDaily: 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->InitWithFuncCallback(DailyCallback,
+ this,
+ milliSecLeftUntilDaily,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+
+ // Register for when we should terminate/pause
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsIdleServiceDaily: 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);
+ }
+}
+
+nsIdleServiceDaily::~nsIdleServiceDaily()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+
+void
+nsIdleServiceDaily::StageIdleDaily(bool aHasBeenLongWait)
+{
+ NS_ASSERTION(mIdleService, "No idle service available?");
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsIdleServiceDaily: 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
+nsIdleServiceDaily::DailyCallback(nsITimer* aTimer, void* aClosure)
+{
+ MOZ_LOG(sLog, LogLevel::Debug,
+ ("nsIdleServiceDaily: DailyCallback running"));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "DailyCallback running");
+#endif
+
+ nsIdleServiceDaily* self = static_cast<nsIdleServiceDaily*>(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, ("nsIdleServiceDaily: DailyCallback resetting timer to %lld msec",
+ delayTime / PR_USEC_PER_MSEC));
+#ifdef MOZ_WIDGET_ANDROID
+ __android_log_print(LOG_LEVEL, LOG_TAG,
+ "DailyCallback resetting timer to %lld msec",
+ delayTime / PR_USEC_PER_MSEC);
+#endif
+
+ (void)self->mTimer->InitWithFuncCallback(DailyCallback,
+ self,
+ delayTime / PR_USEC_PER_MSEC,
+ nsITimer::TYPE_ONE_SHOT);
+ 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.
+ *
+ */
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIdleService
+
+namespace {
+nsIdleService* gIdleService;
+} // namespace
+
+already_AddRefed<nsIdleService>
+nsIdleService::GetInstance()
+{
+ RefPtr<nsIdleService> instance(gIdleService);
+ return instance.forget();
+}
+
+nsIdleService::nsIdleService() : mCurrentlySetToTimeoutAt(TimeStamp()),
+ mIdleObserverCount(0),
+ mDeltaToNextIdleSwitchInS(UINT32_MAX),
+ mLastUserInteraction(TimeStamp::Now())
+{
+ if (sLog == nullptr)
+ sLog = PR_NewLogModule("idleService");
+ MOZ_ASSERT(!gIdleService);
+ gIdleService = this;
+ if (XRE_IsParentProcess()) {
+ mDailyIdle = new nsIdleServiceDaily(this);
+ mDailyIdle->Init();
+ }
+}
+
+nsIdleService::~nsIdleService()
+{
+ if(mTimer) {
+ mTimer->Cancel();
+ }
+
+
+ MOZ_ASSERT(gIdleService == this);
+ gIdleService = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsIdleService, nsIIdleService, nsIIdleServiceInternal)
+
+NS_IMETHODIMP
+nsIdleService::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);
+
+ if (!mArrayListeners.AppendElement(listener)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Create our timer callback if it's not there already.
+ if (!mTimer) {
+ nsresult rv;
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // 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
+nsIdleService::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
+nsIdleService::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
+nsIdleService::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
+nsIdleService::PollIdleTime(uint32_t* /*aIdleTime*/)
+{
+ // Default behavior is not to have the ability to poll an idle time.
+ return false;
+}
+
+bool
+nsIdleService::UsePollMode()
+{
+ uint32_t dummy;
+ return PollIdleTime(&dummy);
+}
+
+void
+nsIdleService::StaticIdleTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ static_cast<nsIdleService*>(aClosure)->IdleTimerCallback();
+}
+
+void
+nsIdleService::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;
+ }
+
+ // 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
+nsIdleService::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->InitWithFuncCallback(StaticIdleTimerCallback,
+ this,
+ deltaTime.ToMilliseconds(),
+ nsITimer::TYPE_ONE_SHOT);
+
+ }
+}
+
+void
+nsIdleService::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/nsIdleService.h b/widget/nsIdleService.h
new file mode 100644
index 000000000..17046201d
--- /dev/null
+++ b/widget/nsIdleService.h
@@ -0,0 +1,225 @@
+/* -*- 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 nsIdleService_h__
+#define nsIdleService_h__
+
+#include "nsIIdleServiceInternal.h"
+#include "nsCOMPtr.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsIObserver.h"
+#include "nsIIdleService.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() {}
+};
+
+// This one will be declared later.
+class nsIdleService;
+
+/**
+ * Class to handle the daily idle timer.
+ */
+class nsIdleServiceDaily : public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ explicit nsIdleServiceDaily(nsIIdleService* 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 ~nsIdleServiceDaily();
+
+ /**
+ * 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 nsIdleServiceDaily 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.
+ */
+ nsIIdleService* 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 nsIdleService : public nsIIdleServiceInternal
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIDLESERVICE
+ NS_DECL_NSIIDLESERVICEINTERNAL
+
+protected:
+ static already_AddRefed<nsIdleService> GetInstance();
+
+ nsIdleService();
+ virtual ~nsIdleService();
+
+ /**
+ * 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<nsIdleServiceDaily> 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;
+
+ /**
+ * 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 // nsIdleService_h__
diff --git a/widget/nsNativeTheme.cpp b/widget/nsNativeTheme.cpp
new file mode 100644
index 000000000..a5bd85faf
--- /dev/null
+++ b/widget/nsNativeTheme.cpp
@@ -0,0 +1,775 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNativeTheme.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsIPresShell.h"
+#include "nsNumberControlFrame.h"
+#include "nsPresContext.h"
+#include "nsString.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMXULMenuListElement.h"
+#include "nsThemeConstants.h"
+#include "nsIComponentManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsProgressFrame.h"
+#include "nsMeterFrame.h"
+#include "nsMenuFrame.h"
+#include "nsRangeFrame.h"
+#include "nsCSSRendering.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/HTMLProgressElement.h"
+#include "nsIDocumentInlines.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsNativeTheme::nsNativeTheme()
+: mAnimatedContentTimeout(UINT32_MAX)
+{
+}
+
+NS_IMPL_ISUPPORTS(nsNativeTheme, nsITimerCallback)
+
+nsIPresShell *
+nsNativeTheme::GetPresShell(nsIFrame* aFrame)
+{
+ if (!aFrame)
+ return nullptr;
+
+ nsPresContext* context = aFrame->PresContext();
+ return context ? context->GetPresShell() : nullptr;
+}
+
+EventStates
+nsNativeTheme::GetContentState(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ if (!aFrame)
+ return EventStates();
+
+ bool isXULCheckboxRadio =
+ (aWidgetType == NS_THEME_CHECKBOX ||
+ aWidgetType == NS_THEME_RADIO) &&
+ aFrame->GetContent()->IsXULElement();
+ if (isXULCheckboxRadio)
+ aFrame = aFrame->GetParent();
+
+ if (!aFrame->GetContent())
+ return EventStates();
+
+ nsIPresShell *shell = GetPresShell(aFrame);
+ if (!shell)
+ return EventStates();
+
+ nsIContent* frameContent = aFrame->GetContent();
+ EventStates flags;
+ if (frameContent->IsElement()) {
+ flags = frameContent->AsElement()->State();
+
+ // <input type=number> needs special handling since its nested native
+ // anonymous <input type=text> takes focus for it.
+ if (aWidgetType == NS_THEME_NUMBER_INPUT &&
+ frameContent->IsHTMLElement(nsGkAtoms::input)) {
+ nsNumberControlFrame *numberControlFrame = do_QueryFrame(aFrame);
+ if (numberControlFrame && numberControlFrame->IsFocused()) {
+ flags |= NS_EVENT_STATE_FOCUS;
+ }
+ }
+
+ nsNumberControlFrame* numberControlFrame =
+ nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+ if (numberControlFrame &&
+ numberControlFrame->GetContent()->AsElement()->State().
+ HasState(NS_EVENT_STATE_DISABLED)) {
+ flags |= NS_EVENT_STATE_DISABLED;
+ }
+ }
+
+ if (isXULCheckboxRadio && aWidgetType == NS_THEME_RADIO) {
+ if (IsFocused(aFrame))
+ flags |= NS_EVENT_STATE_FOCUS;
+ }
+
+ // On Windows and Mac, only draw focus rings if they should be shown. This
+ // means that focus rings are only shown once the keyboard has been used to
+ // focus something in the window.
+#if defined(XP_MACOSX)
+ // Mac always draws focus rings for textboxes and lists.
+ if (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
+ aWidgetType == NS_THEME_SEARCHFIELD ||
+ aWidgetType == NS_THEME_LISTBOX) {
+ return flags;
+ }
+#endif
+#if defined(XP_WIN)
+ // On Windows, focused buttons are always drawn as such by the native theme.
+ if (aWidgetType == NS_THEME_BUTTON)
+ return flags;
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
+ nsIDocument* doc = aFrame->GetContent()->OwnerDoc();
+ nsPIDOMWindowOuter* window = doc->GetWindow();
+ if (window && !window->ShouldShowFocusRing())
+ flags &= ~NS_EVENT_STATE_FOCUS;
+#endif
+
+ return flags;
+}
+
+/* static */
+bool
+nsNativeTheme::CheckBooleanAttr(nsIFrame* aFrame, nsIAtom* aAtom)
+{
+ if (!aFrame)
+ return false;
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content)
+ return false;
+
+ if (content->IsHTMLElement())
+ return content->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->AttrValueIs(kNameSpaceID_None, aAtom,
+ NS_LITERAL_STRING("true"), eCaseMatters);
+}
+
+/* static */
+int32_t
+nsNativeTheme::CheckIntAttr(nsIFrame* aFrame, nsIAtom* aAtom, int32_t defaultValue)
+{
+ if (!aFrame)
+ return defaultValue;
+
+ nsAutoString attr;
+ aFrame->GetContent()->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)
+{
+ // When we are using the HTML progress element,
+ // we can get the value from the IDL property.
+ if (aFrame && aFrame->GetContent()->IsHTMLElement(nsGkAtoms::progress)) {
+ return static_cast<HTMLProgressElement*>(aFrame->GetContent())->Value();
+ }
+
+ return (double)nsNativeTheme::CheckIntAttr(aFrame, nsGkAtoms::value, 0);
+}
+
+/* static */
+double
+nsNativeTheme::GetProgressMaxValue(nsIFrame* aFrame)
+{
+ // When we are using the HTML progress element,
+ // we can get the max from the IDL property.
+ if (aFrame && aFrame->GetContent()->IsHTMLElement(nsGkAtoms::progress)) {
+ return static_cast<HTMLProgressElement*>(aFrame->GetContent())->Max();
+ }
+
+ return (double)std::max(nsNativeTheme::CheckIntAttr(aFrame, nsGkAtoms::max, 100), 1);
+}
+
+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
+ nsCOMPtr<nsIDOMHTMLInputElement> inputElt = do_QueryInterface(content);
+ if (inputElt) {
+ bool checked;
+ inputElt->GetChecked(&checked);
+ return checked;
+ }
+ }
+
+ return CheckBooleanAttr(aFrame, aCheckSelected ? nsGkAtoms::selected
+ : nsGkAtoms::checked);
+}
+
+bool
+nsNativeTheme::IsButtonTypeMenu(nsIFrame* aFrame)
+{
+ if (!aFrame)
+ return false;
+
+ nsIContent* content = aFrame->GetContent();
+ return content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ NS_LITERAL_STRING("menu"), eCaseMatters);
+}
+
+bool
+nsNativeTheme::IsPressedButton(nsIFrame* aFrame)
+{
+ EventStates eventState = GetContentState(aFrame, NS_THEME_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
+ nsCOMPtr<nsIDOMHTMLInputElement> inputElt = do_QueryInterface(content);
+ if (inputElt) {
+ bool indeterminate;
+ inputElt->GetIndeterminate(&indeterminate);
+ return indeterminate;
+ }
+
+ return false;
+}
+
+bool
+nsNativeTheme::IsWidgetStyled(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ // 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 (aWidgetType == NS_THEME_RESIZER) {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (parentFrame && parentFrame->GetType() == nsGkAtoms::scrollFrame) {
+ // 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()->mAppearance);
+ }
+ }
+ }
+
+ /**
+ * 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 (aWidgetType == NS_THEME_PROGRESSCHUNK ||
+ aWidgetType == NS_THEME_PROGRESSBAR) {
+ nsProgressFrame* progressFrame = do_QueryFrame(aWidgetType == NS_THEME_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 (aWidgetType == NS_THEME_METERCHUNK ||
+ aWidgetType == NS_THEME_METERBAR) {
+ nsMeterFrame* meterFrame = do_QueryFrame(aWidgetType == NS_THEME_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 (aWidgetType == NS_THEME_RANGE ||
+ aWidgetType == NS_THEME_RANGE_THUMB) {
+ nsRangeFrame* rangeFrame =
+ do_QueryFrame(aWidgetType == NS_THEME_RANGE_THUMB
+ ? aFrame->GetParent() : aFrame);
+ if (rangeFrame) {
+ return !rangeFrame->ShouldUseNativeStyle();
+ }
+ }
+
+ if (aWidgetType == NS_THEME_SPINNER_UPBUTTON ||
+ aWidgetType == NS_THEME_SPINNER_DOWNBUTTON) {
+ nsNumberControlFrame* numberControlFrame =
+ nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+ if (numberControlFrame) {
+ return !numberControlFrame->ShouldUseNativeStyleForSpinner();
+ }
+ }
+
+ return (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
+ aWidgetType == NS_THEME_LISTBOX ||
+ aWidgetType == NS_THEME_MENULIST) &&
+ aFrame->GetContent()->IsHTMLElement() &&
+ aPresContext->HasAuthorSpecifiedRules(aFrame,
+ NS_AUTHOR_SPECIFIED_BORDER |
+ NS_AUTHOR_SPECIFIED_BACKGROUND);
+}
+
+bool
+nsNativeTheme::IsDisabled(nsIFrame* aFrame, EventStates aEventStates)
+{
+ if (!aFrame) {
+ return false;
+ }
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content) {
+ 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->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ NS_LITERAL_STRING("true"), eCaseMatters);
+}
+
+/* static */ bool
+nsNativeTheme::IsFrameRTL(nsIFrame* aFrame)
+{
+ if (!aFrame) {
+ return false;
+ }
+ WritingMode wm = aFrame->GetWritingMode();
+ return !(wm.IsVertical() ? wm.IsVerticalLR() : wm.IsBidiLTR());
+}
+
+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 nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::scrollbarDownBottom, &nsGkAtoms::scrollbarDownTop,
+ &nsGkAtoms::scrollbarUpBottom, &nsGkAtoms::scrollbarUpTop,
+ nullptr};
+
+ switch (aFrame->GetContent()->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 nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::descending, &nsGkAtoms::ascending, nullptr};
+ switch (aFrame->GetContent()->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->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidecolumnpicker,
+ NS_LITERAL_STRING("true"), 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;
+ aFrame->GetContent()->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, classStr);
+ 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;
+
+ return !aFrame->GetContent()->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);
+}
+
+// progressbar:
+bool
+nsNativeTheme::IsIndeterminateProgress(nsIFrame* aFrame,
+ EventStates aEventStates)
+{
+ if (!aFrame || !aFrame->GetContent())
+ return false;
+
+ if (aFrame->GetContent()->IsHTMLElement(nsGkAtoms::progress)) {
+ return aEventStates.HasState(NS_EVENT_STATE_INDETERMINATE);
+ }
+
+ return aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mode,
+ NS_LITERAL_STRING("undetermined"),
+ eCaseMatters);
+}
+
+bool
+nsNativeTheme::IsVerticalProgress(nsIFrame* aFrame)
+{
+ if (!aFrame) {
+ return false;
+ }
+ return IsVerticalMeter(aFrame);
+}
+
+bool
+nsNativeTheme::IsVerticalMeter(nsIFrame* aFrame)
+{
+ NS_PRECONDITION(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();
+ }
+ NS_NOTREACHED("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->GetParentMenuListType() != eNotMenuList));
+}
+
+bool
+nsNativeTheme::IsMenuListEditable(nsIFrame *aFrame)
+{
+ bool isEditable = false;
+ nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aFrame->GetContent());
+ if (menulist)
+ menulist->GetEditable(&isEditable);
+ return isEditable;
+}
+
+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 = do_CreateInstance(NS_TIMER_CONTRACTID);
+ NS_ENSURE_TRUE(mAnimatedContentTimer, false);
+ }
+
+ if (mAnimatedContentList.IsEmpty() || timeout != mAnimatedContentTimeout) {
+ nsresult rv;
+ if (!mAnimatedContentList.IsEmpty()) {
+ rv = mAnimatedContentTimer->Cancel();
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ rv = mAnimatedContentTimer->InitWithCallback(this, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ mAnimatedContentTimeout = timeout;
+ }
+
+ if (!mAnimatedContentList.AppendElement(aContent)) {
+ NS_WARNING("Out of memory!");
+ return false;
+ }
+
+ 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;
+}
+
+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()->mAppearance != aFrame->StyleDisplay()->mAppearance ||
+ (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->GetType() != nsGkAtoms::rangeFrame) {
+ // If the thumb's frame is passed in, get its range parent:
+ rangeFrame = aFrame->GetParent();
+ }
+ if (rangeFrame->GetType() == nsGkAtoms::rangeFrame) {
+ 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;
+ }
+ nsIDocument* document = content->OwnerDoc();
+ nsIContent* body = document->GetBodyElement();
+ if (!body) {
+ return nullptr;
+ }
+ return body->GetPrimaryFrame();
+}
+
+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;
+ }
+ }
+ nsStyleContext* bgSC = nullptr;
+ if (!nsCSSRendering::FindBackground(frame, &bgSC) ||
+ bgSC->StyleBackground()->IsTransparent()) {
+ nsIFrame* backgroundFrame = nsCSSRendering::FindNonTransparentBackgroundFrame(frame, true);
+ nsCSSRendering::FindBackground(backgroundFrame, &bgSC);
+ }
+ if (bgSC) {
+ nscolor bgColor = bgSC->StyleBackground()->mBackgroundColor;
+ // 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;
+}
diff --git a/widget/nsNativeTheme.h b/widget/nsNativeTheme.h
new file mode 100644
index 000000000..621866db4
--- /dev/null
+++ b/widget/nsNativeTheme.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/. */
+
+// This defines a common base class for nsITheme implementations, to reduce
+// code duplication.
+
+#include "nsAlgorithm.h"
+#include "nsIAtom.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsMargin.h"
+#include "nsGkAtoms.h"
+#include "nsTArray.h"
+#include "nsITimer.h"
+#include "nsIContent.h"
+
+class nsIFrame;
+class nsIPresShell;
+class nsPresContext;
+
+namespace mozilla {
+class EventStates;
+} // namespace mozilla
+
+class nsNativeTheme : public nsITimerCallback
+{
+ protected:
+ virtual ~nsNativeTheme() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ enum ScrollbarButtonType {
+ eScrollbarButton_UpTop = 0,
+ eScrollbarButton_Down = 1 << 0,
+ eScrollbarButton_Bottom = 1 << 1
+ };
+
+ enum TreeSortDirection {
+ eTreeSortDirection_Descending,
+ eTreeSortDirection_Natural,
+ eTreeSortDirection_Ascending
+ };
+
+ nsNativeTheme();
+
+ // Returns the content state (hover, focus, etc), see EventStateManager.h
+ mozilla::EventStates GetContentState(nsIFrame* aFrame, uint8_t aWidgetType);
+
+ // 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,
+ uint8_t aWidgetType);
+
+ // Accessors to widget-specific state information
+
+ bool IsDisabled(nsIFrame* aFrame, mozilla::EventStates aEventStates);
+
+ // RTL chrome direction
+ static bool IsFrameRTL(nsIFrame* aFrame);
+
+ 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);
+ }
+
+ 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);
+
+ bool IsMenuListEditable(nsIFrame *aFrame);
+
+ nsIPresShell *GetPresShell(nsIFrame* aFrame);
+ static bool CheckBooleanAttr(nsIFrame* aFrame, nsIAtom* aAtom);
+ static int32_t CheckIntAttr(nsIFrame* aFrame, nsIAtom* 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
+ bool IsDarkBackground(nsIFrame* aFrame);
+
+ private:
+ uint32_t mAnimatedContentTimeout;
+ nsCOMPtr<nsITimer> mAnimatedContentTimer;
+ AutoTArray<nsCOMPtr<nsIContent>, 20> mAnimatedContentList;
+};
diff --git a/widget/nsPrimitiveHelpers.cpp b/widget/nsPrimitiveHelpers.cpp
new file mode 100644
index 000000000..242db3d50
--- /dev/null
+++ b/widget/nsPrimitiveHelpers.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/. */
+
+
+//
+// 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 "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITransferable.h"
+#include "nsIComponentManager.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 char* aFlavor, const void* aDataBuff,
+ uint32_t aDataLen, nsISupports** aPrimitive )
+{
+ if ( !aPrimitive )
+ return;
+
+ if ( strcmp(aFlavor,kTextMime) == 0 || strcmp(aFlavor,kNativeHTMLMime) == 0 ||
+ strcmp(aFlavor,kRTFMime) == 0 || strcmp(aFlavor,kCustomTypesMime) == 0) {
+ 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);
+ if (!utf8)
+ return;
+ memcpy(utf8, aDataBuff, *aDataLen);
+ int32_t signedLen = static_cast<int32_t>(*aDataLen);
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(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 char* aFlavor, nsISupports* aPrimitive,
+ void** aDataBuff, uint32_t aDataLen )
+{
+ if ( !aDataBuff )
+ return;
+
+ *aDataBuff = nullptr;
+
+ if ( strcmp(aFlavor,kTextMime) == 0 || strcmp(aFlavor,kCustomTypesMime) == 0) {
+ nsCOMPtr<nsISupportsCString> plainText ( do_QueryInterface(aPrimitive) );
+ if ( plainText ) {
+ nsAutoCString data;
+ plainText->GetData ( data );
+ *aDataBuff = ToNewCString(data);
+ }
+ }
+ else {
+ nsCOMPtr<nsISupportsString> doubleByteText ( do_QueryInterface(aPrimitive) );
+ if ( doubleByteText ) {
+ nsAutoString data;
+ doubleByteText->GetData ( data );
+ *aDataBuff = ToNewUnicode(data);
+ }
+ }
+
+}
+
+
+//
+// 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 char* 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 ( strcmp(inFlavor, kTextMime) == 0 || strcmp(inFlavor, kRTFMime) == 0) {
+ 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 ( strcmp(inFlavor, "image/jpeg") == 0 ) {
+ // 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 000000000..952803a66
--- /dev/null
+++ b/widget/nsPrimitiveHelpers.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrimitiveHelpers_h___
+#define nsPrimitiveHelpers_h___
+
+#include "nsError.h"
+#include "nscore.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 char* 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 char* 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 char* inFlavor, void** ioData, int32_t* ioLengthInBytes ) ;
+
+}; // class nsLinebreakHelpers
+
+
+#endif // nsPrimitiveHelpers_h___
diff --git a/widget/nsPrintOptionsImpl.cpp b/widget/nsPrintOptionsImpl.cpp
new file mode 100644
index 000000000..97d699a93
--- /dev/null
+++ b/widget/nsPrintOptionsImpl.cpp
@@ -0,0 +1,1418 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrintOptionsImpl.h"
+
+#include "mozilla/embedding/PPrinting.h"
+#include "mozilla/layout/RemotePrintJobChild.h"
+#include "mozilla/RefPtr.h"
+#include "nsIPrinterEnumerator.h"
+#include "nsPrintingProxy.h"
+#include "nsReadableUtils.h"
+#include "nsPrintSettingsImpl.h"
+#include "nsIPrintSession.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsArray.h"
+#include "nsIDOMWindow.h"
+#include "nsIDialogParamBlock.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIWindowWatcher.h"
+#include "prprf.h"
+
+#include "nsIStringEnumerator.h"
+#include "nsISupportsPrimitives.h"
+#include "stdlib.h"
+#include "mozilla/Preferences.h"
+#include "nsPrintfCString.h"
+#include "nsIWebBrowserPrint.h"
+
+using namespace mozilla;
+using namespace mozilla::embedding;
+
+typedef mozilla::layout::RemotePrintJobChild RemotePrintJobChild;
+
+NS_IMPL_ISUPPORTS(nsPrintOptions, 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 kPrintEvenPages[] = "print_evenpages";
+static const char kPrintOddPages[] = "print_oddpages";
+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 kPrintPaperName[] = "print_paper_name";
+static const char kPrintPaperData[] = "print_paper_data";
+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_ENUMERATOR_CONTRACTID "@mozilla.org/gfx/printerenumerator;1"
+
+nsPrintOptions::nsPrintOptions()
+{
+}
+
+nsPrintOptions::~nsPrintOptions()
+{
+}
+
+nsresult
+nsPrintOptions::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintOptions::SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ PrintData* data)
+{
+ nsCOMPtr<nsIPrintSession> session;
+ nsresult rv = aSettings->GetPrintSession(getter_AddRefs(session));
+ if (NS_SUCCEEDED(rv) && session) {
+ RefPtr<RemotePrintJobChild> remotePrintJob;
+ rv = session->GetRemotePrintJob(getter_AddRefs(remotePrintJob));
+ if (NS_SUCCEEDED(rv)) {
+ data->remotePrintJobChild() = remotePrintJob;
+ }
+ }
+
+ aSettings->GetStartPageRange(&data->startPageRange());
+ aSettings->GetEndPageRange(&data->endPageRange());
+
+ 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());
+
+ aSettings->GetPrintBGColors(&data->printBGColors());
+ aSettings->GetPrintBGImages(&data->printBGImages());
+ aSettings->GetPrintRange(&data->printRange());
+
+ // I have no idea if I'm doing this string copying correctly...
+ nsXPIDLString title;
+ aSettings->GetTitle(getter_Copies(title));
+ data->title() = title;
+
+ nsXPIDLString docURL;
+ aSettings->GetDocURL(getter_Copies(docURL));
+ data->docURL() = docURL;
+
+ // Header strings...
+ nsXPIDLString headerStrLeft;
+ aSettings->GetHeaderStrLeft(getter_Copies(headerStrLeft));
+ data->headerStrLeft() = headerStrLeft;
+
+ nsXPIDLString headerStrCenter;
+ aSettings->GetHeaderStrCenter(getter_Copies(headerStrCenter));
+ data->headerStrCenter() = headerStrCenter;
+
+ nsXPIDLString headerStrRight;
+ aSettings->GetHeaderStrRight(getter_Copies(headerStrRight));
+ data->headerStrRight() = headerStrRight;
+
+ // Footer strings...
+ nsXPIDLString footerStrLeft;
+ aSettings->GetFooterStrLeft(getter_Copies(footerStrLeft));
+ data->footerStrLeft() = footerStrLeft;
+
+ nsXPIDLString footerStrCenter;
+ aSettings->GetFooterStrCenter(getter_Copies(footerStrCenter));
+ data->footerStrCenter() = footerStrCenter;
+
+ nsXPIDLString footerStrRight;
+ aSettings->GetFooterStrRight(getter_Copies(footerStrRight));
+ data->footerStrRight() = footerStrRight;
+
+ aSettings->GetHowToEnableFrameUI(&data->howToEnableFrameUI());
+ aSettings->GetIsCancelled(&data->isCancelled());
+ aSettings->GetPrintFrameTypeUsage(&data->printFrameTypeUsage());
+ aSettings->GetPrintFrameType(&data->printFrameType());
+ aSettings->GetPrintSilent(&data->printSilent());
+ aSettings->GetShrinkToFit(&data->shrinkToFit());
+ aSettings->GetShowPrintProgress(&data->showPrintProgress());
+
+ nsXPIDLString paperName;
+ aSettings->GetPaperName(getter_Copies(paperName));
+ data->paperName() = paperName;
+
+ aSettings->GetPaperData(&data->paperData());
+ 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());
+
+ nsXPIDLString printerName;
+ aSettings->GetPrinterName(getter_Copies(printerName));
+ data->printerName() = printerName;
+
+ aSettings->GetPrintToFile(&data->printToFile());
+
+ nsXPIDLString toFileName;
+ aSettings->GetToFileName(getter_Copies(toFileName));
+ data->toFileName() = 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());
+
+ aSettings->GetPrintOptionsBits(&data->optionFlags());
+
+ // 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->printableWidthInInches() = 0;
+ data->printableHeightInInches() = 0;
+ data->isFramesetDocument() = false;
+ data->isFramesetFrameSelected() = false;
+ data->isIFrameSelected() = false;
+ data->isRangeSelection() = false;
+ // data->GTKPrintSettings() default-initializes
+ // data->printJobName() default-initializes
+ data->printAllPages() = true;
+ data->mustCollate() = false;
+ // data->disposition() default-initializes
+ data->pagesAcross() = 1;
+ data->pagesDown() = 1;
+ data->printTime() = 0;
+ data->detailedErrorReporting() = true;
+ // data->faxNumber() default-initializes
+ data->addHeaderAndFooter() = false;
+ data->fileNameExtensionHidden() = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintOptions::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->SetStartPageRange(data.startPageRange());
+ settings->SetEndPageRange(data.endPageRange());
+
+ 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->SetPrintRange(data.printRange());
+
+ // I have no idea if I'm doing this string copying correctly...
+ settings->SetTitle(data.title().get());
+ settings->SetDocURL(data.docURL().get());
+
+ // Header strings...
+ settings->SetHeaderStrLeft(data.headerStrLeft().get());
+ settings->SetHeaderStrCenter(data.headerStrCenter().get());
+ settings->SetHeaderStrRight(data.headerStrRight().get());
+
+ // Footer strings...
+ settings->SetFooterStrLeft(data.footerStrLeft().get());
+ settings->SetFooterStrCenter(data.footerStrCenter().get());
+ settings->SetFooterStrRight(data.footerStrRight().get());
+
+ settings->SetHowToEnableFrameUI(data.howToEnableFrameUI());
+ settings->SetIsCancelled(data.isCancelled());
+ settings->SetPrintFrameTypeUsage(data.printFrameTypeUsage());
+ settings->SetPrintFrameType(data.printFrameType());
+ settings->SetPrintSilent(data.printSilent());
+ settings->SetShrinkToFit(data.shrinkToFit());
+ settings->SetShowPrintProgress(data.showPrintProgress());
+
+ settings->SetPaperName(data.paperName().get());
+
+ settings->SetPaperData(data.paperData());
+ 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->SetPrinterName(data.printerName().get());
+
+ settings->SetPrintToFile(data.printToFile());
+
+ settings->SetToFileName(data.toFileName().get());
+
+ settings->SetOutputFormat(data.outputFormat());
+ settings->SetPrintPageDelay(data.printPageDelay());
+ settings->SetResolution(data.resolution());
+ settings->SetDuplex(data.duplex());
+ settings->SetIsInitializedFromPrinter(data.isInitializedFromPrinter());
+ settings->SetIsInitializedFromPrefs(data.isInitializedFromPrefs());
+
+ settings->SetPrintOptionsBits(data.optionFlags());
+
+ return NS_OK;
+}
+
+
+/** ---------------------------------------------------
+ * Helper function - Creates the "prefix" for the pref
+ * It is either "print."
+ * or "print.printer_<print name>."
+ */
+const char*
+nsPrintOptions::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();
+}
+
+//----------------------------------------------------------------------
+// Testing of read/write prefs
+// This define controls debug output
+#ifdef DEBUG_rods_X
+static void WriteDebugStr(const char* aArg1, const char* aArg2,
+ const char16_t* aStr)
+{
+ nsString str(aStr);
+ char16_t s = '&';
+ char16_t r = '_';
+ str.ReplaceChar(s, r);
+
+ printf("%s %s = %s \n", aArg1, aArg2, ToNewUTF8String(str));
+}
+const char* kWriteStr = "Write Pref:";
+const char* kReadStr = "Read Pref:";
+#define DUMP_STR(_a1, _a2, _a3) WriteDebugStr((_a1), GetPrefName((_a2), \
+aPrefName), (_a3));
+#define DUMP_BOOL(_a1, _a2, _a3) printf("%s %s = %s \n", (_a1), \
+GetPrefName((_a2), aPrefName), (_a3)?"T":"F");
+#define DUMP_INT(_a1, _a2, _a3) printf("%s %s = %d \n", (_a1), \
+GetPrefName((_a2), aPrefName), (_a3));
+#define DUMP_DBL(_a1, _a2, _a3) printf("%s %s = %10.5f \n", (_a1), \
+GetPrefName((_a2), aPrefName), (_a3));
+#else
+#define DUMP_STR(_a1, _a2, _a3)
+#define DUMP_BOOL(_a1, _a2, _a3)
+#define DUMP_INT(_a1, _a2, _a3)
+#define DUMP_DBL(_a1, _a2, _a3)
+#endif /* DEBUG_rods_X */
+//----------------------------------------------------------------------
+
+/**
+ * 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
+nsPrintOptions::ReadPrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName,
+ uint32_t aFlags)
+{
+ NS_ENSURE_ARG_POINTER(aPS);
+
+ 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);
+ DUMP_INT(kReadStr, kMarginTop, margin.top);
+ ReadInchesToTwipsPref(GetPrefName(kMarginLeft, aPrinterName), margin.left,
+ kMarginLeft);
+ DUMP_INT(kReadStr, kMarginLeft, margin.left);
+ ReadInchesToTwipsPref(GetPrefName(kMarginBottom, aPrinterName),
+ margin.bottom, kMarginBottom);
+ DUMP_INT(kReadStr, kMarginBottom, margin.bottom);
+ ReadInchesToTwipsPref(GetPrefName(kMarginRight, aPrinterName), margin.right,
+ kMarginRight);
+ DUMP_INT(kReadStr, kMarginRight, margin.right);
+ aPS->SetMarginInTwips(margin);
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveEdges) {
+ nsIntMargin margin(0,0,0,0);
+ ReadInchesIntToTwipsPref(GetPrefName(kEdgeTop, aPrinterName), margin.top,
+ kEdgeTop);
+ DUMP_INT(kReadStr, kEdgeTop, margin.top);
+ ReadInchesIntToTwipsPref(GetPrefName(kEdgeLeft, aPrinterName), margin.left,
+ kEdgeLeft);
+ DUMP_INT(kReadStr, kEdgeLeft, margin.left);
+ ReadInchesIntToTwipsPref(GetPrefName(kEdgeBottom, aPrinterName),
+ margin.bottom, kEdgeBottom);
+ DUMP_INT(kReadStr, kEdgeBottom, margin.bottom);
+ ReadInchesIntToTwipsPref(GetPrefName(kEdgeRight, aPrinterName), margin.right,
+ kEdgeRight);
+ DUMP_INT(kReadStr, kEdgeRight, margin.right);
+ aPS->SetEdgeInTwips(margin);
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveUnwriteableMargins) {
+ nsIntMargin margin;
+ ReadInchesIntToTwipsPref(GetPrefName(kUnwriteableMarginTop, aPrinterName), margin.top,
+ kUnwriteableMarginTop);
+ DUMP_INT(kReadStr, kUnwriteableMarginTop, margin.top);
+ ReadInchesIntToTwipsPref(GetPrefName(kUnwriteableMarginLeft, aPrinterName), margin.left,
+ kUnwriteableMarginLeft);
+ DUMP_INT(kReadStr, kUnwriteableMarginLeft, margin.left);
+ ReadInchesIntToTwipsPref(GetPrefName(kUnwriteableMarginBottom, aPrinterName),
+ margin.bottom, kUnwriteableMarginBottom);
+ DUMP_INT(kReadStr, kUnwriteableMarginBottom, margin.bottom);
+ ReadInchesIntToTwipsPref(GetPrefName(kUnwriteableMarginRight, aPrinterName), margin.right,
+ kUnwriteableMarginRight);
+ DUMP_INT(kReadStr, kUnwriteableMarginRight, margin.right);
+ aPS->SetUnwriteableMarginInTwips(margin);
+ }
+
+ 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 \
+ ) \
+ )
+
+ // Paper size prefs are read as a group
+ if (aFlags & nsIPrintSettings::kInitSavePaperSize) {
+ int32_t sizeUnit;
+ double width, height;
+
+ bool success = GETINTPREF(kPrintPaperSizeUnit, &sizeUnit)
+ && GETDBLPREF(kPrintPaperWidth, width)
+ && GETDBLPREF(kPrintPaperHeight, height)
+ && GETSTRPREF(kPrintPaperName, &str);
+
+ // 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.
+#if defined(XP_WIN)
+ bool saveSanitizedSizePrefs = false;
+#endif
+ if (success) {
+ success = (sizeUnit != nsIPrintSettings::kPaperSizeInches)
+ || (width < 100.0)
+ || (height < 100.0);
+#if defined(XP_WIN)
+ // Work around legacy invalid prefs where the size unit gets set to
+ // millimeters, but the height and width remains as the previous inches
+ // settings. See bug 1276717 and bug 1369386 for details.
+ if (sizeUnit == nsIPrintSettings::kPaperSizeMillimeters &&
+ height >= 0L && height < 25L &&
+ width >= 0L && width < 25L) {
+
+ // As small pages sizes can be valid we only override when the old
+ // (now no longer set) pref print_paper_size_type exists. This will be
+ // removed when we save the prefs below.
+ const char* paperSizeTypePref =
+ GetPrefName("print_paper_size_type", aPrinterName);
+ if (Preferences::HasUserValue(paperSizeTypePref)) {
+ saveSanitizedSizePrefs = true;
+ height = -1L;
+ width = -1L;
+ }
+ }
+#endif
+ }
+
+ if (success) {
+ aPS->SetPaperSizeUnit(sizeUnit);
+ DUMP_INT(kReadStr, kPrintPaperSizeUnit, sizeUnit);
+ aPS->SetPaperWidth(width);
+ DUMP_DBL(kReadStr, kPrintPaperWidth, width);
+ aPS->SetPaperHeight(height);
+ DUMP_DBL(kReadStr, kPrintPaperHeight, height);
+ aPS->SetPaperName(str.get());
+ DUMP_STR(kReadStr, kPrintPaperName, str.get());
+#if defined(XP_WIN)
+ if (saveSanitizedSizePrefs) {
+ SavePrintSettingsToPrefs(aPS, !aPrinterName.IsEmpty(),
+ nsIPrintSettings::kInitSavePaperSize);
+ }
+#endif
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveOddEvenPages) {
+ if (GETBOOLPREF(kPrintEvenPages, &b)) {
+ aPS->SetPrintOptions(nsIPrintSettings::kPrintEvenPages, b);
+ DUMP_BOOL(kReadStr, kPrintEvenPages, b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveOddEvenPages) {
+ if (GETBOOLPREF(kPrintOddPages, &b)) {
+ aPS->SetPrintOptions(nsIPrintSettings::kPrintOddPages, b);
+ DUMP_BOOL(kReadStr, kPrintOddPages, b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderLeft) {
+ if (GETSTRPREF(kPrintHeaderStrLeft, &str)) {
+ aPS->SetHeaderStrLeft(str.get());
+ DUMP_STR(kReadStr, kPrintHeaderStrLeft, str.get());
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderCenter) {
+ if (GETSTRPREF(kPrintHeaderStrCenter, &str)) {
+ aPS->SetHeaderStrCenter(str.get());
+ DUMP_STR(kReadStr, kPrintHeaderStrCenter, str.get());
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderRight) {
+ if (GETSTRPREF(kPrintHeaderStrRight, &str)) {
+ aPS->SetHeaderStrRight(str.get());
+ DUMP_STR(kReadStr, kPrintHeaderStrRight, str.get());
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterLeft) {
+ if (GETSTRPREF(kPrintFooterStrLeft, &str)) {
+ aPS->SetFooterStrLeft(str.get());
+ DUMP_STR(kReadStr, kPrintFooterStrLeft, str.get());
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterCenter) {
+ if (GETSTRPREF(kPrintFooterStrCenter, &str)) {
+ aPS->SetFooterStrCenter(str.get());
+ DUMP_STR(kReadStr, kPrintFooterStrCenter, str.get());
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterRight) {
+ if (GETSTRPREF(kPrintFooterStrRight, &str)) {
+ aPS->SetFooterStrRight(str.get());
+ DUMP_STR(kReadStr, kPrintFooterStrRight, str.get());
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveBGColors) {
+ if (GETBOOLPREF(kPrintBGColors, &b)) {
+ aPS->SetPrintBGColors(b);
+ DUMP_BOOL(kReadStr, kPrintBGColors, b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveBGImages) {
+ if (GETBOOLPREF(kPrintBGImages, &b)) {
+ aPS->SetPrintBGImages(b);
+ DUMP_BOOL(kReadStr, kPrintBGImages, b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveReversed) {
+ if (GETBOOLPREF(kPrintReversed, &b)) {
+ aPS->SetPrintReversed(b);
+ DUMP_BOOL(kReadStr, kPrintReversed, b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveInColor) {
+ if (GETBOOLPREF(kPrintInColor, &b)) {
+ aPS->SetPrintInColor(b);
+ DUMP_BOOL(kReadStr, kPrintInColor, b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePaperData) {
+ if (GETINTPREF(kPrintPaperData, &iVal)) {
+ aPS->SetPaperData(iVal);
+ DUMP_INT(kReadStr, kPrintPaperData, iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveOrientation) {
+ if (GETINTPREF(kPrintOrientation, &iVal)) {
+ aPS->SetOrientation(iVal);
+ DUMP_INT(kReadStr, kPrintOrientation, iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePrintToFile) {
+ if (GETBOOLPREF(kPrintToFile, &b)) {
+ aPS->SetPrintToFile(b);
+ DUMP_BOOL(kReadStr, kPrintToFile, b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveToFileName) {
+ if (GETSTRPREF(kPrintToFileName, &str)) {
+ aPS->SetToFileName(str.get());
+ DUMP_STR(kReadStr, kPrintToFileName, str.get());
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePageDelay) {
+ if (GETINTPREF(kPrintPageDelay, &iVal)) {
+ aPS->SetPrintPageDelay(iVal);
+ DUMP_INT(kReadStr, kPrintPageDelay, iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveShrinkToFit) {
+ if (GETBOOLPREF(kPrintShrinkToFit, &b)) {
+ aPS->SetShrinkToFit(b);
+ DUMP_BOOL(kReadStr, kPrintShrinkToFit, b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveScaling) {
+ if (GETDBLPREF(kPrintScaling, dbl)) {
+ aPS->SetScaling(dbl);
+ DUMP_DBL(kReadStr, kPrintScaling, dbl);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveResolution) {
+ if (GETINTPREF(kPrintResolution, &iVal)) {
+ aPS->SetResolution(iVal);
+ DUMP_INT(kReadStr, kPrintResolution, iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveDuplex) {
+ if (GETINTPREF(kPrintDuplex, &iVal)) {
+ aPS->SetDuplex(iVal);
+ DUMP_INT(kReadStr, kPrintDuplex, iVal);
+ }
+ }
+
+ // Not Reading In:
+ // Number of Copies
+
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsImpl.h
+ * @update 1/12/01 rods
+ */
+nsresult
+nsPrintOptions::WritePrefs(nsIPrintSettings *aPS, const nsAString& aPrinterName,
+ uint32_t aFlags)
+{
+ NS_ENSURE_ARG_POINTER(aPS);
+
+ nsIntMargin margin;
+ if (aFlags & nsIPrintSettings::kInitSaveMargins) {
+ if (NS_SUCCEEDED(aPS->GetMarginInTwips(margin))) {
+ WriteInchesFromTwipsPref(GetPrefName(kMarginTop, aPrinterName),
+ margin.top);
+ DUMP_INT(kWriteStr, kMarginTop, margin.top);
+ WriteInchesFromTwipsPref(GetPrefName(kMarginLeft, aPrinterName),
+ margin.left);
+ DUMP_INT(kWriteStr, kMarginLeft, margin.top);
+ WriteInchesFromTwipsPref(GetPrefName(kMarginBottom, aPrinterName),
+ margin.bottom);
+ DUMP_INT(kWriteStr, kMarginBottom, margin.top);
+ WriteInchesFromTwipsPref(GetPrefName(kMarginRight, aPrinterName),
+ margin.right);
+ DUMP_INT(kWriteStr, kMarginRight, margin.top);
+ }
+ }
+
+ nsIntMargin edge;
+ if (aFlags & nsIPrintSettings::kInitSaveEdges) {
+ if (NS_SUCCEEDED(aPS->GetEdgeInTwips(edge))) {
+ WriteInchesIntFromTwipsPref(GetPrefName(kEdgeTop, aPrinterName),
+ edge.top);
+ DUMP_INT(kWriteStr, kEdgeTop, edge.top);
+ WriteInchesIntFromTwipsPref(GetPrefName(kEdgeLeft, aPrinterName),
+ edge.left);
+ DUMP_INT(kWriteStr, kEdgeLeft, edge.top);
+ WriteInchesIntFromTwipsPref(GetPrefName(kEdgeBottom, aPrinterName),
+ edge.bottom);
+ DUMP_INT(kWriteStr, kEdgeBottom, edge.top);
+ WriteInchesIntFromTwipsPref(GetPrefName(kEdgeRight, aPrinterName),
+ edge.right);
+ DUMP_INT(kWriteStr, kEdgeRight, edge.top);
+ }
+ }
+
+ nsIntMargin unwriteableMargin;
+ if (aFlags & nsIPrintSettings::kInitSaveUnwriteableMargins) {
+ if (NS_SUCCEEDED(aPS->GetUnwriteableMarginInTwips(unwriteableMargin))) {
+ WriteInchesIntFromTwipsPref(GetPrefName(kUnwriteableMarginTop, aPrinterName),
+ unwriteableMargin.top);
+ DUMP_INT(kWriteStr, kUnwriteableMarginTop, unwriteableMargin.top);
+ WriteInchesIntFromTwipsPref(GetPrefName(kUnwriteableMarginLeft, aPrinterName),
+ unwriteableMargin.left);
+ DUMP_INT(kWriteStr, kUnwriteableMarginLeft, unwriteableMargin.top);
+ WriteInchesIntFromTwipsPref(GetPrefName(kUnwriteableMarginBottom, aPrinterName),
+ unwriteableMargin.bottom);
+ DUMP_INT(kWriteStr, kUnwriteableMarginBottom, unwriteableMargin.top);
+ WriteInchesIntFromTwipsPref(GetPrefName(kUnwriteableMarginRight, aPrinterName),
+ unwriteableMargin.right);
+ DUMP_INT(kWriteStr, kUnwriteableMarginRight, unwriteableMargin.top);
+ }
+ }
+
+ // Paper size prefs are saved as a group
+ if (aFlags & nsIPrintSettings::kInitSavePaperSize) {
+ int16_t sizeUnit;
+ double width, height;
+ char16_t *name;
+
+ if (
+ NS_SUCCEEDED(aPS->GetPaperSizeUnit(&sizeUnit)) &&
+ NS_SUCCEEDED(aPS->GetPaperWidth(&width)) &&
+ NS_SUCCEEDED(aPS->GetPaperHeight(&height)) &&
+ NS_SUCCEEDED(aPS->GetPaperName(&name))
+ ) {
+ DUMP_INT(kWriteStr, kPrintPaperSizeUnit, sizeUnit);
+ Preferences::SetInt(GetPrefName(kPrintPaperSizeUnit, aPrinterName),
+ int32_t(sizeUnit));
+ DUMP_DBL(kWriteStr, kPrintPaperWidth, width);
+ WritePrefDouble(GetPrefName(kPrintPaperWidth, aPrinterName), width);
+ DUMP_DBL(kWriteStr, kPrintPaperHeight, height);
+ WritePrefDouble(GetPrefName(kPrintPaperHeight, aPrinterName), height);
+ DUMP_STR(kWriteStr, kPrintPaperName, name);
+ Preferences::SetString(GetPrefName(kPrintPaperName, aPrinterName), name);
+#if defined(XP_WIN)
+ // If the height and width are -1 then this might be a save triggered by
+ // print pref sanitizing code. This is done as a one off and is partly
+ // triggered by the existence of an old (now no longer set) pref. We
+ // remove that pref if it exists here, so that we don't try and sanitize
+ // what might be valid prefs. See bug 1276717 and bug 1369386 for details.
+ if (height == -1L && width == -1L) {
+ const char* paperSizeTypePref =
+ GetPrefName("print_paper_size_type", aPrinterName);
+ if (Preferences::HasUserValue(paperSizeTypePref)) {
+ Preferences::ClearUser(paperSizeTypePref);
+ }
+ }
+#endif
+ }
+ }
+
+ bool b;
+ char16_t* uStr;
+ int32_t iVal;
+ int16_t iVal16;
+ double dbl;
+
+ if (aFlags & nsIPrintSettings::kInitSaveOddEvenPages) {
+ if (NS_SUCCEEDED(aPS->GetPrintOptions(nsIPrintSettings::kPrintEvenPages,
+ &b))) {
+ DUMP_BOOL(kWriteStr, kPrintEvenPages, b);
+ Preferences::SetBool(GetPrefName(kPrintEvenPages, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveOddEvenPages) {
+ if (NS_SUCCEEDED(aPS->GetPrintOptions(nsIPrintSettings::kPrintOddPages,
+ &b))) {
+ DUMP_BOOL(kWriteStr, kPrintOddPages, b);
+ Preferences::SetBool(GetPrefName(kPrintOddPages, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderLeft) {
+ if (NS_SUCCEEDED(aPS->GetHeaderStrLeft(&uStr))) {
+ DUMP_STR(kWriteStr, kPrintHeaderStrLeft, uStr);
+ Preferences::SetString(GetPrefName(kPrintHeaderStrLeft, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderCenter) {
+ if (NS_SUCCEEDED(aPS->GetHeaderStrCenter(&uStr))) {
+ DUMP_STR(kWriteStr, kPrintHeaderStrCenter, uStr);
+ Preferences::SetString(GetPrefName(kPrintHeaderStrCenter, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveHeaderRight) {
+ if (NS_SUCCEEDED(aPS->GetHeaderStrRight(&uStr))) {
+ DUMP_STR(kWriteStr, kPrintHeaderStrRight, uStr);
+ Preferences::SetString(GetPrefName(kPrintHeaderStrRight, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterLeft) {
+ if (NS_SUCCEEDED(aPS->GetFooterStrLeft(&uStr))) {
+ DUMP_STR(kWriteStr, kPrintFooterStrLeft, uStr);
+ Preferences::SetString(GetPrefName(kPrintFooterStrLeft, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterCenter) {
+ if (NS_SUCCEEDED(aPS->GetFooterStrCenter(&uStr))) {
+ DUMP_STR(kWriteStr, kPrintFooterStrCenter, uStr);
+ Preferences::SetString(GetPrefName(kPrintFooterStrCenter, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveFooterRight) {
+ if (NS_SUCCEEDED(aPS->GetFooterStrRight(&uStr))) {
+ DUMP_STR(kWriteStr, kPrintFooterStrRight, uStr);
+ Preferences::SetString(GetPrefName(kPrintFooterStrRight, aPrinterName),
+ uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveBGColors) {
+ if (NS_SUCCEEDED(aPS->GetPrintBGColors(&b))) {
+ DUMP_BOOL(kWriteStr, kPrintBGColors, b);
+ Preferences::SetBool(GetPrefName(kPrintBGColors, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveBGImages) {
+ if (NS_SUCCEEDED(aPS->GetPrintBGImages(&b))) {
+ DUMP_BOOL(kWriteStr, kPrintBGImages, b);
+ Preferences::SetBool(GetPrefName(kPrintBGImages, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveReversed) {
+ if (NS_SUCCEEDED(aPS->GetPrintReversed(&b))) {
+ DUMP_BOOL(kWriteStr, kPrintReversed, b);
+ Preferences::SetBool(GetPrefName(kPrintReversed, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveInColor) {
+ if (NS_SUCCEEDED(aPS->GetPrintInColor(&b))) {
+ DUMP_BOOL(kWriteStr, kPrintInColor, b);
+ Preferences::SetBool(GetPrefName(kPrintInColor, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePaperData) {
+ if (NS_SUCCEEDED(aPS->GetPaperData(&iVal16))) {
+ DUMP_INT(kWriteStr, kPrintPaperData, iVal16);
+ Preferences::SetInt(GetPrefName(kPrintPaperData, aPrinterName),
+ int32_t(iVal16));
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveOrientation) {
+ if (NS_SUCCEEDED(aPS->GetOrientation(&iVal))) {
+ DUMP_INT(kWriteStr, kPrintOrientation, 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))) {
+ DUMP_STR(kWriteStr, kPrinterName, uStr);
+ Preferences::SetString(kPrinterName, uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePrintToFile) {
+ if (NS_SUCCEEDED(aPS->GetPrintToFile(&b))) {
+ DUMP_BOOL(kWriteStr, kPrintToFile, b);
+ Preferences::SetBool(GetPrefName(kPrintToFile, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveToFileName) {
+ if (NS_SUCCEEDED(aPS->GetToFileName(&uStr))) {
+ DUMP_STR(kWriteStr, kPrintToFileName, uStr);
+ Preferences::SetString(GetPrefName(kPrintToFileName, aPrinterName), uStr);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSavePageDelay) {
+ if (NS_SUCCEEDED(aPS->GetPrintPageDelay(&iVal))) {
+ DUMP_INT(kWriteStr, kPrintPageDelay, iVal);
+ Preferences::SetInt(GetPrefName(kPrintPageDelay, aPrinterName), iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveShrinkToFit) {
+ if (NS_SUCCEEDED(aPS->GetShrinkToFit(&b))) {
+ DUMP_BOOL(kWriteStr, kPrintShrinkToFit, b);
+ Preferences::SetBool(GetPrefName(kPrintShrinkToFit, aPrinterName), b);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveScaling) {
+ if (NS_SUCCEEDED(aPS->GetScaling(&dbl))) {
+ DUMP_DBL(kWriteStr, kPrintScaling, dbl);
+ WritePrefDouble(GetPrefName(kPrintScaling, aPrinterName), dbl);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveResolution) {
+ if (NS_SUCCEEDED(aPS->GetResolution(&iVal))) {
+ DUMP_INT(kWriteStr, kPrintResolution, iVal);
+ Preferences::SetInt(GetPrefName(kPrintResolution, aPrinterName), iVal);
+ }
+ }
+
+ if (aFlags & nsIPrintSettings::kInitSaveDuplex) {
+ if (NS_SUCCEEDED(aPS->GetDuplex(&iVal))) {
+ DUMP_INT(kWriteStr, kPrintDuplex, iVal);
+ Preferences::SetInt(GetPrefName(kPrintDuplex, aPrinterName), iVal);
+ }
+ }
+
+ // Not Writing Out:
+ // Number of Copies
+
+ return NS_OK;
+}
+
+nsresult nsPrintOptions::_CreatePrintSettings(nsIPrintSettings **_retval)
+{
+ // does not initially ref count
+ nsPrintSettings * printSettings = new nsPrintSettings();
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ADDREF(*_retval = printSettings); // ref count
+
+ nsXPIDLString printerName;
+ nsresult rv = GetDefaultPrinterName(getter_Copies(printerName));
+ NS_ENSURE_SUCCESS(rv, rv);
+ (*_retval)->SetPrinterName(printerName.get());
+
+ (void)InitPrintSettingsFromPrefs(*_retval, false,
+ nsIPrintSettings::kInitSaveAll);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintOptions::GetGlobalPrintSettings(nsIPrintSettings **aGlobalPrintSettings)
+{
+ nsresult rv;
+
+ rv = _CreatePrintSettings(getter_AddRefs(mGlobalPrintSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aGlobalPrintSettings = mGlobalPrintSettings.get());
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrintOptions::GetNewPrintSettings(nsIPrintSettings * *aNewPrintSettings)
+{
+ return _CreatePrintSettings(aNewPrintSettings);
+}
+
+NS_IMETHODIMP
+nsPrintOptions::GetDefaultPrinterName(char16_t * *aDefaultPrinterName)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrinterEnumerator> prtEnum =
+ do_GetService(NS_PRINTER_ENUMERATOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Look up the printer from the last print job
+ nsAutoString lastPrinterName;
+ Preferences::GetString(kPrinterName, &lastPrinterName);
+ if (!lastPrinterName.IsEmpty()) {
+ // Verify it's still a valid printer
+ nsCOMPtr<nsIStringEnumerator> printers;
+ rv = prtEnum->GetPrinterNameList(getter_AddRefs(printers));
+ if (NS_SUCCEEDED(rv)) {
+ bool isValid = false;
+ bool hasMore;
+ while (NS_SUCCEEDED(printers->HasMore(&hasMore)) && hasMore) {
+ nsAutoString printer;
+ if (NS_SUCCEEDED(printers->GetNext(printer)) && lastPrinterName.Equals(printer)) {
+ isValid = true;
+ break;
+ }
+ }
+ if (isValid) {
+ *aDefaultPrinterName = ToNewUnicode(lastPrinterName);
+ return NS_OK;
+ }
+ }
+ }
+
+ // There is no last printer preference, or it doesn't name a valid printer.
+ // Return the default from the printer enumeration.
+ return prtEnum->GetDefaultPrinterName(aDefaultPrinterName);
+}
+
+NS_IMETHODIMP
+nsPrintOptions::InitPrintSettingsFromPrinter(const char16_t *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);
+ NS_ENSURE_ARG_POINTER(aPrinterName);
+
+#ifdef DEBUG
+ nsXPIDLString printerName;
+ aPrintSettings->GetPrinterName(getter_Copies(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<nsIPrinterEnumerator> prtEnum =
+ do_GetService(NS_PRINTER_ENUMERATOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = prtEnum->InitPrintSettingsFromPrinter(aPrinterName, aPrintSettings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPrintSettings->SetIsInitializedFromPrinter(true);
+ return rv;
+}
+
+#ifndef MOZ_X11
+/** ---------------------------------------------------
+ * 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
+ char16_t* prtName = nullptr;
+
+ nsresult rv = aPS->GetPrinterName(&prtName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPrinterName = nsDependentString(prtName);
+
+ // Convert any whitespaces, carriage returns or newlines to _
+ // The below algorithm is supposedly faster than using iterators
+ NS_NAMED_LITERAL_STRING(replSubstr, "_");
+ 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;
+}
+#endif
+
+NS_IMETHODIMP
+nsPrintOptions::InitPrintSettingsFromPrefs(nsIPrintSettings* aPS,
+ bool aUsePNP, uint32_t aFlags)
+{
+ NS_ENSURE_ARG_POINTER(aPS);
+
+ bool isInitialized;
+ aPS->GetIsInitializedFromPrefs(&isInitialized);
+
+ if (isInitialized)
+ return NS_OK;
+
+ nsAutoString prtName;
+ // read any non printer specific prefs
+ // with empty printer name
+ nsresult rv = ReadPrefs(aPS, prtName, aFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Do not use printer name in Linux because GTK backend does not support
+ // per printer settings.
+#ifndef MOZ_X11
+ // 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);
+#endif
+
+ 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
+nsPrintOptions::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;
+
+ // Do not use printer name in Linux because GTK backend does not support
+ // per printer settings.
+#ifndef MOZ_X11
+ // Get the printer name from the PrinterSettings for an optional prefix.
+ nsresult rv = GetAdjustedPrinterName(aPS, aUsePrinterNamePrefix, prtName);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+
+ // Write the prefs, with or without a printer name prefix.
+ return WritePrefs(aPS, prtName, aFlags);
+}
+
+
+//-----------------------------------------------------
+//-- Protected Methods --------------------------------
+//-----------------------------------------------------
+nsresult
+nsPrintOptions::ReadPrefDouble(const char * aPrefId, double& aVal)
+{
+ NS_ENSURE_ARG_POINTER(aPrefId);
+
+ nsAutoCString str;
+ nsresult rv = Preferences::GetCString(aPrefId, &str);
+ if (NS_SUCCEEDED(rv) && !str.IsEmpty()) {
+ aVal = atof(str.get());
+ }
+ return rv;
+}
+
+nsresult
+nsPrintOptions::WritePrefDouble(const char * aPrefId, double aVal)
+{
+ NS_ENSURE_ARG_POINTER(aPrefId);
+
+ nsPrintfCString str("%6.2f", aVal);
+ NS_ENSURE_TRUE(!str.IsEmpty(), NS_ERROR_FAILURE);
+
+ return Preferences::SetCString(aPrefId, str);
+}
+
+void
+nsPrintOptions::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
+nsPrintOptions::WriteInchesFromTwipsPref(const char * aPrefId, int32_t aTwips)
+{
+ double inches = NS_TWIPS_TO_INCHES(aTwips);
+ nsAutoCString inchesStr;
+ inchesStr.AppendFloat(inches);
+
+ Preferences::SetCString(aPrefId, inchesStr);
+}
+
+void
+nsPrintOptions::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
+nsPrintOptions::WriteInchesIntFromTwipsPref(const char * aPrefId, int32_t aTwips)
+{
+ Preferences::SetInt(aPrefId,
+ int32_t(NS_TWIPS_TO_INCHES(aTwips) * 100.0f + 0.5f));
+}
+
+void
+nsPrintOptions::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
+nsPrintOptions::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
+}
+
+//----------------------------------------------------------------------
+// Testing of read/write prefs
+// This define turns on the testing module below
+// so at start up it writes and reads the prefs.
+#ifdef DEBUG_rods_X
+class Tester {
+ public:
+ Tester();
+};
+Tester::Tester()
+{
+ nsCOMPtr<nsIPrintSettings> ps;
+ nsresult rv;
+ nsCOMPtr<nsIPrintOptions> printService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = printService->CreatePrintSettings(getter_AddRefs(ps));
+ }
+
+ if (ps) {
+ ps->SetPrintOptions(nsIPrintSettings::kPrintOddPages, true);
+ ps->SetPrintOptions(nsIPrintSettings::kPrintEvenPages, false);
+ ps->SetMarginTop(1.0);
+ ps->SetMarginLeft(1.0);
+ ps->SetMarginBottom(1.0);
+ ps->SetMarginRight(1.0);
+ ps->SetScaling(0.5);
+ ps->SetPrintBGColors(true);
+ ps->SetPrintBGImages(true);
+ ps->SetPrintRange(15);
+ ps->SetHeaderStrLeft(NS_ConvertUTF8toUTF16("Left").get());
+ ps->SetHeaderStrCenter(NS_ConvertUTF8toUTF16("Center").get());
+ ps->SetHeaderStrRight(NS_ConvertUTF8toUTF16("Right").get());
+ ps->SetFooterStrLeft(NS_ConvertUTF8toUTF16("Left").get());
+ ps->SetFooterStrCenter(NS_ConvertUTF8toUTF16("Center").get());
+ ps->SetFooterStrRight(NS_ConvertUTF8toUTF16("Right").get());
+ ps->SetPaperName(NS_ConvertUTF8toUTF16("Paper Name").get());
+ ps->SetPaperData(1);
+ ps->SetPaperWidth(100.0);
+ ps->SetPaperHeight(50.0);
+ ps->SetPaperSizeUnit(nsIPrintSettings::kPaperSizeMillimeters);
+ ps->SetPrintReversed(true);
+ ps->SetPrintInColor(true);
+ ps->SetOrientation(nsIPrintSettings::kLandscapeOrientation);
+ ps->SetNumCopies(2);
+ ps->SetPrinterName(NS_ConvertUTF8toUTF16("Printer Name").get());
+ ps->SetPrintToFile(true);
+ ps->SetToFileName(NS_ConvertUTF8toUTF16("File Name").get());
+ ps->SetPrintPageDelay(1000);
+ ps->SetShrinkToFit(true);
+
+ struct SettingsType {
+ const char* mName;
+ uint32_t mFlag;
+ };
+ SettingsType gSettings[] = {
+ {"OddEven", nsIPrintSettings::kInitSaveOddEvenPages},
+ {kPrintHeaderStrLeft, nsIPrintSettings::kInitSaveHeaderLeft},
+ {kPrintHeaderStrCenter, nsIPrintSettings::kInitSaveHeaderCenter},
+ {kPrintHeaderStrRight, nsIPrintSettings::kInitSaveHeaderRight},
+ {kPrintFooterStrLeft, nsIPrintSettings::kInitSaveFooterLeft},
+ {kPrintFooterStrCenter, nsIPrintSettings::kInitSaveFooterCenter},
+ {kPrintFooterStrRight, nsIPrintSettings::kInitSaveFooterRight},
+ {kPrintBGColors, nsIPrintSettings::kInitSaveBGColors},
+ {kPrintBGImages, nsIPrintSettings::kInitSaveBGImages},
+ {kPrintShrinkToFit, nsIPrintSettings::kInitSaveShrinkToFit},
+ {kPrintPaperSize, nsIPrintSettings::kInitSavePaperSize},
+ {kPrintPaperData, nsIPrintSettings::kInitSavePaperData},
+ {kPrintReversed, nsIPrintSettings::kInitSaveReversed},
+ {kPrintInColor, nsIPrintSettings::kInitSaveInColor},
+ {kPrintOrientation, nsIPrintSettings::kInitSaveOrientation},
+ {kPrinterName, nsIPrintSettings::kInitSavePrinterName},
+ {kPrintToFile, nsIPrintSettings::kInitSavePrintToFile},
+ {kPrintToFileName, nsIPrintSettings::kInitSaveToFileName},
+ {kPrintPageDelay, nsIPrintSettings::kInitSavePageDelay},
+ {"Margins", nsIPrintSettings::kInitSaveMargins},
+ {"All", nsIPrintSettings::kInitSaveAll},
+ {nullptr, 0}};
+
+ nsString prefix; prefix.AssignLiteral("Printer Name");
+ int32_t i = 0;
+ while (gSettings[i].mName != nullptr) {
+ printf("------------------------------------------------\n");
+ printf("%d) %s -> 0x%X\n", i, gSettings[i].mName, gSettings[i].mFlag);
+ printService->SavePrintSettingsToPrefs(ps, true, gSettings[i].mFlag);
+ printService->InitPrintSettingsFromPrefs(ps, true,
+ gSettings[i].mFlag);
+ i++;
+ }
+ }
+
+}
+Tester gTester;
+#endif
diff --git a/widget/nsPrintOptionsImpl.h b/widget/nsPrintOptionsImpl.h
new file mode 100644
index 000000000..89ae391fc
--- /dev/null
+++ b/widget/nsPrintOptionsImpl.h
@@ -0,0 +1,93 @@
+/* -*- 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 nsPrintOptionsImpl_h__
+#define nsPrintOptionsImpl_h__
+
+#include "mozilla/embedding/PPrinting.h"
+#include "nsCOMPtr.h"
+#include "nsIPrintSettingsService.h"
+#include "nsString.h"
+#include "nsFont.h"
+
+class nsIPrintSettings;
+
+/**
+ * Class nsPrintOptions
+ */
+class nsPrintOptions : public nsIPrintSettingsService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTSETTINGSSERVICE
+
+ /**
+ * 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();
+
+ nsPrintOptions();
+
+protected:
+ virtual ~nsPrintOptions();
+
+ 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& aPrefName,
+ 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);
+
+ // Members
+ nsCOMPtr<nsIPrintSettings> mGlobalPrintSettings;
+
+ nsCString mPrefName;
+
+private:
+ // These are not supported and are not implemented!
+ nsPrintOptions(const nsPrintOptions& x);
+ nsPrintOptions& operator=(const nsPrintOptions& x);
+};
+
+#endif /* nsPrintOptionsImpl_h__ */
diff --git a/widget/nsPrintSession.cpp b/widget/nsPrintSession.cpp
new file mode 100644
index 000000000..9b334b424
--- /dev/null
+++ b/widget/nsPrintSession.cpp
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrintSession.h"
+
+#include "mozilla/layout/RemotePrintJobChild.h"
+
+typedef mozilla::layout::RemotePrintJobChild RemotePrintJobChild;
+
+//*****************************************************************************
+//*** nsPrintSession
+//*****************************************************************************
+
+NS_IMPL_ISUPPORTS(nsPrintSession, nsIPrintSession, nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+nsPrintSession::nsPrintSession()
+{
+}
+
+//-----------------------------------------------------------------------------
+nsPrintSession::~nsPrintSession()
+{
+}
+
+//-----------------------------------------------------------------------------
+nsresult nsPrintSession::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSession::GetRemotePrintJob(RemotePrintJobChild** aRemotePrintJob)
+{
+ MOZ_ASSERT(aRemotePrintJob);
+ RefPtr<RemotePrintJobChild> result = mRemotePrintJob;
+ result.forget(aRemotePrintJob);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSession::SetRemotePrintJob(RemotePrintJobChild* aRemotePrintJob)
+{
+ mRemotePrintJob = aRemotePrintJob;
+ return NS_OK;
+}
diff --git a/widget/nsPrintSession.h b/widget/nsPrintSession.h
new file mode 100644
index 000000000..cb349e76f
--- /dev/null
+++ b/widget/nsPrintSession.h
@@ -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/. */
+
+#ifndef nsPrintSession_h__
+#define nsPrintSession_h__
+
+#include "nsIPrintSession.h"
+
+#include "mozilla/RefPtr.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace layout {
+class RemotePrintJobChild;
+}
+}
+
+
+//*****************************************************************************
+//*** 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 000000000..48cce8625
--- /dev/null
+++ b/widget/nsPrintSettingsImpl.cpp
@@ -0,0 +1,1019 @@
+/* -*- 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 "nsReadableUtils.h"
+#include "nsIPrintSession.h"
+#include "mozilla/RefPtr.h"
+
+#define DEFAULT_MARGIN_WIDTH 0.5
+
+NS_IMPL_ISUPPORTS(nsPrintSettings, nsIPrintSettings)
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsImpl.h
+ * @update 6/21/00 dwc
+ */
+nsPrintSettings::nsPrintSettings() :
+ mPrintOptions(0L),
+ mPrintRange(kRangeAllPages),
+ mStartPageNum(1),
+ mEndPageNum(1),
+ mScaling(1.0),
+ mPrintBGColors(false),
+ mPrintBGImages(false),
+ mPrintFrameTypeUsage(kUseInternalDefault),
+ mPrintFrameType(kFramesAsIs),
+ mHowToEnableFrameUI(kFrameEnableNone),
+ mIsCancelled(false),
+ mPrintSilent(false),
+ mPrintPreview(false),
+ mShrinkToFit(true),
+ mShowPrintProgress(true),
+ mPrintPageDelay(50),
+ mPaperData(0),
+ mPaperWidth(8.5),
+ mPaperHeight(11.0),
+ mPaperSizeUnit(kPaperSizeInches),
+ mPrintReversed(false),
+ mPrintInColor(true),
+ mOrientation(kPortraitOrientation),
+ mNumCopies(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);
+
+ mPrintOptions = kPrintOddPages | kPrintEvenPages;
+
+ 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");
+
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsImpl.h
+ * @update 6/21/00 dwc
+ */
+nsPrintSettings::nsPrintSettings(const nsPrintSettings& aPS)
+{
+ *this = aPS;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsImpl.h
+ * @update 6/21/00 dwc
+ */
+nsPrintSettings::~nsPrintSettings()
+{
+}
+
+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::GetStartPageRange(int32_t *aStartPageRange)
+{
+ //NS_ENSURE_ARG_POINTER(aStartPageRange);
+ *aStartPageRange = mStartPageNum;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetStartPageRange(int32_t aStartPageRange)
+{
+ mStartPageNum = aStartPageRange;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetEndPageRange(int32_t *aEndPageRange)
+{
+ //NS_ENSURE_ARG_POINTER(aEndPageRange);
+ *aEndPageRange = mEndPageNum;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetEndPageRange(int32_t aEndPageRange)
+{
+ mEndPageNum = aEndPageRange;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintReversed(bool *aPrintReversed)
+{
+ //NS_ENSURE_ARG_POINTER(aPrintReversed);
+ *aPrintReversed = mPrintReversed;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintReversed(bool aPrintReversed)
+{
+ mPrintReversed = aPrintReversed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintInColor(bool *aPrintInColor)
+{
+ //NS_ENSURE_ARG_POINTER(aPrintInColor);
+ *aPrintInColor = mPrintInColor;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintInColor(bool aPrintInColor)
+{
+ mPrintInColor = aPrintInColor;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetOrientation(int32_t *aOrientation)
+{
+ NS_ENSURE_ARG_POINTER(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(char16_t * *aPrinter)
+{
+ NS_ENSURE_ARG_POINTER(aPrinter);
+
+ *aPrinter = ToNewUnicode(mPrinter);
+ NS_ENSURE_TRUE(*aPrinter, NS_ERROR_OUT_OF_MEMORY);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::SetPrinterName(const char16_t * aPrinter)
+{
+ if (!aPrinter || !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::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(char16_t * *aToFileName)
+{
+ //NS_ENSURE_ARG_POINTER(aToFileName);
+ *aToFileName = ToNewUnicode(mToFileName);
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetToFileName(const char16_t * aToFileName)
+{
+ if (aToFileName) {
+ mToFileName = aToFileName;
+ } else {
+ mToFileName.SetLength(0);
+ }
+ 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 = (bool)mIsInitedFromPrinter;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetIsInitializedFromPrinter(bool aIsInitializedFromPrinter)
+{
+ mIsInitedFromPrinter = (bool)aIsInitializedFromPrinter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetIsInitializedFromPrefs(bool *aInitializedFromPrefs)
+{
+ NS_ENSURE_ARG_POINTER(aInitializedFromPrefs);
+ *aInitializedFromPrefs = (bool)mIsInitedFromPrefs;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetIsInitializedFromPrefs(bool aInitializedFromPrefs)
+{
+ mIsInitedFromPrefs = (bool)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::GetPrintRange(int16_t *aPrintRange)
+{
+ NS_ENSURE_ARG_POINTER(aPrintRange);
+ *aPrintRange = mPrintRange;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintRange(int16_t aPrintRange)
+{
+ mPrintRange = aPrintRange;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetTitle(char16_t * *aTitle)
+{
+ NS_ENSURE_ARG_POINTER(aTitle);
+ if (!mTitle.IsEmpty()) {
+ *aTitle = ToNewUnicode(mTitle);
+ } else {
+ *aTitle = nullptr;
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetTitle(const char16_t * aTitle)
+{
+ if (aTitle) {
+ mTitle = aTitle;
+ } else {
+ mTitle.SetLength(0);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetDocURL(char16_t * *aDocURL)
+{
+ NS_ENSURE_ARG_POINTER(aDocURL);
+ if (!mURL.IsEmpty()) {
+ *aDocURL = ToNewUnicode(mURL);
+ } else {
+ *aDocURL = nullptr;
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetDocURL(const char16_t * aDocURL)
+{
+ if (aDocURL) {
+ mURL = aDocURL;
+ } else {
+ mURL.SetLength(0);
+ }
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsImpl.h
+ * @update 1/12/01 rods
+ */
+NS_IMETHODIMP
+nsPrintSettings::GetPrintOptions(int32_t aType, bool *aTurnOnOff)
+{
+ NS_ENSURE_ARG_POINTER(aTurnOnOff);
+ *aTurnOnOff = mPrintOptions & aType ? true : false;
+ return NS_OK;
+}
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsImpl.h
+ * @update 1/12/01 rods
+ */
+NS_IMETHODIMP
+nsPrintSettings::SetPrintOptions(int32_t aType, bool aTurnOnOff)
+{
+ if (aTurnOnOff) {
+ mPrintOptions |= aType;
+ } else {
+ mPrintOptions &= ~aType;
+ }
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsImpl.h
+ * @update 1/12/01 rods
+ */
+NS_IMETHODIMP
+nsPrintSettings::GetPrintOptionsBits(int32_t *aBits)
+{
+ NS_ENSURE_ARG_POINTER(aBits);
+ *aBits = mPrintOptions;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettings::SetPrintOptionsBits(int32_t aBits)
+{
+ mPrintOptions = aBits;
+ return NS_OK;
+}
+
+nsresult
+nsPrintSettings::GetMarginStrs(char16_t * *aTitle,
+ nsHeaderFooterEnum aType,
+ int16_t aJust)
+{
+ NS_ENSURE_ARG_POINTER(aTitle);
+ *aTitle = nullptr;
+ if (aType == eHeader) {
+ switch (aJust) {
+ case kJustLeft: *aTitle = ToNewUnicode(mHeaderStrs[0]);break;
+ case kJustCenter: *aTitle = ToNewUnicode(mHeaderStrs[1]);break;
+ case kJustRight: *aTitle = ToNewUnicode(mHeaderStrs[2]);break;
+ } //switch
+ } else {
+ switch (aJust) {
+ case kJustLeft: *aTitle = ToNewUnicode(mFooterStrs[0]);break;
+ case kJustCenter: *aTitle = ToNewUnicode(mFooterStrs[1]);break;
+ case kJustRight: *aTitle = ToNewUnicode(mFooterStrs[2]);break;
+ } //switch
+ }
+ return NS_OK;
+}
+
+nsresult
+nsPrintSettings::SetMarginStrs(const char16_t * aTitle,
+ nsHeaderFooterEnum aType,
+ int16_t aJust)
+{
+ NS_ENSURE_ARG_POINTER(aTitle);
+ if (aType == eHeader) {
+ switch (aJust) {
+ case kJustLeft: mHeaderStrs[0] = aTitle;break;
+ case kJustCenter: mHeaderStrs[1] = aTitle;break;
+ case kJustRight: mHeaderStrs[2] = aTitle;break;
+ } //switch
+ } else {
+ switch (aJust) {
+ case kJustLeft: mFooterStrs[0] = aTitle;break;
+ case kJustCenter: mFooterStrs[1] = aTitle;break;
+ case kJustRight: mFooterStrs[2] = aTitle;break;
+ } //switch
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetHeaderStrLeft(char16_t * *aTitle)
+{
+ return GetMarginStrs(aTitle, eHeader, kJustLeft);
+}
+NS_IMETHODIMP nsPrintSettings::SetHeaderStrLeft(const char16_t * aTitle)
+{
+ return SetMarginStrs(aTitle, eHeader, kJustLeft);
+}
+
+NS_IMETHODIMP nsPrintSettings::GetHeaderStrCenter(char16_t * *aTitle)
+{
+ return GetMarginStrs(aTitle, eHeader, kJustCenter);
+}
+NS_IMETHODIMP nsPrintSettings::SetHeaderStrCenter(const char16_t * aTitle)
+{
+ return SetMarginStrs(aTitle, eHeader, kJustCenter);
+}
+
+NS_IMETHODIMP nsPrintSettings::GetHeaderStrRight(char16_t * *aTitle)
+{
+ return GetMarginStrs(aTitle, eHeader, kJustRight);
+}
+NS_IMETHODIMP nsPrintSettings::SetHeaderStrRight(const char16_t * aTitle)
+{
+ return SetMarginStrs(aTitle, eHeader, kJustRight);
+}
+
+NS_IMETHODIMP nsPrintSettings::GetFooterStrLeft(char16_t * *aTitle)
+{
+ return GetMarginStrs(aTitle, eFooter, kJustLeft);
+}
+NS_IMETHODIMP nsPrintSettings::SetFooterStrLeft(const char16_t * aTitle)
+{
+ return SetMarginStrs(aTitle, eFooter, kJustLeft);
+}
+
+NS_IMETHODIMP nsPrintSettings::GetFooterStrCenter(char16_t * *aTitle)
+{
+ return GetMarginStrs(aTitle, eFooter, kJustCenter);
+}
+NS_IMETHODIMP nsPrintSettings::SetFooterStrCenter(const char16_t * aTitle)
+{
+ return SetMarginStrs(aTitle, eFooter, kJustCenter);
+}
+
+NS_IMETHODIMP nsPrintSettings::GetFooterStrRight(char16_t * *aTitle)
+{
+ return GetMarginStrs(aTitle, eFooter, kJustRight);
+}
+NS_IMETHODIMP nsPrintSettings::SetFooterStrRight(const char16_t * aTitle)
+{
+ return SetMarginStrs(aTitle, eFooter, kJustRight);
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintFrameTypeUsage(int16_t *aPrintFrameTypeUsage)
+{
+ NS_ENSURE_ARG_POINTER(aPrintFrameTypeUsage);
+ *aPrintFrameTypeUsage = mPrintFrameTypeUsage;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintFrameTypeUsage(int16_t aPrintFrameTypeUsage)
+{
+ mPrintFrameTypeUsage = aPrintFrameTypeUsage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPrintFrameType(int16_t *aPrintFrameType)
+{
+ NS_ENSURE_ARG_POINTER(aPrintFrameType);
+ *aPrintFrameType = (int32_t)mPrintFrameType;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPrintFrameType(int16_t aPrintFrameType)
+{
+ mPrintFrameType = aPrintFrameType;
+ 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::GetPaperName(char16_t * *aPaperName)
+{
+ NS_ENSURE_ARG_POINTER(aPaperName);
+ if (!mPaperName.IsEmpty()) {
+ *aPaperName = ToNewUnicode(mPaperName);
+ } else {
+ *aPaperName = nullptr;
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPaperName(const char16_t * aPaperName)
+{
+ if (aPaperName) {
+ mPaperName = aPaperName;
+ } else {
+ mPaperName.SetLength(0);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetHowToEnableFrameUI(int16_t *aHowToEnableFrameUI)
+{
+ NS_ENSURE_ARG_POINTER(aHowToEnableFrameUI);
+ *aHowToEnableFrameUI = mHowToEnableFrameUI;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetHowToEnableFrameUI(int16_t aHowToEnableFrameUI)
+{
+ mHowToEnableFrameUI = aHowToEnableFrameUI;
+ 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::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;
+}
+
+NS_IMETHODIMP nsPrintSettings::GetPaperData(int16_t *aPaperData)
+{
+ NS_ENSURE_ARG_POINTER(aPaperData);
+ *aPaperData = mPaperData;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettings::SetPaperData(int16_t aPaperData)
+{
+ mPaperData = aPaperData;
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsImpl.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;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsImpl.h
+ * @update 6/21/00 dwc
+ */
+NS_IMETHODIMP
+nsPrintSettings::GetMarginInTwips(nsIntMargin& aMargin)
+{
+ aMargin = mMargin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettings::GetEdgeInTwips(nsIntMargin& aEdge)
+{
+ aEdge = mEdge;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettings::GetUnwriteableMarginInTwips(nsIntMargin& aUnwriteableMargin)
+{
+ aUnwriteableMargin = mUnwriteableMargin;
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * Stub - platform-specific implementations can use this function.
+ */
+NS_IMETHODIMP
+nsPrintSettings::SetupSilentPrinting()
+{
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsImpl.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 {
+ *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;
+}
+
+NS_IMETHODIMP
+nsPrintSettings::GetPageRanges(nsTArray<int32_t> &aPages)
+{
+ aPages.Clear();
+ return NS_OK;
+}
+
+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;
+ }
+
+ mStartPageNum = rhs.mStartPageNum;
+ mEndPageNum = rhs.mEndPageNum;
+ mMargin = rhs.mMargin;
+ mEdge = rhs.mEdge;
+ mUnwriteableMargin = rhs.mUnwriteableMargin;
+ mScaling = rhs.mScaling;
+ mPrintBGColors = rhs.mPrintBGColors;
+ mPrintBGImages = rhs.mPrintBGImages;
+ mPrintRange = rhs.mPrintRange;
+ mTitle = rhs.mTitle;
+ mURL = rhs.mURL;
+ mHowToEnableFrameUI = rhs.mHowToEnableFrameUI;
+ mIsCancelled = rhs.mIsCancelled;
+ mPrintFrameTypeUsage = rhs.mPrintFrameTypeUsage;
+ mPrintFrameType = rhs.mPrintFrameType;
+ mPrintSilent = rhs.mPrintSilent;
+ mShrinkToFit = rhs.mShrinkToFit;
+ mShowPrintProgress = rhs.mShowPrintProgress;
+ mPaperName = rhs.mPaperName;
+ mPaperData = rhs.mPaperData;
+ mPaperWidth = rhs.mPaperWidth;
+ mPaperHeight = rhs.mPaperHeight;
+ mPaperSizeUnit = rhs.mPaperSizeUnit;
+ mPrintReversed = rhs.mPrintReversed;
+ mPrintInColor = rhs.mPrintInColor;
+ mOrientation = rhs.mOrientation;
+ mNumCopies = rhs.mNumCopies;
+ mPrinter = rhs.mPrinter;
+ mPrintToFile = rhs.mPrintToFile;
+ mToFileName = rhs.mToFileName;
+ mOutputFormat = rhs.mOutputFormat;
+ 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;
+}
+
diff --git a/widget/nsPrintSettingsImpl.h b/widget/nsPrintSettingsImpl.h
new file mode 100644
index 000000000..c28374da4
--- /dev/null
+++ b/widget/nsPrintSettingsImpl.h
@@ -0,0 +1,102 @@
+/* -*- 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 "nsMargin.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+
+#define NUM_HEAD_FOOT 3
+
+//*****************************************************************************
+//*** nsPrintSettings
+//*****************************************************************************
+
+class nsPrintSettings : public nsIPrintSettings
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTSETTINGS
+
+ nsPrintSettings();
+ nsPrintSettings(const nsPrintSettings& aPS);
+
+ nsPrintSettings& operator=(const nsPrintSettings& rhs);
+
+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;
+
+
+ nsresult GetMarginStrs(char16_t * *aTitle, nsHeaderFooterEnum aType, int16_t aJust);
+ nsresult SetMarginStrs(const char16_t * aTitle, nsHeaderFooterEnum aType, int16_t aJust);
+
+ // 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;
+
+ int32_t mPrintOptions;
+
+ // scriptable data members
+ int16_t mPrintRange;
+ int32_t mStartPageNum; // only used for ePrintRange_SpecifiedRange
+ int32_t mEndPageNum;
+ double mScaling;
+ bool mPrintBGColors; // print background colors
+ bool mPrintBGImages; // print background images
+
+ int16_t mPrintFrameTypeUsage;
+ int16_t mPrintFrameType;
+ int16_t mHowToEnableFrameUI;
+ bool mIsCancelled;
+ bool mPrintSilent;
+ bool mPrintPreview;
+ bool mShrinkToFit;
+ bool mShowPrintProgress;
+ int32_t mPrintPageDelay;
+
+ nsString mTitle;
+ nsString mURL;
+ nsString mPageNumberFormat;
+ nsString mHeaderStrs[NUM_HEAD_FOOT];
+ nsString mFooterStrs[NUM_HEAD_FOOT];
+
+ nsString mPaperName;
+ int16_t mPaperData;
+ 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;
+ nsXPIDLString mPrinter;
+ bool mPrintToFile;
+ nsString mToFileName;
+ int16_t mOutputFormat;
+ bool mIsInitedFromPrinter;
+ bool mIsInitedFromPrefs;
+};
+
+#endif /* nsPrintSettings_h__ */
diff --git a/widget/nsScreenManagerProxy.cpp b/widget/nsScreenManagerProxy.cpp
new file mode 100644
index 000000000..18f400ff5
--- /dev/null
+++ b/widget/nsScreenManagerProxy.cpp
@@ -0,0 +1,218 @@
+/* -*- 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/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsScreenManagerProxy.h"
+#include "nsServiceManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIAppShell.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "nsWidgetsCID.h"
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS(nsScreenManagerProxy, nsIScreenManager)
+
+nsScreenManagerProxy::nsScreenManagerProxy()
+ : mNumberOfScreens(-1)
+ , mSystemDefaultScale(1.0)
+ , mCacheValid(true)
+ , mCacheWillInvalidate(false)
+{
+ bool success = false;
+ Unused << ContentChild::GetSingleton()->SendPScreenManagerConstructor(
+ this,
+ &mNumberOfScreens,
+ &mSystemDefaultScale,
+ &success);
+
+ if (!success) {
+ // We're in bad shape. We'll return the default values, but we'll basically
+ // be lying.
+ NS_WARNING("Setting up communications with the parent nsIScreenManager failed.");
+ }
+
+ InvalidateCacheOnNextTick();
+
+ // nsScreenManagerProxy is a service, which will always have a reference
+ // held to it by the Component Manager once the service is requested.
+ // However, nsScreenManagerProxy also implements PScreenManagerChild, and
+ // that means that the manager of the PScreenManager protocol (PContent
+ // in this case) needs to know how to deallocate this actor. We AddRef here
+ // so that in the event that PContent tries to deallocate us either before
+ // or after process shutdown, we don't try to do a double-free.
+ AddRef();
+}
+
+/**
+ * nsIScreenManager
+ **/
+
+NS_IMETHODIMP
+nsScreenManagerProxy::GetPrimaryScreen(nsIScreen** outScreen)
+{
+ InvalidateCacheOnNextTick();
+
+ if (!mPrimaryScreen) {
+ ScreenDetails details;
+ bool success = false;
+ Unused << SendGetPrimaryScreen(&details, &success);
+ if (!success) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPrimaryScreen = new ScreenProxy(this, details);
+ }
+ NS_ADDREF(*outScreen = mPrimaryScreen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerProxy::ScreenForId(uint32_t aId, nsIScreen** outScreen)
+{
+ // At this time, there's no need for child processes to query for
+ // screens by ID.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsScreenManagerProxy::ScreenForRect(int32_t inLeft,
+ int32_t inTop,
+ int32_t inWidth,
+ int32_t inHeight,
+ nsIScreen** outScreen)
+{
+ bool success = false;
+ ScreenDetails details;
+ Unused << SendScreenForRect(inLeft, inTop, inWidth, inHeight, &details, &success);
+ if (!success) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<ScreenProxy> screen = new ScreenProxy(this, details);
+ NS_ADDREF(*outScreen = screen);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerProxy::ScreenForNativeWidget(void* aWidget,
+ nsIScreen** outScreen)
+{
+ // Because ScreenForNativeWidget can be called numerous times
+ // indirectly from content via the DOM Screen API, we cache the
+ // results for this tick of the event loop.
+ TabChild* tabChild = static_cast<TabChild*>(aWidget);
+
+ // Enumerate the cached screen array, looking for one that has
+ // the TabChild that we're looking for...
+ for (uint32_t i = 0; i < mScreenCache.Length(); ++i) {
+ ScreenCacheEntry& curr = mScreenCache[i];
+ if (curr.mTabChild == aWidget) {
+ NS_ADDREF(*outScreen = static_cast<nsIScreen*>(curr.mScreenProxy));
+ return NS_OK;
+ }
+ }
+
+ // Never cached this screen, so we have to ask the parent process
+ // for it.
+ bool success = false;
+ ScreenDetails details;
+ Unused << SendScreenForBrowser(tabChild->GetTabId(), &details, &success);
+ if (!success) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ScreenCacheEntry newEntry;
+ RefPtr<ScreenProxy> screen = new ScreenProxy(this, details);
+
+ newEntry.mScreenProxy = screen;
+ newEntry.mTabChild = tabChild;
+
+ mScreenCache.AppendElement(newEntry);
+
+ NS_ADDREF(*outScreen = screen);
+
+ InvalidateCacheOnNextTick();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerProxy::GetNumberOfScreens(uint32_t* aNumberOfScreens)
+{
+ if (!EnsureCacheIsValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aNumberOfScreens = mNumberOfScreens;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerProxy::GetSystemDefaultScale(float *aSystemDefaultScale)
+{
+ if (!EnsureCacheIsValid()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aSystemDefaultScale = mSystemDefaultScale;
+ return NS_OK;
+}
+
+bool
+nsScreenManagerProxy::EnsureCacheIsValid()
+{
+ if (mCacheValid) {
+ return true;
+ }
+
+ bool success = false;
+ // Kick off a synchronous IPC call to the parent to get the
+ // most up-to-date information.
+ Unused << SendRefresh(&mNumberOfScreens, &mSystemDefaultScale, &success);
+ if (!success) {
+ NS_WARNING("Refreshing nsScreenManagerProxy failed in the parent process.");
+ return false;
+ }
+
+ mCacheValid = true;
+
+ InvalidateCacheOnNextTick();
+ return true;
+}
+
+void
+nsScreenManagerProxy::InvalidateCacheOnNextTick()
+{
+ if (mCacheWillInvalidate) {
+ return;
+ }
+
+ mCacheWillInvalidate = true;
+
+ nsContentUtils::RunInStableState(NewRunnableMethod(this, &nsScreenManagerProxy::InvalidateCache));
+}
+
+void
+nsScreenManagerProxy::InvalidateCache()
+{
+ mCacheValid = false;
+ mCacheWillInvalidate = false;
+
+ if (mPrimaryScreen) {
+ mPrimaryScreen = nullptr;
+ }
+ for (int32_t i = mScreenCache.Length() - 1; i >= 0; --i) {
+ mScreenCache.RemoveElementAt(i);
+ }
+}
+
diff --git a/widget/nsScreenManagerProxy.h b/widget/nsScreenManagerProxy.h
new file mode 100644
index 000000000..bcfa387a4
--- /dev/null
+++ b/widget/nsScreenManagerProxy.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 nsScreenManagerProxy_h
+#define nsScreenManagerProxy_h
+
+#include "nsIScreenManager.h"
+#include "mozilla/dom/PScreenManagerChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "ScreenProxy.h"
+
+/**
+ * The nsScreenManagerProxy is used by the content process to get
+ * information about system screens. It uses the PScreenManager protocol
+ * to communicate with a PScreenManagerParent to get this information,
+ * and also caches the information it gets back.
+ *
+ * We cache both the system screen information that nsIScreenManagers
+ * provide, as well as the nsIScreens that callers can query for.
+ *
+ * Both of these caches are invalidated on the next tick of the event
+ * loop.
+ */
+class nsScreenManagerProxy final : public nsIScreenManager,
+ public mozilla::dom::PScreenManagerChild
+{
+public:
+ nsScreenManagerProxy();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+private:
+ ~nsScreenManagerProxy() {};
+
+ bool EnsureCacheIsValid();
+ void InvalidateCacheOnNextTick();
+ void InvalidateCache();
+
+ uint32_t mNumberOfScreens;
+ float mSystemDefaultScale;
+ bool mCacheValid;
+ bool mCacheWillInvalidate;
+
+ RefPtr<mozilla::widget::ScreenProxy> mPrimaryScreen;
+
+ // nsScreenManagerProxy caches the results to repeated calls to
+ // ScreenForNativeWidget, which can be triggered indirectly by
+ // web content using the DOM Screen API. This allows us to bypass
+ // a lot of IPC traffic.
+ //
+ // The cache stores ScreenProxy's mapped to the TabChild that
+ // asked for the ScreenForNativeWidget was called with via
+ // ScreenCacheEntry's. The cache is cleared on the next tick of
+ // the event loop.
+ struct ScreenCacheEntry
+ {
+ RefPtr<mozilla::widget::ScreenProxy> mScreenProxy;
+ RefPtr<mozilla::dom::TabChild> mTabChild;
+ };
+
+ nsTArray<ScreenCacheEntry> mScreenCache;
+};
+
+#endif // nsScreenManagerProxy_h
diff --git a/widget/nsShmImage.cpp b/widget/nsShmImage.cpp
new file mode 100644
index 000000000..25d0a8c4b
--- /dev/null
+++ b/widget/nsShmImage.cpp
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <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);
+
+ 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;
+
+ 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 000000000..dae09c6c8
--- /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/nsTransferable.cpp b/widget/nsTransferable.cpp
new file mode 100644
index 000000000..e99d45401
--- /dev/null
+++ b/widget/nsTransferable.cpp
@@ -0,0 +1,664 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsArray.h"
+#include "nsArrayUtils.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsTArray.h"
+#include "nsIFormatConverter.h"
+#include "nsIContentPolicy.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsMemory.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsXPIDLString.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryService.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsIDOMNode.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIFile.h"
+#include "nsILoadContext.h"
+#include "mozilla/UniquePtr.h"
+
+NS_IMPL_ISUPPORTS(nsTransferable, nsITransferable)
+
+size_t GetDataForFlavor (const nsTArray<DataStruct>& aArray,
+ const char* aDataFlavor)
+{
+ for (size_t i = 0 ; i < aArray.Length () ; ++i) {
+ if (aArray[i].GetFlavor().Equals (aDataFlavor))
+ return i;
+ }
+
+ return aArray.NoIndex;
+}
+
+//-------------------------------------------------------------------------
+DataStruct::~DataStruct()
+{
+ if (mCacheFileName) free(mCacheFileName);
+}
+
+//-------------------------------------------------------------------------
+void
+DataStruct::SetData ( nsISupports* aData, uint32_t aDataLen, 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
+ if (aDataLen > kLargeDatasetSize && !aIsPrivateData) {
+ // if so, cache it to disk instead of memory
+ if ( NS_SUCCEEDED(WriteCache(aData, aDataLen)) )
+ return;
+ else
+ NS_WARNING("Oh no, couldn't write data to the cache file");
+ }
+
+ mData = aData;
+ mDataLen = aDataLen;
+}
+
+
+//-------------------------------------------------------------------------
+void
+DataStruct::GetData ( nsISupports** aData, uint32_t *aDataLen )
+{
+ // check here to see if the data is cached on disk
+ if ( !mData && mCacheFileName ) {
+ // if so, read it in and pass it back
+ // ReadCache creates memory and copies the data into it.
+ if ( NS_SUCCEEDED(ReadCache(aData, aDataLen)) )
+ return;
+ else {
+ // oh shit, something went horribly wrong here.
+ NS_WARNING("Oh no, couldn't read data in from the cache file");
+ *aData = nullptr;
+ *aDataLen = 0;
+ return;
+ }
+ }
+
+ *aData = mData;
+ if ( mData )
+ NS_ADDREF(*aData);
+ *aDataLen = mDataLen;
+}
+
+
+//-------------------------------------------------------------------------
+already_AddRefed<nsIFile>
+DataStruct::GetFileSpec(const char* aFileName)
+{
+ nsCOMPtr<nsIFile> cacheFile;
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(cacheFile));
+
+ if (!cacheFile)
+ return nullptr;
+
+ // if the param aFileName contains a name we should use that
+ // because the file probably already exists
+ // otherwise create a unique name
+ if (!aFileName) {
+ cacheFile->AppendNative(NS_LITERAL_CSTRING("clipboardcache"));
+ nsresult rv = cacheFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv))
+ return nullptr;
+ } else {
+ cacheFile->AppendNative(nsDependentCString(aFileName));
+ }
+
+ return cacheFile.forget();
+}
+
+
+//-------------------------------------------------------------------------
+nsresult
+DataStruct::WriteCache(nsISupports* aData, uint32_t aDataLen)
+{
+ // Get a new path and file to the temp directory
+ nsCOMPtr<nsIFile> cacheFile = GetFileSpec(mCacheFileName);
+ if (cacheFile) {
+ // remember the file name
+ if (!mCacheFileName) {
+ nsXPIDLCString fName;
+ cacheFile->GetNativeLeafName(fName);
+ mCacheFileName = strdup(fName);
+ }
+
+ // write out the contents of the clipboard
+ // to the file
+ //uint32_t bytes;
+ nsCOMPtr<nsIOutputStream> outStr;
+
+ NS_NewLocalFileOutputStream(getter_AddRefs(outStr),
+ cacheFile);
+
+ if (!outStr) return NS_ERROR_FAILURE;
+
+ void* buff = nullptr;
+ nsPrimitiveHelpers::CreateDataFromPrimitive ( mFlavor.get(), aData, &buff, aDataLen );
+ if ( buff ) {
+ uint32_t ignored;
+ outStr->Write(reinterpret_cast<char*>(buff), aDataLen, &ignored);
+ free(buff);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+
+//-------------------------------------------------------------------------
+nsresult
+DataStruct::ReadCache(nsISupports** aData, uint32_t* aDataLen)
+{
+ // if we don't have a cache filename we are out of luck
+ if (!mCacheFileName)
+ return NS_ERROR_FAILURE;
+
+ // get the path and file name
+ nsCOMPtr<nsIFile> cacheFile = GetFileSpec(mCacheFileName);
+ bool exists;
+ if ( cacheFile && NS_SUCCEEDED(cacheFile->Exists(&exists)) && exists ) {
+ // get the size of the file
+ int64_t fileSize;
+ int64_t max32 = 0xFFFFFFFF;
+ cacheFile->GetFileSize(&fileSize);
+ if (fileSize > max32)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t size = uint32_t(fileSize);
+ // create new memory for the large clipboard data
+ auto data = mozilla::MakeUnique<char[]>(size);
+ if ( !data )
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // now read it all in
+ nsCOMPtr<nsIInputStream> inStr;
+ NS_NewLocalFileInputStream( getter_AddRefs(inStr),
+ cacheFile);
+
+ if (!cacheFile) return NS_ERROR_FAILURE;
+
+ nsresult rv = inStr->Read(data.get(), fileSize, aDataLen);
+
+ // make sure we got all the data ok
+ if (NS_SUCCEEDED(rv) && *aDataLen == size) {
+ nsPrimitiveHelpers::CreatePrimitiveForData(mFlavor.get(), data.get(),
+ fileSize, aData);
+ return *aData ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ // zero the return params
+ *aData = nullptr;
+ *aDataLen = 0;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+//-------------------------------------------------------------------------
+//
+// Transferable constructor
+//
+//-------------------------------------------------------------------------
+nsTransferable::nsTransferable()
+ : mPrivateData(false)
+ , mContentPolicyType(nsIContentPolicy::TYPE_OTHER)
+#ifdef DEBUG
+ , mInitialized(false)
+#endif
+{
+}
+
+//-------------------------------------------------------------------------
+//
+// Transferable destructor
+//
+//-------------------------------------------------------------------------
+nsTransferable::~nsTransferable()
+{
+}
+
+
+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. This list consists of
+// nsISupportsCString objects so that the flavor list can be accessed from JS.
+//
+already_AddRefed<nsIMutableArray>
+nsTransferable::GetTransferDataFlavors()
+{
+ MOZ_ASSERT(mInitialized);
+
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+
+ for (size_t i = 0; i < mDataArray.Length(); ++i) {
+ DataStruct& data = mDataArray.ElementAt(i);
+ nsCOMPtr<nsISupportsCString> flavorWrapper = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
+ if ( flavorWrapper ) {
+ flavorWrapper->SetData ( data.GetFlavor() );
+ nsCOMPtr<nsISupports> genericWrapper ( do_QueryInterface(flavorWrapper) );
+ array->AppendElement( genericWrapper, /*weak =*/ false );
+ }
+ }
+
+ return array.forget();
+}
+
+
+//
+// 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, uint32_t *aDataLen)
+{
+ MOZ_ASSERT(mInitialized);
+
+ NS_ENSURE_ARG_POINTER(aFlavor && aData && aDataLen);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISupports> savedData;
+
+ // first look and see if the data is present in one of the intrinsic flavors
+ for (size_t i = 0; i < mDataArray.Length(); ++i) {
+ DataStruct& data = mDataArray.ElementAt(i);
+ if ( data.GetFlavor().Equals(aFlavor) ) {
+ nsCOMPtr<nsISupports> dataBytes;
+ uint32_t len;
+ data.GetData(getter_AddRefs(dataBytes), &len);
+ if (len == kFlavorHasDataProvider && dataBytes) {
+ // do we have a data provider?
+ nsCOMPtr<nsIFlavorDataProvider> dataProvider = do_QueryInterface(dataBytes);
+ if (dataProvider) {
+ rv = dataProvider->GetFlavorData(this, aFlavor,
+ getter_AddRefs(dataBytes), &len);
+ if (NS_FAILED(rv))
+ break; // the provider failed. fall into the converter code below.
+ }
+ }
+ if (dataBytes && len > 0) { // XXXmats why is zero length not ok?
+ *aDataLen = len;
+ dataBytes.forget(aData);
+ return NS_OK;
+ }
+ savedData = dataBytes; // return this if format converter fails
+ break;
+ }
+ }
+
+ bool found = false;
+
+ // 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;
+ uint32_t len;
+ data.GetData(getter_AddRefs(dataBytes), &len);
+ if (len == kFlavorHasDataProvider && dataBytes) {
+ // do we have a data provider?
+ nsCOMPtr<nsIFlavorDataProvider> dataProvider = do_QueryInterface(dataBytes);
+ if (dataProvider) {
+ rv = dataProvider->GetFlavorData(this, aFlavor,
+ getter_AddRefs(dataBytes), &len);
+ if (NS_FAILED(rv))
+ break; // give up
+ }
+ }
+ mFormatConv->Convert(data.GetFlavor().get(), dataBytes, len, aFlavor, aData, aDataLen);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ // for backward compatibility
+ if (!found) {
+ savedData.forget(aData);
+ *aDataLen = 0;
+ }
+
+ return found ? NS_OK : 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,
+ uint32_t *aDataLen)
+{
+ MOZ_ASSERT(mInitialized);
+
+ NS_ENSURE_ARG_POINTER(aData && aDataLen);
+
+ for (size_t i = 0; i < mDataArray.Length(); ++i) {
+ DataStruct& data = mDataArray.ElementAt(i);
+ if (data.IsDataAvailable()) {
+ aFlavor.Assign(data.GetFlavor());
+ data.GetData(aData, aDataLen);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+//
+// SetTransferData
+//
+//
+//
+NS_IMETHODIMP
+nsTransferable::SetTransferData(const char *aFlavor, nsISupports *aData, uint32_t aDataLen)
+{
+ MOZ_ASSERT(mInitialized);
+
+ NS_ENSURE_ARG(aFlavor);
+
+ // first check our intrinsic flavors to see if one has been registered.
+ for (size_t i = 0; i < mDataArray.Length(); ++i) {
+ DataStruct& data = mDataArray.ElementAt(i);
+ if ( data.GetFlavor().Equals(aFlavor) ) {
+ data.SetData ( aData, aDataLen, 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;
+ uint32_t ConvertedLen;
+ mFormatConv->Convert(aFlavor, aData, aDataLen, data.GetFlavor().get(), getter_AddRefs(ConvertedData), &ConvertedLen);
+ data.SetData(ConvertedData, ConvertedLen, mPrivateData);
+ return NS_OK;
+ }
+ }
+ }
+
+ // Can't set data neither directly nor through converter. Just add this flavor and try again
+ nsresult result = NS_ERROR_FAILURE;
+ if ( NS_SUCCEEDED(AddDataFlavor(aFlavor)) )
+ result = SetTransferData (aFlavor, aData, aDataLen);
+
+ return result;
+}
+
+
+//
+// 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 (GetDataForFlavor (mDataArray, aDataFlavor) != mDataArray.NoIndex)
+ 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);
+
+ size_t idx = GetDataForFlavor(mDataArray, aDataFlavor);
+ if (idx != mDataArray.NoIndex) {
+ mDataArray.RemoveElementAt (idx);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+
+/**
+ *
+ *
+ */
+NS_IMETHODIMP
+nsTransferable::IsLargeDataSet(bool *_retval)
+{
+ MOZ_ASSERT(mInitialized);
+
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = false;
+ return NS_OK;
+}
+
+
+/**
+ *
+ *
+ */
+NS_IMETHODIMP nsTransferable::SetConverter(nsIFormatConverter * aConverter)
+{
+ MOZ_ASSERT(mInitialized);
+
+ mFormatConv = aConverter;
+ return NS_OK;
+}
+
+
+/**
+ *
+ *
+ */
+NS_IMETHODIMP nsTransferable::GetConverter(nsIFormatConverter * *aConverter)
+{
+ MOZ_ASSERT(mInitialized);
+
+ NS_ENSURE_ARG_POINTER(aConverter);
+ *aConverter = mFormatConv;
+ NS_IF_ADDREF(*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(nsIArray **_retval)
+{
+ MOZ_ASSERT(mInitialized);
+
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // 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.
+ nsCOMPtr<nsIMutableArray> array = GetTransferDataFlavors();
+ nsCOMPtr<nsIFormatConverter> converter;
+ GetConverter(getter_AddRefs(converter));
+ if ( converter ) {
+ nsCOMPtr<nsIArray> convertedList;
+ converter->GetInputDataFlavors(getter_AddRefs(convertedList));
+
+ if ( convertedList ) {
+ uint32_t importListLen;
+ convertedList->GetLength(&importListLen);
+
+ for (uint32_t i = 0; i < importListLen; ++i ) {
+ nsCOMPtr<nsISupportsCString> flavorWrapper =
+ do_QueryElementAt(convertedList, i);
+ nsAutoCString flavorStr;
+ flavorWrapper->GetData( flavorStr );
+
+ if (GetDataForFlavor (mDataArray, flavorStr.get())
+ == mDataArray.NoIndex) // Don't append if already in intrinsic list
+ array->AppendElement (flavorWrapper, /*weak =*/ false);
+ } // foreach flavor that can be converted to
+ }
+ } // if a converter exists
+
+ array.forget(_retval);
+ return NS_OK;
+} // FlavorsTransferableCanImport
+
+
+//
+// FlavorsTransferableCanExport
+//
+// Computes a list of flavors that the transferable can export, either through
+// intrinsic knowledge or output data converters.
+//
+NS_IMETHODIMP
+nsTransferable::FlavorsTransferableCanExport(nsIArray **_retval)
+{
+ MOZ_ASSERT(mInitialized);
+
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // 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.
+ nsCOMPtr<nsIMutableArray> array = GetTransferDataFlavors();
+ nsCOMPtr<nsIFormatConverter> converter;
+ GetConverter(getter_AddRefs(converter));
+ if ( converter ) {
+ nsCOMPtr<nsIArray> convertedList;
+ converter->GetOutputDataFlavors(getter_AddRefs(convertedList));
+
+ if ( convertedList ) {
+ uint32_t importListLen;
+ convertedList->GetLength(&importListLen);
+
+ for ( uint32_t i=0; i < importListLen; ++i ) {
+ nsCOMPtr<nsISupportsCString> flavorWrapper =
+ do_QueryElementAt(convertedList, i);
+ nsAutoCString flavorStr;
+ flavorWrapper->GetData( flavorStr );
+
+ if (GetDataForFlavor (mDataArray, flavorStr.get())
+ == mDataArray.NoIndex) // Don't append if already in intrinsic list
+ array->AppendElement (flavorWrapper, /*weak =*/ false);
+ } // foreach flavor that can be converted to
+ }
+ } // if a converter exists
+
+ array.forget(_retval);
+ return NS_OK;
+} // FlavorsTransferableCanExport
+
+NS_IMETHODIMP
+nsTransferable::GetIsPrivateData(bool *aIsPrivateData)
+{
+ MOZ_ASSERT(mInitialized);
+
+ NS_ENSURE_ARG_POINTER(aIsPrivateData);
+
+ *aIsPrivateData = mPrivateData;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTransferable::SetIsPrivateData(bool aIsPrivateData)
+{
+ MOZ_ASSERT(mInitialized);
+
+ mPrivateData = aIsPrivateData;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTransferable::GetRequestingPrincipal(nsIPrincipal** outRequestingPrincipal)
+{
+ NS_IF_ADDREF(*outRequestingPrincipal = mRequestingPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTransferable::SetRequestingPrincipal(nsIPrincipal* aRequestingPrincipal)
+{
+ mRequestingPrincipal = aRequestingPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTransferable::GetContentPolicyType(nsContentPolicyType* outContentPolicyType)
+{
+ NS_ENSURE_ARG_POINTER(outContentPolicyType);
+ *outContentPolicyType = mContentPolicyType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTransferable::SetContentPolicyType(nsContentPolicyType aContentPolicyType)
+{
+ mContentPolicyType = aContentPolicyType;
+ return NS_OK;
+}
diff --git a/widget/nsTransferable.h b/widget/nsTransferable.h
new file mode 100644
index 000000000..d0db18a46
--- /dev/null
+++ b/widget/nsTransferable.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIContentPolicyBase.h"
+#include "nsIFormatConverter.h"
+#include "nsITransferable.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIPrincipal.h"
+
+class nsIMutableArray;
+class nsString;
+
+//
+// DataStruct
+//
+// Holds a flavor (a mime type) that describes the data and the associated data.
+//
+struct DataStruct
+{
+ explicit DataStruct ( const char* aFlavor )
+ : mDataLen(0), mFlavor(aFlavor), mCacheFileName(nullptr) { }
+ ~DataStruct();
+
+ const nsCString& GetFlavor() const { return mFlavor; }
+ void SetData( nsISupports* inData, uint32_t inDataLen, bool aIsPrivateData );
+ void GetData( nsISupports** outData, uint32_t *outDataLen );
+ already_AddRefed<nsIFile> GetFileSpec(const char* aFileName);
+ bool IsDataAvailable() const { return (mData && mDataLen > 0) || (!mData && mCacheFileName); }
+
+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(nsISupports* aData, uint32_t aDataLen );
+ nsresult ReadCache(nsISupports** aData, uint32_t* aDataLen );
+
+ nsCOMPtr<nsISupports> mData; // OWNER - some varient of primitive wrapper
+ uint32_t mDataLen;
+ const nsCString mFlavor;
+ char * mCacheFileName;
+
+};
+
+/**
+ * XP Transferable wrapper
+ */
+
+class nsTransferable : public nsITransferable
+{
+public:
+
+ nsTransferable();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITRANSFERABLE
+
+protected:
+ virtual ~nsTransferable();
+
+ // get flavors w/out converter
+ already_AddRefed<nsIMutableArray> GetTransferDataFlavors();
+
+ nsTArray<DataStruct> mDataArray;
+ nsCOMPtr<nsIFormatConverter> mFormatConv;
+ bool mPrivateData;
+ nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
+ nsContentPolicyType mContentPolicyType;
+#if DEBUG
+ bool mInitialized;
+#endif
+
+};
+
+#endif // nsTransferable_h__
diff --git a/widget/nsWidgetInitData.h b/widget/nsWidgetInitData.h
new file mode 100644
index 000000000..9b91f8dda
--- /dev/null
+++ b/widget/nsWidgetInitData.h
@@ -0,0 +1,136 @@
+/* -*- 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_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)
+ eWindowType_sheet, // MacOSX sheet (special dialog class)
+};
+
+/**
+ * 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),
+ mUnicode(true),
+ mRTL(false),
+ mNoAutoHide(false),
+ mIsDragPopup(false),
+ mIsAnimationSuppressed(false),
+ mSupportTranslucency(false),
+ mMouseTransparent(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 mUnicode;
+ 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;
+};
+
+#endif // nsWidgetInitData_h__
diff --git a/widget/nsWidgetsCID.h b/widget/nsWidgetsCID.h
new file mode 100644
index 000000000..2589b59e0
--- /dev/null
+++ b/widget/nsWidgetsCID.h
@@ -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/. */
+
+/* 2d96b3d0-c051-11d1-a827-0040959a28c9 */
+#define NS_WINDOW_CID \
+{ 0x2d96b3d0, 0xc051, 0x11d1, \
+ {0xa8, 0x27, 0x00, 0x40, 0x95, 0x9a, 0x28, 0xc9}}
+
+/* 2d96b3d1-c051-11d1-a827-0040959a28c9 */
+#define NS_CHILD_CID \
+{ 0x2d96b3d1, 0xc051, 0x11d1, \
+ {0xa8, 0x27, 0x00, 0x40, 0x95, 0x9a, 0x28, 0xc9} }
+
+/* BA7DE611-6088-11d3-A83E-00105A183419 */
+#define NS_POPUP_CID \
+{ 0xba7de611, 0x6088, 0x11d3, \
+ { 0xa8, 0x3e, 0x0, 0x10, 0x5a, 0x18, 0x34, 0x19 } }
+
+/* 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 } }
+
+/* 0ca832f8-978a-4dc7-a57d-adb803925d39 */
+#define NS_DATEPICKER_CID \
+{ 0x0ca832f8, 0x978a, 0x4dc7, \
+ { 0xa5, 0x7d, 0xad, 0xb8, 0x03, 0x92, 0x5d, 0x39 } }
+
+/* 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} }
+
+/* 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 } }
+
+// {b6e1a890-b2b8-4883-a65f-9476f6185313}
+#define NS_MACSYSTEMSTATUSBAR_CID \
+{ 0xb6e1a890, 0xb2b8, 0x4883, \
+ { 0xa6, 0x5f, 0x94, 0x76, 0xf6, 0x18, 0x53, 0x13 } }
+
+//-----------------------------------------------------------
+//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 } }
+
+// {9f1800ab-f428-4207-b40c-e832e77b01fc}
+#define NS_BIDIKEYBOARD_CID \
+{ 0x9f1800ab, 0xf428, 0x4207, { 0xb4, 0x0c, 0xe8, 0x32, 0xe7, 0x7b, 0x01, 0xfc } }
+
+#define PUPPETBIDIKEYBOARD_CID \
+{ 0x689e2586, 0x0344, 0x40b2, {0x83, 0x75, 0x13, 0x67, 0x2d, 0x3b, 0x71, 0x9a } }
+
+#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 } }
+
+//-----------------------------------------------------------
+//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 } }
+
+// NOTE: This now has the same CID as NS_PRINTSETTINGSSERVICE_CID
+// will go away when Bug 144114 is fixed
+#define NS_PRINTOPTIONS_CID NS_PRINTSETTINGSSERVICE_CID
+
+#define NS_PRINTER_ENUMERATOR_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 000000000..54c619829
--- /dev/null
+++ b/widget/nsXPLookAndFeel.cpp
@@ -0,0 +1,982 @@
+/* -*- 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 "nsCRT.h"
+#include "nsFont.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/widget/WidgetMessageUtils.h"
+
+#include "gfxPlatform.h"
+#include "qcms.h"
+
+#ifdef DEBUG
+#include "nsSize.h"
+#endif
+
+using namespace mozilla;
+
+nsLookAndFeelIntPref nsXPLookAndFeel::sIntPrefs[] =
+{
+ { "ui.caretBlinkTime",
+ eIntID_CaretBlinkTime,
+ false, 0 },
+ { "ui.caretWidth",
+ eIntID_CaretWidth,
+ false, 0 },
+ { "ui.caretVisibleWithSelection",
+ eIntID_ShowCaretDuringSelection,
+ false, 0 },
+ { "ui.submenuDelay",
+ eIntID_SubmenuDelay,
+ false, 0 },
+ { "ui.dragThresholdX",
+ eIntID_DragThresholdX,
+ false, 0 },
+ { "ui.dragThresholdY",
+ eIntID_DragThresholdY,
+ false, 0 },
+ { "ui.useAccessibilityTheme",
+ eIntID_UseAccessibilityTheme,
+ false, 0 },
+ { "ui.menusCanOverlapOSBar",
+ eIntID_MenusCanOverlapOSBar,
+ false, 0 },
+ { "ui.useOverlayScrollbars",
+ eIntID_UseOverlayScrollbars,
+ false, 0 },
+ { "ui.scrollbarDisplayOnMouseMove",
+ eIntID_ScrollbarDisplayOnMouseMove,
+ false, 0 },
+ { "ui.scrollbarFadeBeginDelay",
+ eIntID_ScrollbarFadeBeginDelay,
+ false, 0 },
+ { "ui.scrollbarFadeDuration",
+ eIntID_ScrollbarFadeDuration,
+ false, 0 },
+ { "ui.showHideScrollbars",
+ eIntID_ShowHideScrollbars,
+ false, 0 },
+ { "ui.skipNavigatingDisabledMenuItem",
+ eIntID_SkipNavigatingDisabledMenuItem,
+ false, 0 },
+ { "ui.treeOpenDelay",
+ eIntID_TreeOpenDelay,
+ false, 0 },
+ { "ui.treeCloseDelay",
+ eIntID_TreeCloseDelay,
+ false, 0 },
+ { "ui.treeLazyScrollDelay",
+ eIntID_TreeLazyScrollDelay,
+ false, 0 },
+ { "ui.treeScrollDelay",
+ eIntID_TreeScrollDelay,
+ false, 0 },
+ { "ui.treeScrollLinesMax",
+ eIntID_TreeScrollLinesMax,
+ false, 0 },
+ { "accessibility.tabfocus",
+ eIntID_TabFocusModel,
+ false, 0 },
+ { "ui.alertNotificationOrigin",
+ eIntID_AlertNotificationOrigin,
+ false, 0 },
+ { "ui.scrollToClick",
+ eIntID_ScrollToClick,
+ false, 0 },
+ { "ui.IMERawInputUnderlineStyle",
+ eIntID_IMERawInputUnderlineStyle,
+ false, 0 },
+ { "ui.IMESelectedRawTextUnderlineStyle",
+ eIntID_IMESelectedRawTextUnderlineStyle,
+ false, 0 },
+ { "ui.IMEConvertedTextUnderlineStyle",
+ eIntID_IMEConvertedTextUnderlineStyle,
+ false, 0 },
+ { "ui.IMESelectedConvertedTextUnderlineStyle",
+ eIntID_IMESelectedConvertedTextUnderline,
+ false, 0 },
+ { "ui.SpellCheckerUnderlineStyle",
+ eIntID_SpellCheckerUnderlineStyle,
+ false, 0 },
+ { "ui.scrollbarButtonAutoRepeatBehavior",
+ eIntID_ScrollbarButtonAutoRepeatBehavior,
+ false, 0 },
+ { "ui.tooltipDelay",
+ eIntID_TooltipDelay,
+ false, 0 },
+ { "ui.physicalHomeButton",
+ eIntID_PhysicalHomeButton,
+ false, 0 },
+ { "ui.contextMenuOffsetVertical",
+ eIntID_ContextMenuOffsetVertical,
+ false, 0 },
+ { "ui.contextMenuOffsetHorizontal",
+ eIntID_ContextMenuOffsetHorizontal,
+ false, 0 }
+};
+
+nsLookAndFeelFloatPref nsXPLookAndFeel::sFloatPrefs[] =
+{
+ { "ui.IMEUnderlineRelativeSize",
+ eFloatID_IMEUnderlineRelativeSize,
+ false, 0 },
+ { "ui.SpellCheckerUnderlineRelativeSize",
+ eFloatID_SpellCheckerUnderlineRelativeSize,
+ false, 0 },
+ { "ui.caretAspectRatio",
+ eFloatID_CaretAspectRatio,
+ false, 0 },
+};
+
+
+// This array MUST be kept in the same order as the color list in LookAndFeel.h.
+/* XXX If you add any strings longer than
+ * "ui.IMESelectedConvertedTextBackground"
+ * to the following array then you MUST update the
+ * sizes of the sColorPrefs array in nsXPLookAndFeel.h
+ */
+const char nsXPLookAndFeel::sColorPrefs[][38] =
+{
+ "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.activeborder",
+ "ui.activecaption",
+ "ui.appworkspace",
+ "ui.background",
+ "ui.buttonface",
+ "ui.buttonhighlight",
+ "ui.buttonshadow",
+ "ui.buttontext",
+ "ui.captiontext",
+ "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-field",
+ "ui.-moz-fieldtext",
+ "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-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-win-mediatext",
+ "ui.-moz-win-communicationstext",
+ "ui.-moz-nativehyperlinktext",
+ "ui.-moz-comboboxtext",
+ "ui.-moz-combobox"
+};
+
+int32_t nsXPLookAndFeel::sCachedColors[LookAndFeel::eColorID_LAST_COLOR] = {0};
+int32_t nsXPLookAndFeel::sCachedColorBits[COLOR_CACHE_SIZE] = {0};
+
+bool nsXPLookAndFeel::sInitialized = false;
+bool nsXPLookAndFeel::sUseNativeColors = true;
+bool nsXPLookAndFeel::sUseStandinsForNativeColors = false;
+bool nsXPLookAndFeel::sFindbarModalHighlight = false;
+
+nsLookAndFeel* nsXPLookAndFeel::sInstance = nullptr;
+bool nsXPLookAndFeel::sShutdown = false;
+
+// static
+nsLookAndFeel*
+nsXPLookAndFeel::GetInstance()
+{
+ if (sInstance) {
+ return sInstance;
+ }
+
+ NS_ENSURE_TRUE(!sShutdown, nullptr);
+
+ sInstance = new nsLookAndFeel();
+ return sInstance;
+}
+
+// static
+void
+nsXPLookAndFeel::Shutdown()
+{
+ if (sShutdown) {
+ return;
+ }
+ sShutdown = true;
+ delete sInstance;
+ sInstance = nullptr;
+}
+
+nsXPLookAndFeel::nsXPLookAndFeel() : LookAndFeel()
+{
+}
+
+// static
+void
+nsXPLookAndFeel::IntPrefChanged(nsLookAndFeelIntPref *data)
+{
+ if (!data) {
+ return;
+ }
+
+ int32_t intpref;
+ nsresult rv = Preferences::GetInt(data->name, &intpref);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ data->intVar = intpref;
+ data->isSet = true;
+#ifdef DEBUG_akkana
+ printf("====== Changed int pref %s to %d\n", data->name, data->intVar);
+#endif
+}
+
+// static
+void
+nsXPLookAndFeel::FloatPrefChanged(nsLookAndFeelFloatPref *data)
+{
+ if (!data) {
+ return;
+ }
+
+ int32_t intpref;
+ nsresult rv = Preferences::GetInt(data->name, &intpref);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ data->floatVar = (float)intpref / 100.0f;
+ data->isSet = true;
+#ifdef DEBUG_akkana
+ printf("====== Changed float pref %s to %f\n", data->name, data->floatVar);
+#endif
+}
+
+// static
+void
+nsXPLookAndFeel::ColorPrefChanged (unsigned int index, const char *prefName)
+{
+ nsAutoString colorStr;
+ nsresult rv = Preferences::GetString(prefName, &colorStr);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (!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);
+ }
+}
+
+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)
+{
+ 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()
+{
+ // 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::RegisterCallback(OnPrefChanged, "ui.");
+ 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);
+ }
+
+ Preferences::AddBoolVarCache(&sUseNativeColors,
+ "ui.use_native_colors",
+ sUseNativeColors);
+ Preferences::AddBoolVarCache(&sUseStandinsForNativeColors,
+ "ui.use_standins_for_native_colors",
+ sUseStandinsForNativeColors);
+ Preferences::AddBoolVarCache(&sFindbarModalHighlight,
+ "findbar.modalHighlight",
+ sFindbarModalHighlight);
+
+ if (XRE_IsContentProcess()) {
+ mozilla::dom::ContentChild* cc =
+ mozilla::dom::ContentChild::GetSingleton();
+
+ nsTArray<LookAndFeelInt> lookAndFeelIntCache;
+ cc->SendGetLookAndFeelCache(&lookAndFeelIntCache);
+ LookAndFeel::SetIntCache(lookAndFeelIntCache);
+ }
+}
+
+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 eColorID_TextSelectForeground:
+ return (aColor == NS_DONT_CHANGE_COLOR);
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ case eColorID_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 eColorID_WindowBackground:
+ case eColorID_WindowForeground:
+ case eColorID_WidgetBackground:
+ case eColorID_WidgetForeground:
+ case eColorID_WidgetSelectBackground:
+ case eColorID_WidgetSelectForeground:
+ case eColorID_Widget3DHighlight:
+ case eColorID_Widget3DShadow:
+ case eColorID_TextBackground:
+ case eColorID_TextForeground:
+ case eColorID_TextSelectBackground:
+ case eColorID_TextSelectForeground:
+ case eColorID_TextSelectBackgroundDisabled:
+ case eColorID_TextSelectBackgroundAttention:
+ case eColorID_TextHighlightBackground:
+ case eColorID_TextHighlightForeground:
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMEConvertedTextBackground:
+ case eColorID_IMEConvertedTextForeground:
+ case eColorID_IMEConvertedTextUnderline:
+ case eColorID_IMESelectedConvertedTextBackground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ case eColorID_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 eColorID_activeborder: result = NS_RGB(0xB4, 0xB4, 0xB4); break;
+ case eColorID_activecaption: result = NS_RGB(0x99, 0xB4, 0xD1); break;
+ case eColorID_appworkspace: result = NS_RGB(0xAB, 0xAB, 0xAB); break;
+ case eColorID_background: result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID_buttonface: result = NS_RGB(0xF0, 0xF0, 0xF0); break;
+ case eColorID_buttonhighlight: result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID_buttonshadow: result = NS_RGB(0xA0, 0xA0, 0xA0); break;
+ case eColorID_buttontext: result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID_captiontext: result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID_graytext: result = NS_RGB(0x6D, 0x6D, 0x6D); break;
+ case eColorID_highlight: result = NS_RGB(0x33, 0x99, 0xFF); break;
+ case eColorID_highlighttext: result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID_inactiveborder: result = NS_RGB(0xF4, 0xF7, 0xFC); break;
+ case eColorID_inactivecaption: result = NS_RGB(0xBF, 0xCD, 0xDB); break;
+ case eColorID_inactivecaptiontext:
+ result = NS_RGB(0x43, 0x4E, 0x54); break;
+ case eColorID_infobackground: result = NS_RGB(0xFF, 0xFF, 0xE1); break;
+ case eColorID_infotext: result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID_menu: result = NS_RGB(0xF0, 0xF0, 0xF0); break;
+ case eColorID_menutext: result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID_scrollbar: result = NS_RGB(0xC8, 0xC8, 0xC8); break;
+ case eColorID_threeddarkshadow: result = NS_RGB(0x69, 0x69, 0x69); break;
+ case eColorID_threedface: result = NS_RGB(0xF0, 0xF0, 0xF0); break;
+ case eColorID_threedhighlight: result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID_threedlightshadow: result = NS_RGB(0xE3, 0xE3, 0xE3); break;
+ case eColorID_threedshadow: result = NS_RGB(0xA0, 0xA0, 0xA0); break;
+ case eColorID_window: result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID_windowframe: result = NS_RGB(0x64, 0x64, 0x64); break;
+ case eColorID_windowtext: result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID__moz_buttondefault:
+ result = NS_RGB(0x69, 0x69, 0x69); break;
+ case eColorID__moz_field: result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID__moz_fieldtext: result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID__moz_dialog: result = NS_RGB(0xF0, 0xF0, 0xF0); break;
+ case eColorID__moz_dialogtext: result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID__moz_dragtargetzone:
+ result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID__moz_cellhighlight:
+ result = NS_RGB(0xF0, 0xF0, 0xF0); break;
+ case eColorID__moz_cellhighlighttext:
+ result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID__moz_html_cellhighlight:
+ result = NS_RGB(0x33, 0x99, 0xFF); break;
+ case eColorID__moz_html_cellhighlighttext:
+ result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID__moz_buttonhoverface:
+ result = NS_RGB(0xF0, 0xF0, 0xF0); break;
+ case eColorID__moz_buttonhovertext:
+ result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID__moz_menuhover:
+ result = NS_RGB(0x33, 0x99, 0xFF); break;
+ case eColorID__moz_menuhovertext:
+ result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID__moz_menubartext:
+ result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID__moz_menubarhovertext:
+ result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID__moz_oddtreerow:
+ result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID__moz_mac_chrome_active:
+ result = NS_RGB(0xB2, 0xB2, 0xB2); break;
+ case eColorID__moz_mac_chrome_inactive:
+ result = NS_RGB(0xE1, 0xE1, 0xE1); break;
+ case eColorID__moz_mac_focusring:
+ result = NS_RGB(0x60, 0x9D, 0xD7); break;
+ case eColorID__moz_mac_menuselect:
+ result = NS_RGB(0x38, 0x75, 0xD7); break;
+ case eColorID__moz_mac_menushadow:
+ result = NS_RGB(0xA3, 0xA3, 0xA3); break;
+ case eColorID__moz_mac_menutextdisable:
+ result = NS_RGB(0x88, 0x88, 0x88); break;
+ case eColorID__moz_mac_menutextselect:
+ result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID__moz_mac_disabledtoolbartext:
+ result = NS_RGB(0x3F, 0x3F, 0x3F); break;
+ case eColorID__moz_mac_secondaryhighlight:
+ result = NS_RGB(0xD4, 0xD4, 0xD4); break;
+ case eColorID__moz_win_mediatext:
+ result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID__moz_win_communicationstext:
+ result = NS_RGB(0xFF, 0xFF, 0xFF); break;
+ case eColorID__moz_nativehyperlinktext:
+ result = NS_RGB(0x00, 0x66, 0xCC); break;
+ case eColorID__moz_comboboxtext:
+ result = NS_RGB(0x00, 0x00, 0x00); break;
+ case eColorID__moz_combobox:
+ 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::GetColorImpl(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 eColorID_activecaption:
+ // active window caption background
+ case eColorID_captiontext:
+ // text in active window caption
+ aResult = NS_RGB(0xff, 0x00, 0x00);
+ break;
+
+ case eColorID_highlight:
+ // background of selected item
+ case eColorID_highlighttext:
+ // text of selected item
+ aResult = NS_RGB(0xff, 0xff, 0x00);
+ break;
+
+ case eColorID_inactivecaption:
+ // inactive window caption
+ case eColorID_inactivecaptiontext:
+ // text in inactive window caption
+ aResult = NS_RGB(0x66, 0x66, 0x00);
+ break;
+
+ case eColorID_infobackground:
+ // tooltip background color
+ case eColorID_infotext:
+ // tooltip text color
+ aResult = NS_RGB(0x00, 0xff, 0x00);
+ break;
+
+ case eColorID_menu:
+ // menu background
+ case eColorID_menutext:
+ // menu text
+ aResult = NS_RGB(0x00, 0xff, 0xff);
+ break;
+
+ case eColorID_threedface:
+ case eColorID_buttonface:
+ // 3-D face color
+ case eColorID_buttontext:
+ // text on push buttons
+ aResult = NS_RGB(0x00, 0x66, 0x66);
+ break;
+
+ case eColorID_window:
+ case eColorID_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 eColorID__moz_field:
+ case eColorID__moz_fieldtext:
+ aResult = NS_RGB(0xff, 0x00, 0xff);
+ break;
+
+ case eColorID__moz_dialog:
+ case eColorID__moz_dialogtext:
+ 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) || !sUseStandinsForNativeColors)) {
+ aUseStandinsForNativeColors = false;
+ }
+
+ if (!aUseStandinsForNativeColors && IS_COLOR_CACHED(aID)) {
+ aResult = sCachedColors[aID];
+ return NS_OK;
+ }
+
+ // There are no system color settings for these, so set them manually
+ if (aID == eColorID_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;
+ }
+
+ if (aID == eColorID_TextSelectBackgroundAttention) {
+ if (sFindbarModalHighlight) {
+ 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 == eColorID_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 == eColorID_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 (sUseNativeColors && aUseStandinsForNativeColors) {
+ aResult = GetStandinForNativeColor(aID);
+ return NS_OK;
+ }
+
+ if (sUseNativeColors && NS_SUCCEEDED(NativeGetColor(aID, aResult))) {
+ if ((gfxPlatform::GetCMSMode() == eCMSMode_All) &&
+ !IsSpecialColor(aID, aResult)) {
+ qcms_transform *transform = gfxPlatform::GetCMSInverseRGBTransform();
+ if (transform) {
+ uint8_t color[3];
+ color[0] = NS_GET_R(aResult);
+ color[1] = NS_GET_G(aResult);
+ color[2] = NS_GET_B(aResult);
+ qcms_transform_data(transform, color, color, 1);
+ aResult = NS_RGB(color[0], color[1], color[2]);
+ }
+ }
+
+ CACHE_COLOR(aID, aResult);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult
+nsXPLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ if (!sInitialized)
+ Init();
+
+ // Set the default values for these prefs. but allow different platforms
+ // to override them in their nsLookAndFeel if desired.
+ switch (aID) {
+ case eIntID_ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ return NS_OK;
+ case eIntID_ScrollButtonMiddleMouseButtonAction:
+ aResult = 3;
+ return NS_OK;
+ case eIntID_ScrollButtonRightMouseButtonAction:
+ aResult = 3;
+ return NS_OK;
+ default:
+ /*
+ * The metrics above are hardcoded platform defaults. All the other
+ * metrics are stored in sIntPrefs and can be changed at runtime.
+ */
+ break;
+ }
+
+ 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 NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult
+nsXPLookAndFeel::GetFloatImpl(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 NS_ERROR_NOT_AVAILABLE;
+}
+
+void
+nsXPLookAndFeel::RefreshImpl()
+{
+ // Wipe out our color cache.
+ uint32_t i;
+ for (i = 0; i < eColorID_LAST_COLOR; i++)
+ sCachedColors[i] = 0;
+ for (i = 0; i < COLOR_CACHE_SIZE; i++)
+ sCachedColorBits[i] = 0;
+}
+
+nsTArray<LookAndFeelInt>
+nsXPLookAndFeel::GetIntCacheImpl()
+{
+ return nsTArray<LookAndFeelInt>();
+}
+
+namespace mozilla {
+
+// static
+nsresult
+LookAndFeel::GetColor(ColorID aID, nscolor* aResult)
+{
+ return nsLookAndFeel::GetInstance()->GetColorImpl(aID, false, *aResult);
+}
+
+nsresult
+LookAndFeel::GetColor(ColorID aID, bool aUseStandinsForNativeColors,
+ nscolor* aResult)
+{
+ return nsLookAndFeel::GetInstance()->GetColorImpl(aID,
+ aUseStandinsForNativeColors, *aResult);
+}
+
+// static
+nsresult
+LookAndFeel::GetInt(IntID aID, int32_t* aResult)
+{
+ return nsLookAndFeel::GetInstance()->GetIntImpl(aID, *aResult);
+}
+
+// static
+nsresult
+LookAndFeel::GetFloat(FloatID aID, float* aResult)
+{
+ return nsLookAndFeel::GetInstance()->GetFloatImpl(aID, *aResult);
+}
+
+// static
+bool
+LookAndFeel::GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle,
+ float aDevPixPerCSSPixel)
+{
+ return nsLookAndFeel::GetInstance()->GetFontImpl(aID, aName, aStyle,
+ aDevPixPerCSSPixel);
+}
+
+// static
+char16_t
+LookAndFeel::GetPasswordCharacter()
+{
+ return nsLookAndFeel::GetInstance()->GetPasswordCharacterImpl();
+}
+
+// static
+bool
+LookAndFeel::GetEchoPassword()
+{
+ return nsLookAndFeel::GetInstance()->GetEchoPasswordImpl();
+}
+
+// static
+uint32_t
+LookAndFeel::GetPasswordMaskDelay()
+{
+ return nsLookAndFeel::GetInstance()->GetPasswordMaskDelayImpl();
+}
+
+// static
+void
+LookAndFeel::Refresh()
+{
+ nsLookAndFeel::GetInstance()->RefreshImpl();
+}
+
+// static
+nsTArray<LookAndFeelInt>
+LookAndFeel::GetIntCache()
+{
+ return nsLookAndFeel::GetInstance()->GetIntCacheImpl();
+}
+
+// static
+void
+LookAndFeel::SetIntCache(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache)
+{
+ return nsLookAndFeel::GetInstance()->SetIntCacheImpl(aLookAndFeelIntCache);
+}
+
+} // namespace mozilla
diff --git a/widget/nsXPLookAndFeel.h b/widget/nsXPLookAndFeel.h
new file mode 100644
index 000000000..331363f76
--- /dev/null
+++ b/widget/nsXPLookAndFeel.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 __nsXPLookAndFeel
+#define __nsXPLookAndFeel
+
+#include "mozilla/LookAndFeel.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) ((x) >> 5)
+#define CACHE_BIT(x) (1 << ((x) & 31))
+
+#define COLOR_CACHE_SIZE (CACHE_BLOCK(LookAndFeel::eColorID_LAST_COLOR) + 1)
+#define IS_COLOR_CACHED(x) (CACHE_BIT(x) & nsXPLookAndFeel::sCachedColorBits[CACHE_BLOCK(x)])
+#define CLEAR_COLOR_CACHE(x) nsXPLookAndFeel::sCachedColors[(x)] =0; \
+ nsXPLookAndFeel::sCachedColorBits[CACHE_BLOCK(x)] &= ~(CACHE_BIT(x));
+#define CACHE_COLOR(x, y) nsXPLookAndFeel::sCachedColors[(x)] = y; \
+ nsXPLookAndFeel::sCachedColorBits[CACHE_BLOCK(x)] |= CACHE_BIT(x);
+
+class nsXPLookAndFeel: public mozilla::LookAndFeel
+{
+public:
+ virtual ~nsXPLookAndFeel();
+
+ static nsLookAndFeel* GetInstance();
+ static void Shutdown();
+
+ void Init();
+
+ //
+ // 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 GetColorImpl(ColorID aID, bool aUseStandinsForNativeColors,
+ nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+
+ // This one is different: there are no override prefs (fixme?), so
+ // there is no XP implementation, only per-system impls.
+ virtual bool GetFontImpl(FontID aID, nsString& aName,
+ gfxFontStyle& aStyle,
+ float aDevPixPerCSSPixel) = 0;
+
+ virtual void RefreshImpl();
+
+ virtual char16_t GetPasswordCharacterImpl()
+ {
+ return char16_t('*');
+ }
+
+ virtual bool GetEchoPasswordImpl()
+ {
+ return false;
+ }
+
+ virtual uint32_t GetPasswordMaskDelayImpl()
+ {
+ return 600;
+ }
+
+ virtual nsTArray<LookAndFeelInt> GetIntCacheImpl();
+ virtual void SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache) {}
+
+protected:
+ nsXPLookAndFeel();
+
+ 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);
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult) = 0;
+ bool IsSpecialColor(ColorID aID, nscolor &aColor);
+ bool ColorIsNotCSSAccessible(ColorID aID);
+ nscolor GetStandinForNativeColor(ColorID aID);
+
+ 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[][38];
+ static int32_t sCachedColors[LookAndFeel::eColorID_LAST_COLOR];
+ static int32_t sCachedColorBits[COLOR_CACHE_SIZE];
+ static bool sUseNativeColors;
+ static bool sUseStandinsForNativeColors;
+ static bool sFindbarModalHighlight;
+
+ static nsLookAndFeel* sInstance;
+ static bool sShutdown;
+};
+
+#endif
diff --git a/widget/reftests/507947.html b/widget/reftests/507947.html
new file mode 100644
index 000000000..2405850db
--- /dev/null
+++ b/widget/reftests/507947.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="height: 100px; -moz-appearance: toolbar;"></div>
diff --git a/widget/reftests/664925.xhtml b/widget/reftests/664925.xhtml
new file mode 100644
index 000000000..b4a11a738
--- /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 000000000..ea7269881
--- /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: -moz-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 000000000..a5942d7e5
--- /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 000000000..ce434e9f1
--- /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 000000000..91342772f
--- /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 000000000..4426fe93e
--- /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 000000000..445671c5e
--- /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 000000000..5de4274ec
--- /dev/null
+++ b/widget/reftests/progressbar-fallback-default-style-ref.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+ <style>
+ div.progress-element {
+ /**
+ * The purpose of this test is to not show the native style.
+ * -moz-appearance: progressbar;
+ */
+ display: inline-block;
+ height: 1em;
+ width: 10em;
+ vertical-align: -0.2em;
+
+ /* Default style in case of there is -moz-appearance: none; */
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow #e6e6e6;
+ -moz-border-right-colors: ThreeDHighlight #e6e6e6;
+ -moz-border-bottom-colors: ThreeDHighlight #e6e6e6;
+ -moz-border-left-colors: ThreeDShadow #e6e6e6;
+ background-color: #e6e6e6;
+ }
+
+ div.progress-bar {
+ /**
+ * The purpose of this test is to not show the native style.
+ * -moz-appearance: progresschunk;
+ */
+
+ height: 100%;
+ width: 100%;
+
+ box-sizing: border-box;
+
+ /* Default style in case of there is -moz-appearance: none; */
+ background-color: #0064b4;
+ }
+
+ 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 000000000..7594e6555
--- /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-stylo.list b/widget/reftests/reftest-stylo.list
new file mode 100644
index 000000000..86ac4e30e
--- /dev/null
+++ b/widget/reftests/reftest-stylo.list
@@ -0,0 +1,8 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+skip-if(!cocoaWidget) == 507947.html 507947.html
+== progressbar-fallback-default-style.html progressbar-fallback-default-style.html
+fuzzy-if(Android,17,1120) == meter-native-style.html meter-native-style.html
+fails skip-if(!cocoaWidget) == meter-vertical-native-style.html meter-vertical-native-style.html
+# dithering
+== meter-fallback-default-style.html meter-fallback-default-style.html
+load 664925.xhtml
diff --git a/widget/reftests/reftest.list b/widget/reftests/reftest.list
new file mode 100644
index 000000000..26057327b
--- /dev/null
+++ b/widget/reftests/reftest.list
@@ -0,0 +1,6 @@
+skip-if(!cocoaWidget) != 507947.html about:blank
+== progressbar-fallback-default-style.html progressbar-fallback-default-style-ref.html
+fuzzy-if(Android,17,1120) == 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
diff --git a/widget/tests/TestAppShellSteadyState.cpp b/widget/tests/TestAppShellSteadyState.cpp
new file mode 100644
index 000000000..03888fc0a
--- /dev/null
+++ b/widget/tests/TestAppShellSteadyState.cpp
@@ -0,0 +1,503 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "TestHarness.h"
+
+#include "nsIAppShell.h"
+#include "nsIAppShellService.h"
+#include "nsIDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIRunnable.h"
+#include "nsIURI.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIXULWindow.h"
+
+#include "nsAppShellCID.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+
+#ifdef XP_WIN
+#include <windows.h>
+#endif
+
+using namespace mozilla;
+
+typedef void (*TestFunc)(nsIAppShell*);
+
+bool gStableStateEventHasRun = false;
+
+class ExitAppShellRunnable : public Runnable
+{
+ nsCOMPtr<nsIAppShell> mAppShell;
+
+public:
+ explicit ExitAppShellRunnable(nsIAppShell* aAppShell)
+ : mAppShell(aAppShell)
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ return mAppShell->Exit();
+ }
+};
+
+class StableStateRunnable : public Runnable
+{
+public:
+ NS_IMETHOD
+ Run() override
+ {
+ if (gStableStateEventHasRun) {
+ fail("StableStateRunnable already ran");
+ }
+ gStableStateEventHasRun = true;
+ return NS_OK;
+ }
+};
+
+class CheckStableStateRunnable : public Runnable
+{
+ bool mShouldHaveRun;
+
+public:
+ explicit CheckStableStateRunnable(bool aShouldHaveRun)
+ : mShouldHaveRun(aShouldHaveRun)
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ if (mShouldHaveRun == gStableStateEventHasRun) {
+ passed("StableStateRunnable state correct (%s)",
+ mShouldHaveRun ? "true" : "false");
+ } else {
+ fail("StableStateRunnable ran at wrong time");
+ }
+ return NS_OK;
+ }
+};
+
+class ScheduleStableStateRunnable : public CheckStableStateRunnable
+{
+protected:
+ nsCOMPtr<nsIAppShell> mAppShell;
+
+public:
+ explicit ScheduleStableStateRunnable(nsIAppShell* aAppShell)
+ : CheckStableStateRunnable(false), mAppShell(aAppShell)
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ CheckStableStateRunnable::Run();
+
+ nsCOMPtr<nsIRunnable> runnable = new StableStateRunnable();
+ nsresult rv = mAppShell->RunBeforeNextEvent(runnable);
+ if (NS_FAILED(rv)) {
+ fail("RunBeforeNextEvent returned failure code %u", rv);
+ }
+
+ return rv;
+ }
+};
+
+class NextTestRunnable : public Runnable
+{
+ nsCOMPtr<nsIAppShell> mAppShell;
+
+public:
+ explicit NextTestRunnable(nsIAppShell* aAppShell)
+ : mAppShell(aAppShell)
+ { }
+
+ NS_IMETHOD Run();
+};
+
+class ScheduleNestedStableStateRunnable : public ScheduleStableStateRunnable
+{
+public:
+ explicit ScheduleNestedStableStateRunnable(nsIAppShell* aAppShell)
+ : ScheduleStableStateRunnable(aAppShell)
+ { }
+
+ NS_IMETHOD
+ Run() override
+ {
+ ScheduleStableStateRunnable::Run();
+
+ nsCOMPtr<nsIRunnable> runnable = new CheckStableStateRunnable(false);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch check runnable");
+ }
+
+ if (NS_FAILED(NS_ProcessPendingEvents(nullptr))) {
+ fail("Failed to process all pending events");
+ }
+
+ runnable = new CheckStableStateRunnable(true);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch check runnable");
+ }
+
+ runnable = new NextTestRunnable(mAppShell);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch next test runnable");
+ }
+
+ return NS_OK;
+ }
+};
+
+class EventListener final : public nsIDOMEventListener
+{
+ nsCOMPtr<nsIAppShell> mAppShell;
+
+ static nsIDOMWindowUtils* sWindowUtils;
+ static nsIAppShell* sAppShell;
+
+ ~EventListener() {}
+
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit EventListener(nsIAppShell* aAppShell)
+ : mAppShell(aAppShell)
+ { }
+
+ NS_IMETHOD
+ HandleEvent(nsIDOMEvent* aEvent) override
+ {
+ nsString type;
+ if (NS_FAILED(aEvent->GetType(type))) {
+ fail("Failed to get event type");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (type.EqualsLiteral("load")) {
+ passed("Got load event");
+
+ nsCOMPtr<nsIDOMEventTarget> target;
+ if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) {
+ fail("Failed to get event type");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> document = do_QueryInterface(target);
+ if (!document) {
+ fail("Failed to QI to nsIDocument!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsPIDOMWindow> window = document->GetWindow();
+ if (!window) {
+ fail("Failed to get window from document!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
+ if (!utils) {
+ fail("Failed to get DOMWindowUtils!");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!ScheduleTimer(utils)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ if (type.EqualsLiteral("keypress")) {
+ passed("Got keypress event");
+
+ nsCOMPtr<nsIRunnable> runnable = new StableStateRunnable();
+ nsresult rv = mAppShell->RunBeforeNextEvent(runnable);
+ if (NS_FAILED(rv)) {
+ fail("RunBeforeNextEvent returned failure code %u", rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ fail("Got an unexpected event: %s", NS_ConvertUTF16toUTF8(type).get());
+ return NS_OK;
+ }
+
+#ifdef XP_WIN
+ static VOID CALLBACK
+ TimerCallback(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
+ {
+ if (sWindowUtils) {
+ nsCOMPtr<nsIDOMWindowUtils> utils = dont_AddRef(sWindowUtils);
+ sWindowUtils = nullptr;
+
+ if (gStableStateEventHasRun) {
+ fail("StableStateRunnable ran at wrong time");
+ } else {
+ passed("StableStateRunnable state correct (false)");
+ }
+
+ int32_t layout = 0x409; // US
+ int32_t keyCode = 0x41; // VK_A
+ NS_NAMED_LITERAL_STRING(a, "a");
+
+ if (NS_FAILED(utils->SendNativeKeyEvent(layout, keyCode, 0, a, a, nullptr))) {
+ fail("Failed to synthesize native event");
+ }
+
+ return;
+ }
+
+ KillTimer(nullptr, idEvent);
+
+ nsCOMPtr<nsIAppShell> appShell = dont_AddRef(sAppShell);
+
+ if (!gStableStateEventHasRun) {
+ fail("StableStateRunnable didn't run yet");
+ } else {
+ passed("StableStateRunnable state correct (true)");
+ }
+
+ nsCOMPtr<nsIRunnable> runnable = new NextTestRunnable(appShell);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch next test runnable");
+ }
+
+ }
+#endif
+
+ bool
+ ScheduleTimer(nsIDOMWindowUtils* aWindowUtils)
+ {
+#ifdef XP_WIN
+ UINT_PTR timerId = SetTimer(nullptr, 0, 1000, (TIMERPROC)TimerCallback);
+ if (!timerId) {
+ fail("SetTimer failed!");
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMWindowUtils> utils = aWindowUtils;
+ utils.forget(&sWindowUtils);
+
+ nsCOMPtr<nsIAppShell> appShell = mAppShell;
+ appShell.forget(&sAppShell);
+
+ return true;
+#else
+ return false;
+#endif
+ }
+};
+
+nsIDOMWindowUtils* EventListener::sWindowUtils = nullptr;
+nsIAppShell* EventListener::sAppShell = nullptr;
+
+NS_IMPL_ISUPPORTS(EventListener, nsIDOMEventListener)
+
+already_AddRefed<nsIAppShell>
+GetAppShell()
+{
+ static const char* platforms[] = {
+ "android", "mac", "gonk", "gtk", "qt", "win"
+ };
+
+ NS_NAMED_LITERAL_CSTRING(contractPrefix, "@mozilla.org/widget/appshell/");
+ NS_NAMED_LITERAL_CSTRING(contractSuffix, ";1");
+
+ for (size_t index = 0; index < ArrayLength(platforms); index++) {
+ nsAutoCString contractID(contractPrefix);
+ contractID.AppendASCII(platforms[index]);
+ contractID.Append(contractSuffix);
+
+ nsCOMPtr<nsIAppShell> appShell = do_GetService(contractID.get());
+ if (appShell) {
+ return appShell.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+void
+Test1(nsIAppShell* aAppShell)
+{
+ // Schedule stable state runnable to be run before next event.
+
+ nsCOMPtr<nsIRunnable> runnable = new StableStateRunnable();
+ if (NS_FAILED(aAppShell->RunBeforeNextEvent(runnable))) {
+ fail("RunBeforeNextEvent failed");
+ }
+
+ runnable = new CheckStableStateRunnable(true);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch check runnable");
+ }
+
+ runnable = new NextTestRunnable(aAppShell);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch next test runnable");
+ }
+}
+
+void
+Test2(nsIAppShell* aAppShell)
+{
+ // Schedule stable state runnable to be run before next event from another
+ // runnable.
+
+ nsCOMPtr<nsIRunnable> runnable = new ScheduleStableStateRunnable(aAppShell);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch schedule runnable");
+ }
+
+ runnable = new CheckStableStateRunnable(true);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch check runnable");
+ }
+
+ runnable = new NextTestRunnable(aAppShell);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch next test runnable");
+ }
+}
+
+void
+Test3(nsIAppShell* aAppShell)
+{
+ // Schedule steadystate runnable to be run before next event with nested loop.
+
+ nsCOMPtr<nsIRunnable> runnable =
+ new ScheduleNestedStableStateRunnable(aAppShell);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch schedule runnable");
+ }
+}
+
+bool
+Test4Internal(nsIAppShell* aAppShell)
+{
+#ifndef XP_WIN
+ // Not sure how to test on other platforms.
+ return false;
+#else
+ nsCOMPtr<nsIAppShellService> appService =
+ do_GetService(NS_APPSHELLSERVICE_CONTRACTID);
+ if (!appService) {
+ fail("Failed to get appshell service!");
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), "about:", nullptr))) {
+ fail("Failed to create new uri");
+ return false;
+ }
+
+ uint32_t flags = nsIWebBrowserChrome::CHROME_DEFAULT;
+
+ nsCOMPtr<nsIXULWindow> xulWindow;
+ if (NS_FAILED(appService->CreateTopLevelWindow(nullptr, uri, flags, 100, 100, nullptr,
+ getter_AddRefs(xulWindow)))) {
+ fail("Failed to create new window");
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMWindow> window = do_GetInterface(xulWindow);
+ if (!window) {
+ fail("Can't get dom window!");
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(window);
+ if (!target) {
+ fail("Can't QI to nsIDOMEventTarget!");
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMEventListener> listener = new EventListener(aAppShell);
+ if (NS_FAILED(target->AddEventListener(NS_LITERAL_STRING("keypress"),
+ listener, false, false)) ||
+ NS_FAILED(target->AddEventListener(NS_LITERAL_STRING("load"), listener,
+ false, false))) {
+ fail("Can't add event listeners!");
+ return false;
+ }
+
+ return true;
+#endif
+}
+
+void
+Test4(nsIAppShell* aAppShell)
+{
+ if (!Test4Internal(aAppShell)) {
+ nsCOMPtr<nsIRunnable> runnable = new NextTestRunnable(aAppShell);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch next test runnable");
+ }
+ }
+}
+
+const TestFunc gTests[] = {
+ Test1, Test2, Test3, Test4
+};
+
+size_t gTestIndex = 0;
+
+NS_IMETHODIMP
+NextTestRunnable::Run()
+{
+ if (gTestIndex > 0) {
+ passed("Finished test %u", gTestIndex);
+ }
+
+ gStableStateEventHasRun = false;
+
+ if (gTestIndex < ArrayLength(gTests)) {
+ gTests[gTestIndex++](mAppShell);
+ }
+ else {
+ nsCOMPtr<nsIRunnable> exitRunnable = new ExitAppShellRunnable(mAppShell);
+
+ nsresult rv = NS_DispatchToCurrentThread(exitRunnable);
+ if (NS_FAILED(rv)) {
+ fail("Failed to dispatch exit runnable!");
+ }
+ }
+
+ return NS_OK;
+}
+
+int main(int argc, char** argv)
+{
+ ScopedLogging log;
+ ScopedXPCOM xpcom("TestAppShellSteadyState");
+
+ if (!xpcom.failed()) {
+ nsCOMPtr<nsIAppShell> appShell = GetAppShell();
+ if (!appShell) {
+ fail("Couldn't get appshell!");
+ } else {
+ nsCOMPtr<nsIRunnable> runnable = new NextTestRunnable(appShell);
+ if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
+ fail("Failed to dispatch next test runnable");
+ } else if (NS_FAILED(appShell->Run())) {
+ fail("Failed to run appshell");
+ }
+ }
+ }
+
+ return gFailCount != 0;
+}
diff --git a/widget/tests/TestChromeMargin.cpp b/widget/tests/TestChromeMargin.cpp
new file mode 100644
index 000000000..22330f467
--- /dev/null
+++ b/widget/tests/TestChromeMargin.cpp
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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
+// some of the includes make use of internal string types
+#define nsAString_h___
+#define nsString_h___
+#define nsStringFwd_h___
+#define nsReadableUtils_h___
+class nsACString;
+class nsAString;
+class nsAFlatString;
+class nsAFlatCString;
+class nsAdoptingString;
+class nsAdoptingCString;
+class nsXPIDLString;
+template<class T> class nsReadingIterator;
+#endif
+
+#include "nscore.h"
+#include "nsContentUtils.h"
+
+#ifndef MOZILLA_INTERNAL_API
+#undef nsString_h___
+#undef nsAString_h___
+#undef nsReadableUtils_h___
+#endif
+
+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/bug586713_window.xul b/widget/tests/bug586713_window.xul
new file mode 100644
index 000000000..78397afad
--- /dev/null
+++ b/widget/tests/bug586713_window.xul
@@ -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.opener.wrappedJSObject.SimpleTest.ok(condition, message);
+ }
+
+ function onTestsFinished() {
+ window.close();
+ window.opener.wrappedJSObject.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 000000000..00d0d57a9
--- /dev/null
+++ b/widget/tests/chrome.ini
@@ -0,0 +1,100 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ empty_window.xul
+ utils.js
+
+[test_bug343416.xul]
+skip-if = debug
+[test_bug429954.xul]
+support-files = window_bug429954.xul
+[test_bug444800.xul]
+subsuite = clipboard
+[test_bug478536.xul]
+skip-if = true # Bug 561929
+support-files = window_bug478536.xul
+[test_bug517396.xul]
+[test_bug538242.xul]
+support-files = window_bug538242.xul
+[test_bug593307.xul]
+support-files = window_bug593307_offscreen.xul window_bug593307_centerscreen.xul
+[test_bug1151186.html]
+skip-if = os == 'linux' && debug #Bug 1176038
+[test_keycodes.xul]
+[test_wheeltransaction.xul]
+support-files = window_wheeltransaction.xul
+[test_imestate.html]
+support-files = window_imestate_iframes.html
+[test_plugin_scroll_consistency.html]
+[test_composition_text_querycontent.xul]
+support-files = window_composition_text_querycontent.xul
+[test_input_events_on_deactive_window.xul]
+[test_position_on_resize.xul]
+[test_sizemode_events.xul]
+[test_taskbar_progress.xul]
+skip-if = toolkit != "cocoa" && toolkit != "windows"
+[test_bug760802.xul]
+[test_clipboard.xul]
+subsuite = clipboard
+[test_panel_mouse_coords.xul]
+skip-if = toolkit == "windows" # bug 1009955
+
+# Cocoa
+[test_native_menus.xul]
+skip-if = toolkit != "cocoa"
+support-files = native_menus_window.xul
+[test_native_mouse_mac.xul]
+skip-if = toolkit != "cocoa" || os_version == '10.10' # 10.10: bug 1137575
+support-files = native_mouse_mac_window.xul
+[test_bug413277.html]
+skip-if = toolkit != "cocoa"
+[test_bug428405.xul]
+skip-if = toolkit != "cocoa"
+[test_bug466599.xul]
+subsuite = clipboard
+skip-if = toolkit != "cocoa"
+[test_bug485118.xul]
+skip-if = toolkit != "cocoa"
+[test_bug522217.xul]
+tags = fullscreen
+skip-if = toolkit != "cocoa"
+support-files = window_bug522217.xul
+[test_platform_colors.xul]
+#skip-if = toolkit != "cocoa"
+skip-if = true # Bug 1207190
+[test_standalone_native_menu.xul]
+skip-if = toolkit != "cocoa"
+support-files = standalone_native_menu_window.xul
+[test_bug586713.xul]
+skip-if = toolkit != "cocoa"
+support-files = bug586713_window.xul
+[test_key_event_counts.xul]
+skip-if = toolkit != "cocoa"
+[test_bug596600.xul]
+skip-if = toolkit != "cocoa"
+[test_bug673301.xul]
+subsuite = clipboard
+skip-if = toolkit != "cocoa"
+[test_secure_input.html]
+skip-if = toolkit != "cocoa"
+[test_native_key_bindings_mac.html]
+skip-if = toolkit != "cocoa"
+[test_system_status_bar.xul]
+skip-if = toolkit != "cocoa"
+
+# Windows
+# taskbar_previews.xul
+# window_state_windows.xul
+[test_chrome_context_menus_win.xul]
+skip-if = toolkit != "windows"
+support-files = chrome_context_menus_win.xul
+[test_plugin_input_event.html]
+skip-if = toolkit != "windows"
+[test_mouse_scroll.xul]
+skip-if = toolkit != "windows"
+support-files = window_mouse_scroll_win.html
+
+# Privacy relevant
+[test_bug1123480.xul]
+subsuite = clipboard
+
diff --git a/widget/tests/chrome_context_menus_win.xul b/widget/tests/chrome_context_menus_win.xul
new file mode 100644
index 000000000..9a4590747
--- /dev/null
+++ b/widget/tests/chrome_context_menus_win.xul
@@ -0,0 +1,101 @@
+<?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="ChromeContextMenuTest"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="300"
+ height="300"
+ title="Chrome Context Menu Test w/Plugin Focus">
+
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+<popupset>
+ <menupopup id="testmenu" onpopupshown="menuDisplayed()">
+ <menuitem label="One"/>
+ <menuitem label="Two"/>
+ <menuitem label="Three"/>
+ </menupopup>
+</popupset>
+
+<toolbox>
+ <toolbar id="nav-toolbar" style="height:30px" context="testmenu">
+ </toolbar>
+</toolbox>
+
+<script type="application/javascript"><![CDATA[
+
+function ok(condition, message) {
+ window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
+}
+
+function onTestsFinished() {
+ window.close();
+ window.opener.wrappedJSObject.SimpleTest.finish();
+}
+
+function openContextMenuFor(element) {
+
+ var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ var tbX = (window.mozInnerScreenX + 10) * utils.screenPixelsPerCSSPixel;
+ var tbY = (window.mozInnerScreenY + 10) * utils.screenPixelsPerCSSPixel;
+
+ // See nsWidnow's SynthesizeNativeMouseEvent & SendInput on msdn
+ var MOUSEEVENTF_RIGHTDOWN = 0x0008;
+ var MOUSEEVENTF_RIGHTUP = 0x0010;
+
+ utils.sendNativeMouseEvent(tbX, tbY,
+ MOUSEEVENTF_RIGHTDOWN,
+ 0, element);
+ utils.sendNativeMouseEvent(tbX, tbY,
+ MOUSEEVENTF_RIGHTUP,
+ 0, element);
+}
+
+var tid = 0;
+
+function onFocus() {
+ var _delayedOnLoad = function() {
+ var plugin = document.getElementById("plugin");
+ var toolbar = document.getElementById("nav-toolbar");
+
+ plugin.focus();
+
+ tid = setTimeout("menuTimeout()", 5000);
+
+ openContextMenuFor(toolbar);
+ }
+ setTimeout(_delayedOnLoad, 3000);
+}
+
+function menuTimeout() {
+ ok(false, "Right-click chrome menu did not display with focus on a plugin.");
+ onTestsFinished();
+}
+
+function menuDisplayed() {
+ clearTimeout(tid);
+ ok(true, "Right-click chrome menu displayed despite plugin having focus.");
+ onTestsFinished();
+}
+
+window.opener.wrappedJSObject.SimpleTest.waitForFocus(onFocus, window);
+
+
+]]></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <br/>
+ <embed id="plugin" type="application/x-test" width="50" height="50"></embed>
+</body>
+
+</window>
diff --git a/widget/tests/empty_window.xul b/widget/tests/empty_window.xul
new file mode 100644
index 000000000..f0e01761d
--- /dev/null
+++ b/widget/tests/empty_window.xul
@@ -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/mochitest.ini b/widget/tests/mochitest.ini
new file mode 100644
index 000000000..bf7bfe689
--- /dev/null
+++ b/widget/tests/mochitest.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files = utils.js
+
+[test_assign_event_data.html]
+subsuite = clipboard
+skip-if = toolkit == "cocoa" # Mac: Bug 933303
+[test_bug565392.html]
+subsuite = clipboard
+skip-if = toolkit != "windows" || e10s # Bug 1267406
+[test_picker_no_crash.html]
+skip-if = toolkit != "windows" || e10s # Bug 1267491
+support-files = window_picker_no_crash_child.html
diff --git a/widget/tests/moz.build b/widget/tests/moz.build
new file mode 100644
index 000000000..750202b48
--- /dev/null
+++ b/widget/tests/moz.build
@@ -0,0 +1,15 @@
+# -*- 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 += ['unit/xpcshell.ini']
+MOCHITEST_MANIFESTS += ['mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['chrome.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.xul b/widget/tests/native_menus_window.xul
new file mode 100644
index 000000000..6e614d017
--- /dev/null
+++ b/widget/tests/native_menus_window.xul
@@ -0,0 +1,285 @@
+<?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.opener.wrappedJSObject.SimpleTest.ok(condition, message);
+ }
+
+ function onTestsFinished() {
+ window.close();
+ window.opener.wrappedJSObject.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.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ try {
+ utils.forceUpdateNativeMenuAt(location);
+ }
+ catch (e) {
+ dump(e + "\n");
+ }
+ }
+
+ var executedCommandID = "";
+
+ function testItem(location, targetID) {
+ var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ 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);
+ delete 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.xul b/widget/tests/native_mouse_mac_window.xul
new file mode 100644
index 000000000..8680c3b1a
--- /dev/null
+++ b/widget/tests/native_mouse_mac_window.xul
@@ -0,0 +1,773 @@
+<?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 type="application/javascript"
+ 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.opener.wrappedJSObject.SimpleTest.ok(condition, message);
+ }
+
+ function is(a, b, message) {
+ window.opener.wrappedJSObject.SimpleTest.is(a, b, message);
+ }
+
+ function todo(condition, message) {
+ window.opener.wrappedJSObject.SimpleTest.todo(condition, message);
+ }
+
+ function todo_is(a, b, message) {
+ window.opener.wrappedJSObject.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.opener.wrappedJSObject.SimpleTest;
+ window.close();
+ openerSimpleTest.finish();
+ }
+
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ const xulWin = 'data:application/vnd.mozilla.xul+xml,<?xml version="1.0"?><?xml-stylesheet href="chrome://global/skin" type="text/css"?><window xmlns="' + XUL_NS + '"/>';
+
+ const NSLeftMouseDown = 1,
+ NSLeftMouseUp = 2,
+ NSRightMouseDown = 3,
+ NSRightMouseUp = 4,
+ NSMouseMoved = 5,
+ NSLeftMouseDragged = 6,
+ NSRightMouseDragged = 7,
+ NSMouseEntered = 8,
+ NSMouseExited = 9,
+ NSKeyDown = 10,
+ NSKeyUp = 11,
+ NSFlagsChanged = 12,
+ NSAppKitDefined = 13,
+ NSSystemDefined = 14,
+ NSApplicationDefined = 15,
+ NSPeriodic = 16,
+ NSCursorUpdate = 17,
+ NSScrollWheel = 22,
+ NSTabletPoint = 23,
+ NSTabletProximity = 24,
+ NSOtherMouseDown = 25,
+ NSOtherMouseUp = 26,
+ NSOtherMouseDragged = 27,
+ NSEventTypeGesture = 29,
+ NSEventTypeMagnify = 30,
+ NSEventTypeSwipe = 31,
+ NSEventTypeRotate = 18,
+ NSEventTypeBeginGesture = 19,
+ NSEventTypeEndGesture = 20;
+
+ const NSAlphaShiftKeyMask = 1 << 16,
+ NSShiftKeyMask = 1 << 17,
+ NSControlKeyMask = 1 << 18,
+ NSAlternateKeyMask = 1 << 19,
+ NSCommandKeyMask = 1 << 20,
+ NSNumericPadKeyMask = 1 << 21,
+ NSHelpKeyMask = 1 << 22,
+ NSFunctionKeyMask = 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.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ 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) {
+ elem.removeEventListener(name, arguments.callee, false);
+ callback(e);
+ }, false);
+ }
+
+ 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 > 0) {
+ 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));
+ return processEvent(e);
+ }
+ 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 = (function createTooltipInRightWindow() {
+ 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);
+ return _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, NSMouseMoved, null, left, [
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem }
+ ]],
+ // Test that moving inside the window fires mousemove events.
+ [170, 150, NSMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Leaving the window should fire a mouseout event...
+ [170, 20, NSMouseMoved, null, left, [
+ { type: "mouseout", target: leftElem },
+ ]],
+ // ... and entering a mouseover event.
+ [170, 120, NSMouseMoved, 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, NSMouseMoved, 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, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ], NSCommandKeyMask],
+ [400, 150, NSLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ], NSCommandKeyMask],
+ [400, 150, NSOtherMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 150, NSOtherMouseUp, 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, NSLeftMouseDown, null, right, [
+ { type: "mouseover", target: rightElem, synthetic: true },
+ ]],
+ [400, 150, NSLeftMouseUp, null, right, [
+ ]],
+ // Now it's focused, so we should get a mousedown event when clicking.
+ [400, 150, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ // Let's drag to the right without letting the button go.
+ [410, 150, NSLeftMouseDragged, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Let go of the mouse.
+ [410, 150, NSLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // Move the mouse back over the left window, which is inactive.
+ [150, 170, NSMouseMoved, 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, NSRightMouseDown, 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, NSRightMouseUp, 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, NSMouseMoved, 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, NSMouseMoved, 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, NSMouseMoved, null, left, [
+ { type: "mouseout", target: gPopup },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Back over the popup...
+ [190, 80, NSMouseMoved, 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, NSMouseMoved, null, right, [
+ { type: "mouseout", target: gPopup },
+ ]],
+ // Again, no mouse events please, even though a popup is open. (bug 425556)
+ [400, 180, NSMouseMoved, null, right, [
+ ]],
+ // Activate the right window with a click.
+ // This will close the popup and make the mouse enter the right window.
+ [400, 180, NSLeftMouseDown, null, right, [
+ { type: "mouseover", target: rightElem, synthetic: true },
+ ]],
+ [400, 180, NSLeftMouseUp, null, right, [
+ ]],
+ function verifyPopupClosed2(callback) {
+ is(gPopup.popupBoxObject.popupState, "closed", "popup should have closed when clicking");
+ callback();
+ },
+ // Now the right window is active; click it again, just for fun.
+ [400, 180, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 180, NSLeftMouseUp, 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.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendNativeMouseEvent(410, 180, NSMouseMoved, 0, null);
+ },
+ // Now the tooltip is visible.
+ // Move the mouse a little to the right.
+ [411, 180, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Move another pixel.
+ [412, 180, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Move up and click to make the tooltip go away.
+ [412, 80, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [412, 80, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [412, 80, NSLeftMouseUp, 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, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [390, 171, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [391, 171, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Now move off the right window, so that the mouse is directly over the
+ // panel.
+ [260, 170, NSMouseMoved, panel, left, [
+ { type: "mouseout", target: rightElem },
+ ]],
+ [260, 171, NSMouseMoved, panel, left, [
+ ]],
+ [261, 171, NSMouseMoved, panel, left, [
+ ]],
+ // Let's be evil and click it.
+ [261, 171, NSLeftMouseDown, panel, left, [
+ ]],
+ [261, 171, NSLeftMouseUp, 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, NSMouseMoved, panel, left, [
+ { type: "mouseover", target: panel },
+ { type: "mousemove", target: panel },
+ ]],
+ [387, 171, NSMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ [388, 171, NSMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ // Click the panel.
+ [388, 171, NSLeftMouseDown, panel, left, [
+ { type: "mousedown", target: panel }
+ ]],
+ [388, 171, NSLeftMouseUp, 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, NSMouseMoved, null, left, [
+ { type: "mouseout", target: panel },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ [173, 201, NSMouseMoved, 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, NSMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem }
+ ]],
+ // Test that moving inside the window fires mousemove events.
+ [170, 150, NSMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Leaving the window should fire a mouseout event...
+ [170, 20, NSMouseMoved, null, left, [
+ { type: "mouseout", target: leftElem },
+ ]],
+ // ... and entering a mouseover event.
+ [170, 120, NSMouseMoved, 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, NSMouseMoved, 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, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ], NSCommandKeyMask],
+ [400, 150, NSLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ], NSCommandKeyMask],
+ [400, 150, NSOtherMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 150, NSOtherMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // Clicking an inactive window should make it active
+ [400, 150, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 150, NSLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // Now it's focused.
+ [401, 150, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ // Let's drag to the right without letting the button go.
+ [410, 150, NSLeftMouseDragged, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Let go of the mouse.
+ [410, 150, NSLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ // Move the mouse back over the left window, which is inactive.
+ [150, 170, NSMouseMoved, null, left, [
+ { type: "mouseout", target: rightElem },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Right-click it.
+ [150, 170, NSRightMouseDown, null, left, [
+ { type: "mousedown", target: leftElem },
+ ]],
+ // Let go of the mouse.
+ [150, 170, NSRightMouseUp, 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, NSMouseMoved, 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, NSMouseMoved, 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, NSMouseMoved, null, left, [
+ { type: "mouseout", target: gPopup },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ // Back over the popup...
+ [190, 80, NSMouseMoved, gPopup, left, [
+ { type: "mouseout", target: leftElem },
+ { type: "mouseover", target: gPopup },
+ { type: "mousemove", target: gPopup },
+ ]],
+ // ...and over into the right window.
+ [400, 170, NSMouseMoved, null, right, [
+ { type: "mouseout", target: gPopup },
+ { type: "mouseover", target: rightElem },
+ { type: "mousemove", target: rightElem },
+ ]],
+ [400, 180, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Activate the right window with a click.
+ [400, 180, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 180, NSLeftMouseUp, null, right, [
+ { type: "mouseup", target: rightElem },
+ { type: "click", target: rightElem },
+ ]],
+ function verifyPopupClosed2(callback) {
+ is(gPopup.popupBoxObject.popupState, "closed", "popup should have closed when clicking");
+ callback();
+ },
+ // Now the right window is active; click it again, just for fun.
+ [400, 180, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [400, 180, NSLeftMouseUp, 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.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendNativeMouseEvent(410, 180, NSMouseMoved, 0, null);
+ },
+ // Now the tooltip is visible.
+ // Move the mouse a little to the right.
+ [411, 180, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Move another pixel.
+ [412, 180, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Move up and click to make the tooltip go away.
+ [412, 80, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [412, 80, NSLeftMouseDown, null, right, [
+ { type: "mousedown", target: rightElem },
+ ]],
+ [412, 80, NSLeftMouseUp, 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, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [390, 171, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ [391, 171, NSMouseMoved, null, right, [
+ { type: "mousemove", target: rightElem },
+ ]],
+ // Now move off the right window, so that the mouse is directly over the
+ // panel.
+ [260, 170, NSMouseMoved, panel, left, [
+ { type: "mouseout", target: rightElem },
+ { type: "mouseover", target: panel },
+ { type: "mousemove", target: panel },
+ ]],
+ [260, 171, NSMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ [261, 171, NSMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ // Let's be evil and click it.
+ [261, 171, NSLeftMouseDown, panel, left, [
+ { type: "mousedown", target: panel },
+ ]],
+ [261, 171, NSLeftMouseUp, 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, NSMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ [387, 171, NSMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ [388, 171, NSMouseMoved, panel, left, [
+ { type: "mousemove", target: panel },
+ ]],
+ // Click the panel.
+ [388, 171, NSLeftMouseDown, panel, left, [
+ { type: "mousedown", target: panel }
+ ]],
+ [388, 171, NSLeftMouseUp, 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, NSMouseMoved, null, left, [
+ { type: "mouseout", target: panel },
+ { type: "mouseover", target: leftElem },
+ { type: "mousemove", target: leftElem },
+ ]],
+ [173, 201, NSMouseMoved, null, left, [
+ { type: "mousemove", target: leftElem },
+ ]],
+ ];
+ function runNextTest() {
+ if (!tests.length)
+ return onTestsFinished();
+
+ var test = tests.shift();
+ if (typeof test == "function")
+ return test(runNextTest);
+
+ 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.xul b/widget/tests/standalone_native_menu_window.xul
new file mode 100644
index 000000000..6783a66e6
--- /dev/null
+++ b/widget/tests/standalone_native_menu_window.xul
@@ -0,0 +1,334 @@
+<?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.opener.wrappedJSObject.SimpleTest.ok(condition, message);
+ }
+
+ function todo(condition, message) {
+ window.opener.wrappedJSObject.SimpleTest.todo(condition, message);
+ }
+
+ function onTestsFinished() {
+ window.close();
+ window.opener.wrappedJSObject.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 Cc = Components.classes;
+ let Ci = Components.interfaces;
+ 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");
+ 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);
+ delete 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/taskbar_previews.xul b/widget/tests/taskbar_previews.xul
new file mode 100644
index 000000000..2f294d187
--- /dev/null
+++ b/widget/tests/taskbar_previews.xul
@@ -0,0 +1,127 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+ let Cc = Components.classes;
+ let Ci = Components.interfaces;
+ let Cu = Components.utils;
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ let taskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(Ci.nsIWinTaskbar);
+
+ function IsWin7OrHigher() {
+ try {
+ var sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ var ver = parseFloat(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,
+ drawThumbnail: function () { return false; },
+ drawPreview: function () { return false; },
+ get wrappedJSObject() { return this; }
+ }
+ // HACK from mconnor:
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+ let win = 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_assign_event_data.html b/widget/tests/test_assign_event_data.html
new file mode 100644
index 000000000..39f31cafe
--- /dev/null
+++ b/widget/tests/test_assign_event_data.html
@@ -0,0 +1,748 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Testing ns*Event::Assign*EventData()</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" 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 kIsMac = (navigator.platform.indexOf("Mac") == 0);
+const kIsWin = (navigator.platform.indexOf("Win") == 0);
+
+var gEvent = null;
+var gCopiedEvent = [];
+var gCallback = null;
+var gCallPreventDefault = false;
+
+function onEvent(aEvent)
+{
+ if (gCallPreventDefault) {
+ aEvent.preventDefault();
+ }
+ 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);
+}
+
+const kTests = [
+ { description: "InternalScrollPortEvent (overflow, vertical)",
+ targetID: "scrollable-div", eventType: "overflow",
+ dispatchEvent: function () {
+ document.getElementById("scrolled-div").style.height = "500px";
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalScrollPortEvent (overflow, horizontal)",
+ targetID: "scrollable-div", eventType: "overflow",
+ dispatchEvent: function () {
+ document.getElementById("scrolled-div").style.width = "500px";
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalScrollAreaEvent (MozScrolledAreaChanged, spreading)",
+ target: function () { return document; }, eventType: "MozScrolledAreaChanged",
+ dispatchEvent: function () {
+ document.getElementById("scrollable-div").style.width = "50000px";
+ document.getElementById("scrollable-div").style.height = "50000px";
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalScrollAreaEvent (MozScrolledAreaChanged, shrinking)",
+ target: function () { return document; }, eventType: "MozScrolledAreaChanged",
+ dispatchEvent: function () {
+ document.getElementById("scrollable-div").style.width = "30px";
+ document.getElementById("scrollable-div").style.height = "30px";
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keydown of 'a' key without modifiers)",
+ targetID: "input-text", eventType: "keydown",
+ dispatchEvent: function () {
+ 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");
+ },
+ canRun: function () {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keyup of 'a' key without modifiers)",
+ targetID: "input-text", eventType: "keydown",
+ dispatchEvent: function () {
+ 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");
+ },
+ canRun: function () {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keypress of 'b' key with Shift)",
+ targetID: "input-text", eventType: "keypress",
+ dispatchEvent: function () {
+ 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");
+ },
+ canRun: function () {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keypress of 'c' key with Accel)",
+ targetID: "input-text", eventType: "keypress",
+ dispatchEvent: function () {
+ 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");
+ },
+ canRun: function () {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetKeyboardEvent (keyup during composition)",
+ targetID: "input-text", eventType: "keyup",
+ dispatchEvent: function () {
+ 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", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A },
+ });
+ synthesizeComposition({ type: "compositioncommitasis" });
+ setAndObserveCompositionPref(null, runNextTest);
+ });
+ return true;
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetKeyboardEvent (keydown during composition)",
+ targetID: "input-text", eventType: "keydown",
+ dispatchEvent: function () {
+ 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 }
+ });
+ synthesizeComposition({ type: "compositioncommitasis",
+ key: { key: "KEY_Enter", code: "Enter" } });
+ setAndObserveCompositionPref(null, runNextTest);
+ });
+ return true;
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseEvent (mousedown of left button without modifier)",
+ targetID: "button", eventType: "mousedown",
+ dispatchEvent: function () {
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 0 });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetMouseEvent (click of middle button with Shift)",
+ // XXX I'm not sure why middle click event isn't fired on button element.
+ targetID: "input-text", eventType: "click",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 1, shiftKey: true, pressure: 0.5 });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetMouseEvent (mouseup of right button with Alt)",
+ targetID: "button", eventType: "mouseup",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 2, altKey: true });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetDragEvent",
+ targetID: "input-text", eventType: "dragstart",
+ dispatchEvent: function () {
+ return;
+ },
+ canRun: function () {
+ todo(false, "WidgetDragEvent isn't tested");
+ return false;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetTextEvent (text)",
+ targetID: "input-text", eventType: "text",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeComposition({ type: "compositioncommit", data: "\u306D" });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetCompositionEvent (compositionupdate)",
+ targetID: "input-text", eventType: "compositionupdate",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ document.getElementById(this.targetID).focus();
+ synthesizeComposition({ type: "compositioncommit", data: "\u30E9\u30FC\u30E1\u30F3" });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalEditorInputEvent (input at key input)",
+ targetID: "input-text", eventType: "input",
+ dispatchEvent: function () {
+ 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");
+ },
+ canRun: function () {
+ return (kIsMac || kIsWin);
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalEditorInputEvent (input at composing)",
+ targetID: "input-text", eventType: "input",
+ dispatchEvent: function () {
+ 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 }
+ });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalEditorInputEvent (input at committing)",
+ targetID: "input-text", eventType: "input",
+ dispatchEvent: function () {
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (DOMMouseScroll, vertical)",
+ targetID: "input-text", eventType: "DOMMouseScroll",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 3, 4,
+ { deltaY: 30, lineOrPageDeltaY: 2 });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (DOMMouseScroll, horizontal)",
+ targetID: "input-text", eventType: "DOMMouseScroll",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 30, lineOrPageDeltaX: 2, shiftKey: true });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (MozMousePixelScroll, vertical)",
+ targetID: "input-text", eventType: "MozMousePixelScroll",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 3, 4,
+ { deltaY: 20, lineOrPageDeltaY: 1, altKey: true });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetMouseScrollEvent (MozMousePixelScroll, horizontal)",
+ targetID: "input-text", eventType: "MozMousePixelScroll",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 20, lineOrPageDeltaX: 1, ctrlKey: true });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetWheelEvent (wheel, vertical)",
+ targetID: "input-text", eventType: "wheel",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 3, 4,
+ { deltaY: 20, lineOrPageDeltaY: 1, altKey: true });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetWheelEvent (wheel, horizontal)",
+ targetID: "input-text", eventType: "wheel",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 20, lineOrPageDeltaX: 1, ctrlKey: true });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetWheelEvent (wheel, both)",
+ targetID: "input-text", eventType: "wheel",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).value = "";
+ synthesizeWheel(document.getElementById(this.targetID), 4, 5,
+ { deltaX: 20, deltaY: 10,
+ lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetTouchEvent (touchstart)",
+ target: function () { return document; }, eventType: "touchstart",
+ dispatchEvent: function () {
+ synthesizeTouchAtPoint(1, 2, { id: 10, rx: 4, ry: 3, angle: 0, force: 1, shiftKey: true});
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetTouchEvent (touchend)",
+ target: function () { return document; }, eventType: "touchend",
+ dispatchEvent: function () {
+ synthesizeTouchAtPoint(4, 6, { id: 5, rx: 1, ry: 2, angle: 0.5, force: 0.8, ctrlKey: true});
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalFormEvent (reset)",
+ targetID: "form", eventType: "reset",
+ dispatchEvent: function () {
+ document.getElementById("form").reset();
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "WidgetCommandEvent",
+ targetID: "input-text", eventType: "",
+ dispatchEvent: function () {
+ return;
+ },
+ canRun: function () {
+ todo(false, "WidgetCommandEvent isn't tested");
+ return false;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalClipboardEvent (copy)",
+ targetID: "input-text", eventType: "copy",
+ dispatchEvent: function () {
+ document.getElementById("input-text").value = "go to clipboard!";
+ document.getElementById("input-text").focus();
+ document.getElementById("input-text").select();
+ synthesizeKey("c", { accelKey: true });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [ ],
+ },
+ { description: "InternalUIEvent (DOMActivate)",
+ targetID: "button", eventType: "DOMActivate",
+ dispatchEvent: function () {
+ synthesizeMouseAtCenter(document.getElementById(this.targetID),
+ { button: 0, shiftKey: true });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalFocusEvent (focus)",
+ targetID: "input-text", eventType: "focus",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).focus();
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalFocusEvent (blur)",
+ targetID: "input-text", eventType: "blur",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).blur();
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "WidgetSimpleGestureEvent",
+ targetID: "", eventType: "",
+ dispatchEvent: function () {
+ return;
+ },
+ canRun: function () {
+ // 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: function () {
+ document.getElementById(this.targetID).focus();
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalAnimationEvent (animationend)",
+ targetID: "animated-div", eventType: "animationend",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).className = "slidin";
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalMutationEvent (DOMAttrModified)",
+ targetID: "animated-div", eventType: "DOMAttrModified",
+ dispatchEvent: function () {
+ document.getElementById(this.targetID).setAttribute("x-data", "foo");
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalMutationEvent (DOMNodeInserted)",
+ targetID: "animated-div", eventType: "DOMNodeInserted",
+ dispatchEvent: function () {
+ var div = document.createElement("div");
+ div.id = "inserted-div";
+ document.getElementById("animated-div").appendChild(div);
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "InternalMutationEvent (DOMNodeRemoved)",
+ targetID: "animated-div", eventType: "DOMNodeRemoved",
+ dispatchEvent: function () {
+ document.getElementById("animated-div").removeChild(document.getElementById("inserted-div"));
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "PointerEvent (pointerdown)",
+ targetID: "pointer-target", eventType: "pointerdown",
+ dispatchEvent: function () {
+ var elem = document.getElementById(this.targetID);
+ var rect = elem.getBoundingClientRect();
+ synthesizePointer(elem, rect.width/2, rect.height/2,
+ { type: this.eventType, button: 1, clickCount: 1, inputSource: 2, pressure: 0.25, isPrimary: true });
+ },
+ canRun: function () {
+ return true;
+ },
+ todoMismatch: [],
+ },
+ { description: "PointerEvent (pointerup)",
+ targetID: "pointer-target", eventType: "pointerup",
+ dispatchEvent: function () {
+ var elem = document.getElementById(this.targetID);
+ var rect = elem.getBoundingClientRect();
+ synthesizePointer(elem, rect.width/2, rect.height/2,
+ { type: this.eventType, button: -1, ctrlKey: true, shiftKey: true, altKey: true, isSynthesized: false });
+ },
+ canRun: function () {
+ 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";
+ let branch = SpecialPowers.Cc["@mozilla.org/preferences-service;1"].
+ getService(SpecialPowers.Ci.nsIPrefService).
+ getBranch(pref);
+ let obs = SpecialPowers.wrapCallback(function () {
+ branch.removeObserver("", obs);
+ // Make sure the code under test sees the change first, so executeSoon().
+ SimpleTest.executeSoon(aCallback);
+ });
+ branch.addObserver("", obs, false);
+ if (aValue === null) {
+ SpecialPowers.clearUserPref(pref);
+ } else {
+ SpecialPowers.setBoolPref(pref, aValue);
+ }
+}
+
+function doTest(aTest)
+{
+ if (!aTest.canRun()) {
+ SimpleTest.executeSoon(runNextTest);
+ return;
+ }
+ gEvent = null;
+ gCopiedEvent = [];
+ var target = aTest.target ? aTest.target() : document.getElementById(aTest.targetID);
+ target.addEventListener(aTest.eventType, onEvent, true);
+ gCallback = function () {
+ var description = aTest.description + " (gCallPreventDefault=" + gCallPreventDefault + ")";
+ target.removeEventListener(aTest.eventType, onEvent, true);
+ ok(gEvent !== null, description + ": failed to get duplicated event");
+ ok(gCopiedEvent.length > 0, description + ": 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, description + ": " + name + " attribute value is never reset (" + gEvent[name] + ")");
+ } else if (name == "eventPhase") {
+ is(gEvent[name], 0, description + ": mismatch with fixed value (" + name + ")");
+ } else if (name == "rangeParent" || name == "currentTarget") {
+ is(gEvent[name], null, description + ": mismatch with fixed value (" + name + ")");
+ } else if (aTest.todoMismatch.indexOf(name) >= 0) {
+ todo_is(gEvent[name], gCopiedEvent[i].value, description + ": 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, description + ": 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],
+ ["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_bug1123480.xul b/widget/tests/test_bug1123480.xul
new file mode 100644
index 000000000..56ce0ed10
--- /dev/null
+++ b/widget/tests/test_bug1123480.xul
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ // Boilerplate constructs
+ var SmallDataset = 100000; // Hundred thousand bytes
+
+ // Create 1 Mo of sample garbage text
+ var Ipsum = ""; // Select text from this
+ for (var Iter = 0; Iter < SmallDataset; Iter++) {
+ Ipsum += Math.random().toString(36) + ' ';
+ }
+
+ function RunTest() {
+ // Construct a nsIFile object for access to file methods
+ Components.utils.import("resource://gre/modules/FileUtils.jsm");
+ var clipboardFile = FileUtils.getFile("TmpD", ["clipboardcache"]);
+
+ // Sanitize environment
+ if (clipboardFile.exists()) {
+ clipboardFile.remove(false);
+ }
+ ok(!clipboardFile.exists(), "failed to presanitize the environment");
+
+ // Overflow a nsTransferable region by using the clipboard helper
+ const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
+ gClipboardHelper.copyString(Ipsum);
+
+ // Disabled private browsing mode should cache large selections to disk
+ ok(clipboardFile.exists(), "correctly saved memory by caching to disk");
+
+ // Sanitize environment again
+ if (clipboardFile.exists()) {
+ clipboardFile.remove(false);
+ }
+ ok(!clipboardFile.exists(), "failed to postsanitize the environment");
+
+ // Repeat procedure of plain text selection with private browsing enabled
+ Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+ var Winpriv = window.open("about:blank", "_blank", "chrome, width=500, height=200, private");
+ ok(Winpriv, "failed to open private window");
+ ok(PrivateBrowsingUtils.isContentWindowPrivate(Winpriv), "correctly used a private window context");
+
+ // Select plaintext in private channel
+ Components.utils.import('resource://gre/modules/Services.jsm');
+ const nsTransferable = Components.Constructor("@mozilla.org/widget/transferable;1", "nsITransferable");
+ const nsSupportsString = Components.Constructor("@mozilla.org/supports-string;1", "nsISupportsString");
+ var Loadctx = PrivateBrowsingUtils.privacyContextFromWindow(Winpriv);
+ var Transfer = nsTransferable();
+ var Suppstr = nsSupportsString();
+ Suppstr.data = Ipsum;
+ Transfer.init(Loadctx);
+ Transfer.addDataFlavor("text/plain");
+ Transfer.setTransferData("text/plain", Suppstr, Ipsum.length);
+ Services.clipboard.setData(Transfer, null, Services.clipboard.kGlobalClipboard);
+
+ // Enabled private browsing mode should not cache any selection to disk
+ ok(!clipboardFile.exists(), "did not violate private browsing mode");
+ }
+ ]]>
+ </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_bug1151186.html b/widget/tests/test_bug1151186.html
new file mode 100644
index 000000000..391e53d78
--- /dev/null
+++ b/widget/tests/test_bug1151186.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1151186
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1151186</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1151186 **/
+ SimpleTest.waitForExplicitFinish();
+
+ document.addEventListener("focus", () => {
+ document.getElementById("editor").focus();
+ SimpleTest.executeSoon(runTests);
+ });
+
+ function runTests()
+ {
+ is(document.activeElement, document.getElementById("editor"),
+ "The div element should be focused");
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ is(utils.IMEStatus, utils.IME_STATUS_ENABLED,
+ "IME should be enabled");
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151186">Mozilla Bug 1151186</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="editor" contenteditable="true"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/widget/tests/test_bug343416.xul b/widget/tests/test_bug343416.xul
new file mode 100644
index 000000000..1c31bdf54
--- /dev/null
+++ b/widget/tests/test_bug343416.xul
@@ -0,0 +1,202 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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: function _qi(iid)
+ {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIObserver))
+ {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ 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 nsIIdleService
+ var subjectOK = false;
+ try {
+ var idleService = subject.QueryInterface(nsIIdleService);
+ subjectOK = true;
+ }
+ catch (ex)
+ {}
+ ok(subjectOK, "The subject of the notification should be the " +
+ "nsIIdleService.");
+
+ // 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 nsIIdleService = Components.interfaces.nsIIdleService;
+const nsIISCID = "@mozilla.org/widget/idleservice;1";
+var idleService = null;
+try
+{
+ idleService = Components.classes[nsIISCID].getService(nsIIdleService);
+}
+catch (ex)
+{}
+
+ok(idleService, "nsIIdleService 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 nsIIdleService should allow us to add an observer.");
+
+addedObserver = false;
+try
+{
+ idleService.addIdleObserver(idleObserver, newIdleSeconds);
+ addedObserver = true;
+}
+catch (ex)
+{}
+
+ok(addedObserver, "The nsIIdleService should allow us to add the same observer again.");
+
+var removedObserver = false;
+try
+{
+ idleService.removeIdleObserver(idleObserver, newIdleSeconds);
+ removedObserver = true;
+}
+catch (ex)
+{}
+
+ok(removedObserver, "The nsIIdleService should allow us to remove the observer just once.");
+
+function testIdleTime()
+{
+ 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));
+
+ var timePassed = Date.now() - startTimeStamp;
+ var idleTimeDiff = newIdleTime - idleTime;
+ // 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 000000000..dfb1ac8d4
--- /dev/null
+++ b/widget/tests/test_bug413277.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=413277
+-->
+<head>
+ <title>Test for Bug 413277</title>
+ <script type="text/javascript"
+ 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.xul b/widget/tests/test_bug428405.xul
new file mode 100644
index 000000000..365480468
--- /dev/null
+++ b/widget/tests/test_bug428405.xul
@@ -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 type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="text/javascript" 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[
+
+ 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() {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
+ getService(Components.interfaces.nsIWindowMediator);
+ gChromeWindow = 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.
+ gChromeWindow.addEventListener("keypress", onKeyPress, false);
+ }
+
+ // 1) Start loading first tab.
+ // 6) Start reloading first tab.
+ function loadFirstTab() {
+ var browser = document.getElementById("tab1browser");
+ browser.loadURI("data:text/html;charset=utf-8,<body><h2>First Tab</h2><p><input type='submit' value='Button' id='button1'/></body>", null, null);
+ }
+
+ function configureFirstTab() {
+ try {
+ var button = document.getElementById("tab1browser").contentDocument.getElementById("button1");
+ button.addEventListener("click", onFirstTabButtonClicked, false);
+ 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");
+ browser.loadURI("data:text/html;charset=utf-8,<body><h2>Second Tab</h2><p><input type='submit' value='Button' id='button1'/></body>", null, null);
+ }
+
+ function configureSecondTab() {
+ try {
+ var button = document.getElementById("tab2browser").contentDocument.getElementById("button1");
+ button.addEventListener("click", onSecondTabButtonClicked, false);
+ 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.xul b/widget/tests/test_bug429954.xul
new file mode 100644
index 000000000..9b617ed4d
--- /dev/null
+++ b/widget/tests/test_bug429954.xul
@@ -0,0 +1,44 @@
+<?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 type="application/javascript"
+ 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 () {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
+ getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("navigator:browser");
+ win.maximize();
+ var maxX = win.screenX, maxY = win.screenY;
+ var maxWidth = win.outerWidth, maxHeight = win.outerHeight;
+ win.restore();
+
+ window.open("window_bug429954.xul", "_blank",
+ "chrome,resizable,width=" + maxWidth + ",height=" + maxHeight +
+ "screenX=" + maxX + "screenY=" + maxY);
+});
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug444800.xul b/widget/tests/test_bug444800.xul
new file mode 100644
index 000000000..14d572baa
--- /dev/null
+++ b/widget/tests/test_bug444800.xul
@@ -0,0 +1,102 @@
+<?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 type="application/javascript"
+ 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 = Components.interfaces.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() {
+ const Ci = Components.interfaces;
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function runImageClipboardTests(aCBSvc, aImageType)
+{
+ // Verify that hasDataMatchingFlavors() is working correctly.
+ var typeArray = [ aImageType ];
+ var hasImage = aCBSvc.hasDataMatchingFlavors(typeArray, typeArray.length,
+ knsIClipboard.kGlobalClipboard);
+ ok(hasImage, aImageType + " - hasDataMatchingFlavors()");
+
+ // Verify that getData() is working correctly.
+ var xfer = Components.classes["@mozilla.org/widget/transferable;1"]
+ .createInstance(Components.interfaces.nsITransferable);
+ xfer.init(getLoadContext());
+ xfer.addDataFlavor(aImageType);
+ aCBSvc.getData(xfer, knsIClipboard.kGlobalClipboard);
+
+ var typeObj = {}, dataObj = {}, lenObj = {};
+ xfer.getAnyTransferData(typeObj, dataObj, lenObj);
+ var gotValue = (null != dataObj.value);
+ ok(gotValue, aImageType + " - getData() returned a value");
+ if (gotValue)
+ {
+ const knsIInputStream = Components.interfaces.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 = Components.classes["@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.xul b/widget/tests/test_bug466599.xul
new file mode 100644
index 000000000..a70f47add
--- /dev/null
+++ b/widget/tests/test_bug466599.xul
@@ -0,0 +1,109 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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() {
+ const Ci = Components.interfaces;
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function copyToClipboard(txt)
+{
+ var clipid = Components.interfaces.nsIClipboard;
+ var clip =
+ Components.classes['@mozilla.org/widget/clipboard;1'].createInstance(clipid);
+ if (!clip)
+ return false;
+ var trans =
+ Components.classes['@mozilla.org/widget/transferable;1'].createInstance(Components.interfaces.nsITransferable);
+ if (!trans)
+ return false;
+ trans.init(getLoadContext());
+ trans.addDataFlavor('text/html');
+ var str =
+ Components.classes['@mozilla.org/supports-string;1'].createInstance(Components.interfaces.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 = Components.interfaces.nsIClipboard;
+ var clip =
+ Components.classes['@mozilla.org/widget/clipboard;1'].createInstance(clipid);
+ if (!clip)
+ return;
+ var trans =
+ Components.classes['@mozilla.org/widget/transferable;1'].createInstance(Components.interfaces.nsITransferable);
+ if (!trans)
+ return;
+ trans.init(getLoadContext());
+ trans.addDataFlavor('text/html');
+ clip.getData(trans,clipid.kGlobalClipboard);
+ var str = new Object();
+ var strLength = new Object();
+ trans.getTransferData("text/html",str,strLength);
+ if (str)
+ str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
+ if (str)
+ pastetext = str.data.substring(0,strLength.value / 2);
+ return pastetext;
+}
+
+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");
+
+ $("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.xul b/widget/tests/test_bug478536.xul
new file mode 100644
index 000000000..e83ff6032
--- /dev/null
+++ b/widget/tests/test_bug478536.xul
@@ -0,0 +1,34 @@
+<?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 type="application/javascript"
+ 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.open("window_bug478536.xul", "_blank",
+ "chrome,width=600,height=600");
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug485118.xul b/widget/tests/test_bug485118.xul
new file mode 100644
index 000000000..bbd5daed9
--- /dev/null
+++ b/widget/tests/test_bug485118.xul
@@ -0,0 +1,71 @@
+<?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 type="application/javascript"
+ 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="-moz-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="-moz-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 = document.getAnonymousElementByAttribute(elem, 'sbattr', 'scrollbar-thumb').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, false);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug517396.xul b/widget/tests/test_bug517396.xul
new file mode 100644
index 000000000..18a1b8f59
--- /dev/null
+++ b/widget/tests/test_bug517396.xul
@@ -0,0 +1,56 @@
+<?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 type="application/javascript"
+ 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.indexOf("Lin") != -1) {
+ ok(true, "disabled on Linux");
+ SimpleTest.finish();
+ return;
+ }
+
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
+ getService(Components.interfaces.nsIWindowMediator);
+ var win = 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.xul b/widget/tests/test_bug522217.xul
new file mode 100644
index 000000000..22aa1a061
--- /dev/null
+++ b/widget/tests/test_bug522217.xul
@@ -0,0 +1,36 @@
+<?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 type="application/javascript"
+ 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.open("window_bug522217.xul", "_blank",
+ "chrome,resizable,width=400,height=300");
+});
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug538242.xul b/widget/tests/test_bug538242.xul
new file mode 100644
index 000000000..9ebab5259
--- /dev/null
+++ b/widget/tests/test_bug538242.xul
@@ -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 type="application/javascript"
+ 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.indexOf("Lin") != -1) {
+ 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.open("window_bug538242.xul", "_blank",
+ "chrome=1,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 000000000..da6999ec6
--- /dev/null
+++ b/widget/tests/test_bug565392.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=565392
+-->
+<head>
+ <title>Test for Bug 565392</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/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">
+
+/** Test for Bug 565392 **/
+
+ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+ const Cc = SpecialPowers.Cc;
+ const Ci = SpecialPowers.Ci;
+ var ds = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+ var dir1 = ds.get("ProfD", Ci.nsIFile);
+ var clipboard = Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(Ci.nsIClipboard);
+
+ function getLoadContext() {
+ return SpecialPowers.wrap(window).QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .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, 0);
+ 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,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.xul b/widget/tests/test_bug586713.xul
new file mode 100644
index 000000000..e91eb7931
--- /dev/null
+++ b/widget/tests/test_bug586713.xul
@@ -0,0 +1,30 @@
+<?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 type="application/javascript"
+ 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.open("bug586713_window.xul", "bug586713_window",
+ "chrome,width=600,height=600");
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug593307.xul b/widget/tests/test_bug593307.xul
new file mode 100644
index 000000000..0b6e4d4a1
--- /dev/null
+++ b/widget/tests/test_bug593307.xul
@@ -0,0 +1,46 @@
+<?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 type="application/javascript"
+ 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.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindow);
+
+var offscreenWindow = mainWindow.openDialog("window_bug593307_offscreen.xul", "",
+ "dialog=no,chrome,width=200,height=200,screenX=-3000,screenY=-3000",
+ SimpleTest, finish);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_bug596600.xul b/widget/tests/test_bug596600.xul
new file mode 100644
index 000000000..0468f7d4d
--- /dev/null
+++ b/widget/tests/test_bug596600.xul
@@ -0,0 +1,177 @@
+<?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 type="application/javascript"
+ 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 NSMouseMoved = 5;
+
+var gLeftWindow, gRightWindow, gIFrame;
+var gExpectedEvents = [];
+
+function moveMouseTo(x, y, andThen) {
+ var utils = gLeftWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendNativeMouseEvent(x, y, NSMouseMoved, 0, gLeftWindow.documentElement);
+ SimpleTest.executeSoon(andThen);
+}
+
+function openWindows() {
+ gLeftWindow = open('empty_window.xul', '_blank', 'chrome,screenX=50,screenY=50,width=200,height=200');
+ SimpleTest.waitForFocus(function () {
+ gRightWindow = open('empty_window.xul', '', 'chrome,screenX=300,screenY=50,width=200,height=200');
+ SimpleTest.waitForFocus(attachIFrameToRightWindow, gRightWindow);
+ }, gLeftWindow);
+}
+
+function attachIFrameToRightWindow() {
+ gIFrame = gLeftWindow.document.createElementNS(XUL_NS, "iframe");
+ gIFrame.setAttribute("type", "content");
+ gIFrame.setAttribute("clickthrough", "never");
+ gIFrame.setAttribute("src", "data:text/html,<!DOCTYPE html>Content page");
+ gIFrame.style.width = "100px";
+ gIFrame.style.height = "100px";
+ gIFrame.style.margin = "50px";
+ gLeftWindow.document.documentElement.appendChild(gIFrame);
+ gIFrame.contentWindow.addEventListener("load", function (e) {
+ gIFrame.removeEventListener("load", arguments.callee, false);
+ test1();
+ }, false);
+}
+
+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, false);
+ gLeftWindow.addEventListener("mouseout", mouseOutListener, false);
+
+ // Move into the left window
+ expectMouseOver = true;
+ moveMouseTo(80, 80, function () {
+ ok(!expectMouseOver, "Should have got mouseover event");
+
+ // Move over the iframe, which has clickthrough="never".
+ expectMouseOut = true;
+ moveMouseTo(150, 150, function () {
+ ok (!expectMouseOut, "Should have got mouseout event");
+ gLeftWindow.removeEventListener("mouseover", mouseOverListener, false);
+ gLeftWindow.removeEventListener("mouseout", mouseOutListener, false);
+ test2();
+ });
+ });
+ });
+}
+
+function test2() {
+ // Make the iframe cover the whole window.
+ gIFrame.style.margin = "0";
+ gIFrame.style.width = gIFrame.style.height = "200px";
+
+ // Add a box to the iframe at the left edge.
+ var doc = gIFrame.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 iframe 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,
+ false);
+ elementToWatchForMouseEventOn.removeEventListener("mouseout",
+ mouseWatcher,
+ false);
+ SimpleTest.executeSoon(callback);
+ }
+ elementToWatchForMouseEventOn.addEventListener("mouseover",
+ mouseWatcher,
+ false);
+ elementToWatchForMouseEventOn.addEventListener("mouseout",
+ mouseWatcher,
+ false);
+ // 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 iframe in a background window)");
+ // Activate the left window.
+ changeFocusAndAwaitSyntheticMouse(function () {
+ ok(gIFrame.matches(":hover"), "iframe should be hovered");
+ ok(box.matches(":hover"), "Box should be hovered");
+ // De-activate the window (by activating the right window).
+ changeFocusAndAwaitSyntheticMouse(function () {
+ ok(!gIFrame.matches(":hover"), "iframe shouldn't be hovered");
+ ok(!box.matches(":hover"), "Box shouldn't be hovered");
+ // Re-activate it.
+ changeFocusAndAwaitSyntheticMouse(function () {
+ ok(gIFrame.matches(":hover"), "iframe should be hovered");
+ ok(box.matches(":hover"), "Box should be hovered");
+ // Unhover box and iframe by moving the mouse outside the window.
+ moveMouseTo(0, 150, function () {
+ const isOSXSnowLeopard = navigator.userAgent.indexOf("Mac OS X 10.6") != -1;
+ if (!isOSXSnowLeopard) {
+ ok(!gIFrame.matches(":hover"), "iframe 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.xul b/widget/tests/test_bug673301.xul
new file mode 100644
index 000000000..a5736b86f
--- /dev/null
+++ b/widget/tests/test_bug673301.xul
@@ -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"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"/>
+<script type="application/javascript" 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() {
+ const Ci = Components.interfaces;
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
+ .getService(Components.interfaces.nsIClipboard);
+
+var transferable = Components.classes['@mozilla.org/widget/transferable;1']
+ .createInstance(Components.interfaces.nsITransferable);
+transferable.init(getLoadContext());
+
+transferable.addDataFlavor("text/unicode");
+transferable.setTransferData("text/unicode", document, 4);
+
+clipboard.setData(transferable, null, Components.interfaces.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.xul b/widget/tests/test_bug760802.xul
new file mode 100644
index 000000000..c79be785e
--- /dev/null
+++ b/widget/tests/test_bug760802.xul
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript" 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();
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function getBaseWindowInterface(win) {
+ return win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .nsIBaseWindow;
+}
+
+function getBaseWindowInterfaceFromDocShell(win) {
+ return win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIBaseWindow);
+}
+
+function shouldThrowException(fun, exception) {
+ try {
+ fun.call();
+ return false;
+ } catch (e) {
+ $("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;
+$("display").innerHTML = "found nativeHandle for this window: "+nativeHandle;
+
+var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+var win = wm.getMostRecentWindow("navigator:browser");
+var docShell = getBaseWindowInterfaceFromDocShell(win);
+
+ok(
+ shouldThrowException(function(){docShell.nativeHandle;},
+ Components.results.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_chrome_context_menus_win.xul b/widget/tests/test_chrome_context_menus_win.xul
new file mode 100644
index 000000000..575e7743d
--- /dev/null
+++ b/widget/tests/test_chrome_context_menus_win.xul
@@ -0,0 +1,27 @@
+<?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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<script type="application/javascript" src="utils.js"></script>
+<script>
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+ SimpleTest.waitForExplicitFinish();
+
+ var w = window.open('chrome_context_menus_win.xul', '_blank', 'chrome,resizable=yes,width=600,height=600');
+
+ function done()
+ {
+ w.close();
+ SimpleTest.finish();
+ }
+</script>
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" />
+</window>
diff --git a/widget/tests/test_clipboard.xul b/widget/tests/test_clipboard.xul
new file mode 100644
index 000000000..19a55714d
--- /dev/null
+++ b/widget/tests/test_clipboard.xul
@@ -0,0 +1,80 @@
+<?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 type="application/javascript"
+ 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 Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ const kIsMac = navigator.platform.indexOf("Mac") == 0;
+
+ function getLoadContext() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .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 = {};
+ let length = {};
+ try {
+ trans.getTransferData('text/unicode', str, length);
+ } catch (e) {
+ str = '';
+ }
+ if (str) {
+ str = str.value.QueryInterface(Ci.nsISupportsString);
+ if (str) {
+ str = str.data.substring(0, length.value / 2);
+ }
+ }
+ return str;
+ }
+
+ function initAndRunTests() {
+ let clipboard = Cc['@mozilla.org/widget/clipboard;1']
+ .getService(Ci.nsIClipboard);
+
+ // Test copy.
+ const data = "random number: " + Math.random();
+ let helper = Cc['@mozilla.org/widget/clipboardhelper;1']
+ .getService(Ci.nsIClipboardHelper);
+ helper.copyString(data);
+ is(paste(clipboard), data, 'Data was successfully copied.');
+
+ // Test emptyClipboard, disabled for OSX because bug 666254
+ if (!kIsMac) {
+ clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
+ is(paste(clipboard), '', 'Data was successfully cleared.');
+ }
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/widget/tests/test_composition_text_querycontent.xul b/widget/tests/test_composition_text_querycontent.xul
new file mode 100644
index 000000000..e48a1b14a
--- /dev/null
+++ b/widget/tests/test_composition_text_querycontent.xul
@@ -0,0 +1,34 @@
+<?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 type="application/javascript"
+ 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 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.
+SimpleTest.expectAssertions(0, 3);
+SimpleTest.waitForExplicitFinish();
+window.open("window_composition_text_querycontent.xul", "_blank",
+ "chrome,width=600,height=600");
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_imestate.html b/widget/tests/test_imestate.html
new file mode 100644
index 000000000..fe5a3cce2
--- /dev/null
+++ b/widget/tests/test_imestate.html
@@ -0,0 +1,1529 @@
+<html style="ime-mode: disabled;">
+<head>
+ <title>Test for IME state controling</title>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ 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;">
+<script>
+setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+</script>
+<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">
+
+SimpleTest.waitForExplicitFinish();
+
+function hitEventLoop(aFunc, aTimes)
+{
+ if (--aTimes) {
+ setTimeout(hitEventLoop, 0, aFunc, aTimes);
+ } else {
+ setTimeout(aFunc, 20);
+ }
+}
+
+var gUtils = window.
+ QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+var gFM = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+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 = Components.classes["@mozilla.org/text-input-processor;1"]
+ .createInstance(Components.interfaces.nsITextInputProcessor);
+ if (!TIP.beginInputTransactionForTests(window, TIPCallback)) {
+ ok(false, "runBasicTest(): failed to begin input transaction");
+ return;
+ }
+
+ function test(aTest)
+ {
+ function moveFocus(aTest, 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;
+ var subDocument = null;
+ if (element.contentDocument) {
+ focusEventTarget = element.contentDocument;
+ subDocument = 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;
+ if (focusedElement) {
+ var bindingParent = document.getBindingParent(focusedElement);
+ if (bindingParent) {
+ focusedElement = bindingParent;
+ }
+ }
+ 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(aTest, 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;
+ 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;
+ }
+
+ if (navigator.platform.indexOf("Mac") == 0) {
+ // XXX on mac, currently, this test isn't passed because it doesn't return
+ // IME_STATUS_PLUGIN by its bug.
+ return;
+ }
+
+ var plugin = document.getElementById("plugin");
+
+ 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_PLUGIN,
+ "runPluginTest: unexpected enabled state when plugin has focus");
+
+ plugin.blur();
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+ "runPluginTest: unexpected enabled state when plugin has focus");
+
+ plugin.focus();
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_PLUGIN,
+ "runPluginTest: unexpected enabled state when plugin has focus #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 =
+ Components.interfaces.nsIPlaintextEditor.eEditorReadonlyMask;
+ var editor =
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIWebNavigation).
+ QueryInterface(Components.interfaces.nsIDocShell).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.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIWebNavigation).
+ QueryInterface(Components.interfaces.nsIDocShell).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 isReadonly = aEditor.readOnly;
+ 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.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIWebNavigation).
+ QueryInterface(Components.interfaces.nsIDocShell).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.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIWebNavigation).
+ QueryInterface(Components.interfaces.nsIDocShell).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 =
+ Components.interfaces.nsIPlaintextEditor.eEditorPasswordMask |
+ Components.interfaces.nsIPlaintextEditor.eEditorReadonlyMask |
+ Components.interfaces.nsIPlaintextEditor.eEditorDisabledMask;
+ var editor =
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIWebNavigation).
+ QueryInterface(Components.interfaces.nsIDocShell).editor;
+ var editorIMESupport =
+ editor.QueryInterface(Components.interfaces.nsIEditorIMESupport);
+ 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(editorIMESupport.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;
+ ok(editorIMESupport.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(editorIMESupport.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()
+ {
+ Components.classes["@mozilla.org/observer-service;1"].
+ getService(Components.interfaces.nsIObserverService).
+ addObserver(this, "domwindowopened", false);
+ }
+
+ WindowObserver.prototype = {
+ QueryInterface: function (iid)
+ {
+ if (iid.equals(Components.interfaces.nsIObserver) ||
+ iid.equals(Components.interfaces.nsISupports)) {
+ return this;
+ }
+ },
+
+ observe: function (subject, topic, data)
+ {
+ if (topic === "domwindowopened") {
+ ok(true, "dialog window is created");
+ dialog = subject.QueryInterface(Components.interfaces.nsIDOMWindow);
+ dialog.addEventListener("load", onPasswordDialogLoad, false);
+ }
+ }
+ };
+
+ var observer = new WindowObserver();
+ var arg1 = new Object(), arg2 = new Object();
+ Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Components.interfaces.nsIPromptService).
+ promptPassword(window, "title", "text", arg1, "msg", arg2);
+
+ ok(true, "password dialog was closed");
+
+ Components.classes["@mozilla.org/observer-service;1"].
+ getService(Components.interfaces.nsIObserverService).
+ removeObserver(observer, "domwindowopened");
+
+ var passwordField;
+
+ function onPasswordDialogLoad()
+ {
+ ok(true, "onPasswordDialogLoad is called");
+ dialog.removeEventListener("load", onPasswordDialogLoad, false);
+ passwordField = dialog.document.getElementById("password1Textbox");
+ passwordField.addEventListener("focus", onPasswordFieldFocus, false);
+ }
+
+ function onPasswordFieldFocus()
+ {
+ ok(true, "onPasswordFieldFocus is called");
+ passwordField.removeEventListener("focus", onPasswordFieldFocus, false);
+ var utils = dialog.
+ QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ 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
+function runEditorReframeTests(aCallback)
+{
+ 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 = Components.classes["@mozilla.org/text-input-processor;1"]
+ .createInstance(Components.interfaces.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);
+
+ hitEventLoop(function () {
+ 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);
+
+ hitEventLoop(function() {
+ 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;
+
+ hitEventLoop(aCallback, 20);
+ }, 20);
+ }, 20);
+}
+
+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
+ runEditorReframeTests(function () {
+ // 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.xul b/widget/tests/test_input_events_on_deactive_window.xul
new file mode 100644
index 000000000..a85646266
--- /dev/null
+++ b/widget/tests/test_input_events_on_deactive_window.xul
@@ -0,0 +1,236 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ 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);
+
+var fm = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+var textarea = document.getElementById("textarea");
+var otherWindow;
+var timer;
+
+function runTests()
+{
+ textarea.focus();
+ is(fm.focusedElement, textarea, "we're deactive");
+ if (fm.focusedElement != textarea) {
+ SimpleTest.finish();
+ return;
+ }
+
+ otherWindow =
+ window.open("data:text/plain,this is an active window.", "_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(fm.focusedWindow, window, "we're not deactive");
+ if (fm.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, false);
+ textarea.addEventListener("keypress", onEvent, false);
+ textarea.addEventListener("keyup", onEvent, false);
+ textarea.addEventListener("compositionstart", onEvent, false);
+ textarea.addEventListener("compositionend", onEvent, false);
+ textarea.addEventListener("compositionupdate", onEvent, false);
+ textarea.addEventListener("input", onEvent, false);
+
+ 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("VK_BACK_SPACE", {});
+ checkKeyEvents(true, true, true, true, "VK_BACK_SPACE 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, false);
+ textarea.removeEventListener("keypress", onEvent, false);
+ textarea.removeEventListener("keyup", onEvent, false);
+ textarea.removeEventListener("compositionstart", onEvent, false);
+ textarea.removeEventListener("compositionupdate", onEvent, false);
+ textarea.removeEventListener("compositionend", onEvent, false);
+ textarea.removeEventListener("input", onEvent, false);
+
+ otherWindow.close();
+
+ SimpleTest.finish();
+}
+
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/test_key_event_counts.xul b/widget/tests/test_key_event_counts.xul
new file mode 100644
index 000000000..4dd4b83da
--- /dev/null
+++ b/widget/tests/test_key_event_counts.xul
@@ -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 type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="text/javascript" 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 gKeyDownEventCound = 0;
+
+ function onKeyDown(e)
+ {
+ gKeyDownEventCount++;
+ }
+
+ function onKeyPress(e)
+ {
+ gKeyPressEventCount++;
+ e.preventDefault();
+ }
+
+ function* testBody()
+ {
+ window.addEventListener("keydown", onKeyDown, false);
+ window.addEventListener("keypress", onKeyPress, false);
+
+ // 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, false);
+ window.removeEventListener("keypress", onKeyPress, false);
+ }
+
+ 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.xul b/widget/tests/test_keycodes.xul
new file mode 100644
index 000000000..3ec460ecb
--- /dev/null
+++ b/widget/tests/test_keycodes.xul
@@ -0,0 +1,4361 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.js" />
+ <script type="application/javascript"
+ 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="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 IS_MAC = navigator.platform.indexOf("Mac") == 0;
+const IS_WIN = navigator.platform.indexOf("Win") == 0;
+const OS_VERSION =
+ IS_WIN ? parseFloat(Components.classes["@mozilla.org/system-info;1"]
+ .getService(Components.interfaces.nsIPropertyBag2)
+ .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 Ci = Components.interfaces;
+
+ var browser = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell).chromeEventHandler;
+ var chromeWin = browser.ownerDocument.defaultView;
+ 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.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.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()
+{
+ const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
+ var eventList, keyDownFlags, keyUpFlags, testingEvent, expectedDOMKeyCode;
+ const kShiftFlag = 0x1;
+ const kCtrlFlag = 0x2;
+ const kAltFlag = 0x4;
+ const kMetaFlag = 0x8;
+ const kNumLockFlag = 0x10;
+ const kCapsLockFlag = 0x20;
+
+ function onKeyEvent(e)
+ {
+ function removeFlag(e, aFlag)
+ {
+ if (e.type == "keydown") {
+ var oldValue = keyDownFlags;
+ keyDownFlags &= ~aFlag;
+ return oldValue != keyDownFlags;
+ } else if (e.type == "keyup") {
+ var oldValue = keyUpFlags;
+ keyUpFlags &= ~aFlag;
+ return oldValue != keyUpFlags;
+ }
+ return false;
+ }
+
+ function isStateChangingModifierKeyEvent(e)
+ {
+ var flags = 0;
+ if (e.type == "keydown") {
+ flags = keyDownFlags ^ keyUpFlags;
+ } else if (e.type == "keyup") {
+ flags = keyUpFlags;
+ }
+ switch (e.keyCode) {
+ case e.DOM_VK_SHIFT:
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of Shift " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of Shift " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of Shift " + e.type + " event mismatch");
+ is(e.shiftKey, e.type == "keydown", name + ", Shift of Shift " + e.type + " event mismatch");
+ return (testingEvent.modifiers.shiftKey || testingEvent.modifiers.shiftRightKey) &&
+ removeFlag(e, kShiftFlag) && expectedDOMKeyCode != e.keyCode;
+ case e.DOM_VK_CONTROL:
+ is(e.ctrlKey, e.type == "keydown", name + ", Ctrl of Ctrl " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of Ctrl " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of Ctrl " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift 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 e.DOM_VK_ALT:
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of Alt " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of Alt " + e.type + " event mismatch");
+ is(e.altKey, e.type == "keydown", name + ", Alt of Alt " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift 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 e.DOM_VK_META:
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of Command " + e.type + " evnet mismatch");
+ is(e.metaKey, e.type == "keydown", name + ", Command of Command " + e.type + " evnet mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of Command " + e.type + " evnet mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift of Command " + e.type + " evnet mismatch");
+ return (testingEvent.modifiers.metaKey || testingEvent.modifiers.metaRightKey) &&
+ removeFlag(e, kMetaFlag) && expectedDOMKeyCode != e.keyCode;
+ case e.DOM_VK_NUM_LOCK:
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of NumLock " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of NumLock " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of NumLock " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift of NumLock " + e.type + " event mismatch");
+ return (testingEvent.modifiers.numLockKey || testingEvent.modifiers.numericKeyPadKey) &&
+ removeFlag(e, kNumLockFlag) && expectedDOMKeyCode != e.keyCode;
+ case e.DOM_VK_CAPS_LOCK:
+ is(e.ctrlKey, (flags & kCtrlFlag) != 0, name + ", Ctrl of CapsLock " + e.type + " event mismatch");
+ is(e.metaKey, (flags & kMetaFlag) != 0, name + ", Command of CapsLock " + e.type + " event mismatch");
+ is(e.altKey, (flags & kAltFlag) != 0, name + ", Alt of CapsLock " + e.type + " event mismatch");
+ is(e.shiftKey, (flags & kShiftFlag) != 0, name + ", Shift 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_NONE = 0x0;
+ 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 ||
+ (IS_WIN && aEvent.modifiers.altGrKey)) {
+ keyDownFlags |= kAltFlag;
+ }
+ 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;
+
+ var name = eventToString(aEvent);
+ ok(true, "Starting: " + name);
+
+ return synthesizeKey(aEvent, "button", function() {
+
+ var expectEventTypeList = [];
+ if (aShouldDelivedEvent & SHOULD_DELIVER_KEYDOWN)
+ expectEventTypeList.push("keydown");
+ if (aShouldDelivedEvent & SHOULD_DELIVER_KEYPRESS) {
+ expectEventTypeList.push("keypress");
+ for (var i = 1; i < aExpectGeckoChar.length; i++) {
+ expectEventTypeList.push("keypress");
+ }
+ }
+ if (aShouldDelivedEvent & SHOULD_DELIVER_KEYUP)
+ expectEventTypeList.push("keyup");
+ is(eventList.length, expectEventTypeList.length, name + ", wrong number of key events");
+
+ var longerLength = Math.max(eventList.length, expectEventTypeList.length);
+ var keypressCount = 0;
+ for (var 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, name + ", " + expectEventType + " should be fired");
+ else
+ is(firedEventType, expectEventType, name + ", a needed event is not fired");
+
+ if (firedEventType != "") {
+ var e = eventList[i];
+ if (e.type == "keypress") {
+ var isCtrlExpected =
+ !!(aEvent.modifiers.ctrlKey || aEvent.modifiers.ctrlRightKey) && !aEvent.isInputtingCharacters;
+ var isAltExpected =
+ !!(aEvent.modifiers.altKey || aEvent.modifiers.altRightKey) && !aEvent.isInputtingCharacters;
+ if (IS_WIN && (aEvent.modifiers.altGrKey || isCtrlExpected && isAltExpected)) {
+ isCtrlExpected = isAltExpected = (aEvent.chars == "");
+ }
+ is(e.ctrlKey, isCtrlExpected, name + ", Ctrl mismatch");
+ is(e.metaKey, !!(aEvent.modifiers.metaKey || aEvent.modifiers.metaRightKey), name + ", Command mismatch");
+ is(e.altKey, isAltExpected, name + ", Alt mismatch");
+ is(e.shiftKey, !!(aEvent.modifiers.shiftKey || aEvent.modifiers.shiftRightKey), name + ", Shift mismatch");
+ }
+
+ var expectedKeyValue =
+ typeof aExpectedKeyValues === "string" ? aExpectedKeyValues :
+ i < aExpectedKeyValues.length ? aExpectedKeyValues[i] :
+ undefined;
+ is(e.key, expectedKeyValue, name + ", wrong key value");
+ is(e.code, aExpectedCodeValue, name + ", wrong code value");
+
+ if (aExpectGeckoChar.length > 0 && e.type == "keypress") {
+ is(e.charCode, aExpectGeckoChar.charCodeAt(keypressCount++), name + ", charcode");
+ if (aExpectedGeckoKeyCode >= 0) {
+ if (aExpectGeckoChar) {
+ is(e.keyCode, 0, name + ", wrong keycode");
+ } else {
+ is(e.keyCode, aExpectedGeckoKeyCode, name + ", wrong keycode");
+ }
+ }
+ } else {
+ is(e.charCode, 0, name + ", no charcode");
+ if (aExpectedGeckoKeyCode >= 0) {
+ is(e.keyCode, aExpectedGeckoKeyCode, name + ", wrong keycode");
+ }
+ }
+ is(e.location, aExpectLocation, name + ", wrong location");
+ }
+ }
+
+ continueTest();
+ });
+ }
+
+ // These tests have to be per-plaform.
+ document.addEventListener("keydown", onKeyEvent, false);
+ document.addEventListener("keypress", onKeyEvent, false);
+ document.addEventListener("keyup", onKeyEvent, false);
+ // Prevent almost all shortcut key handlers.
+ SpecialPowers.addSystemEventListener(document, "keypress", consumer, true);
+
+ function cleanup()
+ {
+ document.removeEventListener("keydown", onKeyEvent, false);
+ document.removeEventListener("keypress", onKeyEvent, false);
+ document.removeEventListener("keyup", onKeyEvent, false);
+ 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.
+
+ // Ctrl keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{ctrlKey:1}, chars:"\u0001", unmodifiedChars:"a"},
+ "a", "KeyA", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Alt keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{altKey:1}, chars:"\u00e5", unmodifiedChars:"a"},
+ "\u00e5", "KeyA", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_A, "\u00c5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // Command keys
+ yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_A,
+ modifiers:{metaKey:1}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", 0, "\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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"a", unmodifiedChars:"a"},
+ "a", "KeyA", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"b", unmodifiedChars:"b"},
+ "b", "KeyB", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"c", unmodifiedChars:"c"},
+ "c", "KeyC", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"d", unmodifiedChars:"d"},
+ "d", "KeyD", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"e", unmodifiedChars:"e"},
+ "e", "KeyE", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_F, "\u0192", 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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"g", unmodifiedChars:"g"},
+ "g", "KeyG", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"h", unmodifiedChars:"h"},
+ "h", "KeyH", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_I, "\u02C6", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // dead key
+ // 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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"j", unmodifiedChars:"j"},
+ "j", "KeyJ", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"k", unmodifiedChars:"k"},
+ "k", "KeyK", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"l", unmodifiedChars:"l"},
+ "l", "KeyL", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"m", unmodifiedChars:"m"},
+ "m", "KeyM", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"n", unmodifiedChars:"n"},
+ "n", "KeyN", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"o", unmodifiedChars:"o"},
+ "o", "KeyO", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_P, "\u03C0", 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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"q", unmodifiedChars:"q"},
+ "q", "KeyQ", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_R, "\u00AE", 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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"s", unmodifiedChars:"s"},
+ "s", "KeyS", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"t", unmodifiedChars:"t"},
+ "t", "KeyT", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"u", unmodifiedChars:"u"},
+ "u", "KeyU", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"v", unmodifiedChars:"v"},
+ "v", "KeyV", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"w", unmodifiedChars:"w"},
+ "w", "KeyW", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"x", unmodifiedChars:"x"},
+ "x", "KeyX", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"y", unmodifiedChars:"y"},
+ "y", "KeyY", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"z", unmodifiedChars:"z"},
+ "z", "KeyZ", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"1", unmodifiedChars:"1"},
+ "1", "Digit1", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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}, chars:"\u00A1", unmodifiedChars:"2"},
+ "\u00A1", "Digit2", nsIDOMKeyEvent.DOM_VK_2, "\u00A1", 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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"3", unmodifiedChars:"3"},
+ "3", "Digit3", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"4", unmodifiedChars:"4"},
+ "4", "Digit4", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"5", unmodifiedChars:"5"},
+ "5", "Digit5", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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}, chars:"\u00A7", unmodifiedChars:"6"},
+ "\u00A7", "Digit6", nsIDOMKeyEvent.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:{metaKey:1}, chars:"6", unmodifiedChars:"6"},
+ "6", "Digit6", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"7", unmodifiedChars:"7"},
+ "7", "Digit7", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"8", unmodifiedChars:"8"},
+ "8", "Digit8", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"9", unmodifiedChars:"9"},
+ "9", "Digit9", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"0", unmodifiedChars:"0"},
+ "0", "Digit0", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"`", unmodifiedChars:"`"},
+ "`", "Backquote", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // TODO:
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Minus,
+ // modifiers:{ctrlKey:1}, chars:"\u001F", unmodifiedChars:"-"},
+ // "-", "Minus", nsIDOMKeyEvent.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:{altKey:1}, chars:"\u2013", unmodifiedChars:"-"},
+ "\u2013", "Minus", nsIDOMKeyEvent.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:{metaKey:1}, chars:"-", unmodifiedChars:"-"},
+ "-", "Minus", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"=", unmodifiedChars:"="},
+ "=", "Equal", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // TODO:
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_LeftBracket,
+ // modifiers:{ctrlKey:1}, chars:"\u001B", unmodifiedChars:"["},
+ // "[", "LeftBracket", nsIDOMKeyEvent.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:{altKey:1}, chars:"\u201C", unmodifiedChars:"["},
+ "\u201C", "BracketLeft", nsIDOMKeyEvent.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:{metaKey:1}, chars:"[", unmodifiedChars:"["},
+ "[", "BracketLeft", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // TODO:
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_RightBracket,
+ // modifiers:{ctrlKey:1}, chars:"\u001D", unmodifiedChars:"]"},
+ // "]", "BracketRight", nsIDOMKeyEvent.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:{altKey:1}, chars:"\u2018", unmodifiedChars:"]"},
+ "\u2018", "BracketRight", nsIDOMKeyEvent.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:{metaKey:1}, chars:"]", unmodifiedChars:"]"},
+ "]", "BracketRight", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // TODO:
+ // yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:MAC_VK_ANSI_Backslash,
+ // modifiers:{ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\\"},
+ // "\\", "Backslash", nsIDOMKeyEvent.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:{altKey:1}, chars:"\u00AB", unmodifiedChars:"\\"},
+ "\u00AB", "Backslash", nsIDOMKeyEvent.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:{metaKey:1}, chars:"\\", unmodifiedChars:"\\"},
+ "\\", "Backslash", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:";", unmodifiedChars:";"},
+ ";", "Semicolon", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"'", unmodifiedChars:"'"},
+ "'", "Quote", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:",", unmodifiedChars:","},
+ ",", "Comma", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:".", unmodifiedChars:"."},
+ ".", "Period", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:{metaKey:1}, chars:"/", unmodifiedChars:"/"},
+ "/", "Slash", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:"&"},
+ "1", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_1,
+ modifiers:{metaKey:1}, chars:"&", unmodifiedChars:"&"},
+ "&", "Digit1", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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"},
+ "2", "Digit2", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:"\""},
+ "3", "Digit3", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:"'"},
+ "4", "Digit4", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:"("},
+ "5", "Digit5", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // TODO:
+ // yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ // modifiers:{ctrlKey:1}, chars:"\u001D", unmodifiedChars:"\u00A7"},
+ // "6", "Digit6", nsIDOMKeyEvent.DOM_VK_6, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Ctrl+6 sets strange char
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_6,
+ modifiers:{metaKey:1}, chars:"\u00A7", unmodifiedChars:"\u00A7"},
+ "\u00A7", "Digit6", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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"},
+ "7", "Digit7", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:"!"},
+ "8", "Digit8", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // TODO:
+ // yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ // modifiers:{ctrlKey:1}, chars:"\u001C", unmodifiedChars:"\u00E7"},
+ // "9", "Digit9", nsIDOMKeyEvent.DOM_VK_9, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Ctrl+9 sets strange char
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_9,
+ modifiers:{metaKey:1}, chars:"\u00E7", unmodifiedChars:"\u00E7"},
+ "\u00E7", "Digit9", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // XXX No events fired, not sure the reason.
+ // yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:MAC_VK_ANSI_0,
+ // modifiers:{ctrlKey:1}, chars:"", unmodifiedChars:"\u00E0"},
+ // "0", "Digit0", nsIDOMKeyEvent.DOM_VK_0, "", 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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_PERIOD, "\u0E07", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ // keycode should be zero 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", 0, "\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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", "", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", "Unidentified" /* TODO: Process */], "KeyA", 0 /* TODO: 0xE5 */, "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", "Unidentified" /* TODO: Process */], "KeyA", 0 /* TODO: 0xE5 */, "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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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:""},
+ // "", "", nsIDOMKeyEvent.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:""},
+ // "", "", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_9, "\u00E7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // OEM keys
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_7,
+ modifiers:{}, chars:"\u00B2"},
+ "\u00B2", "Backquote", 0, "\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", 0, "", SHOULD_DELIVER_KEYDOWN_KEYUP, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_OEM_4,
+ modifiers:{}, chars:")"},
+ ")", "Minus", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", 0, "\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", 0, "", 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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", 0, "", 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", 0, "", 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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_0, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
+ modifiers:{altGrKey:1}, chars:""},
+ "&", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+ //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
+ // modifiers:{altGrKey:1}, chars:""},
+ // "Dead", "Digit2", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.DOM_VK_EQUALS, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+
+ // German
+ yield testKey({layout:KEYBOARD_LAYOUT_GERMAN, keyCode:WIN_VK_OEM_2,
+ modifiers:{}, chars:"#"},
+ "#", "Backslash", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", 0, "", 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", 0, "\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", 0, "", 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", nsIDOMKeyEvent.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", 0, "", 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", nsIDOMKeyEvent.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", 0, "", 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", nsIDOMKeyEvent.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", 0, "", 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", 0, "\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", 0, "", 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", nsIDOMKeyEvent.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", 0, "", 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", nsIDOMKeyEvent.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", 0, "", 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", nsIDOMKeyEvent.DOM_VK_Q, "\u00A8Q", 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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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", nsIDOMKeyEvent.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 name = eventToString(aEvent);
+
+ is(activationCount, aShouldActivate ? 1 : 0, name + ", activating '" + aAccessKey + "'");
+
+ continueTest();
+ });
+ }
+
+ button.addEventListener("click", onClick, false);
+
+ // 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, false);
+}
+
+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, false);
+ buttonParent.addEventListener("keypress", onKeyInDefaultEventGroup, false);
+ 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, false);
+ buttonParent.removeEventListener("keypress", onKeyInDefaultEventGroup, false);
+ 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");
+ }
+
+ 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()
+ {
+ is(keyEvents.length, 8, 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");
+
+ 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:""},
+ "");
+ }
+
+ 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, false);
+ contents[i].addEventListener("keypress", onKeyInDefaultEventGroup, false);
+ 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()
+ {
+ 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, false);
+ contents[i].removeEventListener("keypress", onKeyInDefaultEventGroup, false);
+ 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() {
+ 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();
+
+ var name = eventToString(aEvent);
+
+ // Check if the text comes with keypress events rather than composition events.
+ var keypress = 0;
+ function onKeypress(aEvent) {
+ keypress++;
+ if (keypress == 1 && aExpectText == "") {
+ if (!aEvent.ctrlKey && !aEvent.altKey) {
+ is(aEvent.charCode, 0, name + ", the charCode value should be 0 when it shouldn't cause inputting text");
+ }
+ return;
+ }
+ if (keypress > aExpectText.length) {
+ ok(false, name + " causes too many keypress events");
+ return;
+ }
+ is(aEvent.key, aExpectText[keypress - 1],
+ name + ", " + keypress + "th keypress event's key value should be '" + aExpectText[keypress - 1] + "'");
+ is(aEvent.charCode, aExpectText.charCodeAt(keypress - 1),
+ name + ", " + 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", function() {
+ textbox.removeEventListener("keypress", onKeypress, true);
+ if (aExpectText == "") {
+ is(keypress, 1, name + " should cause one keypress event because it doesn't cause inputting text");
+ } else {
+ is(keypress, aExpectText.length, name + " should cause " + aExpectText.length + " keypress events");
+ is(textbox.value, aExpectText, name + " 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:""},
+ "");
+
+ // 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* runAllTests() {
+ yield* runKeyEventTests();
+ yield* runAccessKeyTests();
+ yield* runXULKeyTests();
+ yield* runReservedKeyTests();
+ yield* runTextInputTests();
+}
+
+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().
+ SimpleTest.expectAssertions(2, 2);
+ }
+ SimpleTest.waitForExplicitFinish();
+
+ clearInfobars();
+
+ continueTest();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_mouse_scroll.xul b/widget/tests/test_mouse_scroll.xul
new file mode 100644
index 000000000..570b304ea
--- /dev/null
+++ b/widget/tests/test_mouse_scroll.xul
@@ -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="Testing composition, text and query content events"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ 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.open("window_mouse_scroll_win.html", "_blank",
+ "chrome,width=600,height=600");
+
+]]>
+</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 000000000..dc3872f02
--- /dev/null
+++ b/widget/tests/test_native_key_bindings_mac.html
@@ -0,0 +1,343 @@
+<!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 type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript"
+ 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;version=1.8">
+ 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.call(null, 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.xul b/widget/tests/test_native_menus.xul
new file mode 100644
index 000000000..cf9bcc881
--- /dev/null
+++ b/widget/tests/test_native_menus.xul
@@ -0,0 +1,30 @@
+<?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 type="application/javascript"
+ 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.open("native_menus_window.xul", "NativeMenuWindow",
+ "chrome,width=600,height=600");
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_native_mouse_mac.xul b/widget/tests/test_native_mouse_mac.xul
new file mode 100644
index 000000000..5d86864b2
--- /dev/null
+++ b/widget/tests/test_native_mouse_mac.xul
@@ -0,0 +1,30 @@
+<?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 type="application/javascript"
+ 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.open("native_mouse_mac_window.xul", "NativeMouseWindow",
+ "chrome,width=600,height=600");
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_panel_mouse_coords.xul b/widget/tests/test_panel_mouse_coords.xul
new file mode 100644
index 000000000..41ef49044
--- /dev/null
+++ b/widget/tests/test_panel_mouse_coords.xul
@@ -0,0 +1,83 @@
+<?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 type="application/javascript"
+ 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();
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+let utils = window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils);
+let panel = document.getElementById('thepanel');
+let nativeMouseMove;
+let rect;
+
+function startTest() {
+ let widgetToolkit = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULRuntime).widgetToolkit;
+
+ if (widgetToolkit == "cocoa") {
+ nativeMouseMove = 5; // NSMouseMoved
+ } 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 000000000..08ba1db12
--- /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 type="application/javascript" 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.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.xul b/widget/tests/test_platform_colors.xul
new file mode 100644
index 000000000..b30b9837f
--- /dev/null
+++ b/widget/tests/test_platform_colors.xul
@@ -0,0 +1,107 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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, null).getPropertyValue('background-color');
+ ok(colors[c].indexOf(rgb) != -1 || colors[c].length == 8, 'platform color ' + c + ' is wrong: ' + rgb);
+}
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_plugin_input_event.html b/widget/tests/test_plugin_input_event.html
new file mode 100644
index 000000000..6ed2a14c4
--- /dev/null
+++ b/widget/tests/test_plugin_input_event.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for plugin input event</title>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/NativeKeyCodes.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>
+<script type="application/javascript">
+setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+</script>
+
+<p id="display">
+ <embed id="plugin" type="application/x-test" wmode="opaque">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var gPlugin = document.getElementById("plugin");
+
+var gUtils = window.
+ QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+function* doTest() {
+ gPlugin.focus();
+
+ is(gUtils.IMEStatus, gUtils.IME_STATUS_PLUGIN,
+ "Plugin failed to get focus");
+
+ is(gPlugin.getLastKeyText(), "", "Must be empty before first key test");
+
+ yield synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, WIN_VK_A, {}, "a", "a", continueTest);
+ is(gPlugin.getLastKeyText(), "a", "Invalid character was inputted");
+
+ yield synthesizeNativeKey(KEYBOARD_LAYOUT_GERMAN, WIN_VK_OEM_PLUS, {}, "+", "+", continueTest);
+ is(gPlugin.getLastKeyText(), "+", "Invalid character was inputted");
+
+ yield synthesizeNativeKey(KEYBOARD_LAYOUT_GERMAN, WIN_VK_OEM_PLUS, {altGrKey:1}, "~", "+", continueTest);
+ is(gPlugin.getLastKeyText(), "~", "Invalid character was inputted");
+}
+
+var gTestContinuation = null;
+
+function continueTest() {
+ if (!gTestContinuation) {
+ gTestContinuation = doTest(continueTest);
+ }
+ var ret = gTestContinuation.next();
+ if (ret.done) {
+ SimpleTest.finish();
+ } else {
+ is(ret.value, true, "Key synthesized successfully");
+ }
+}
+
+SimpleTest.waitForFocus(continueTest);
+
+</script>
+</body>
+</html>
diff --git a/widget/tests/test_plugin_scroll_consistency.html b/widget/tests/test_plugin_scroll_consistency.html
new file mode 100644
index 000000000..76d97f4ef
--- /dev/null
+++ b/widget/tests/test_plugin_scroll_consistency.html
@@ -0,0 +1,61 @@
+<html>
+<head>
+ <title>Test for plugin child widgets not being messed up by scrolling</title>
+ <script type="text/javascript"
+ 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.xul b/widget/tests/test_position_on_resize.xul
new file mode 100644
index 000000000..e1adfc2b4
--- /dev/null
+++ b/widget/tests/test_position_on_resize.xul
@@ -0,0 +1,94 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript"
+ 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.openDialog("about:blank",
+ null,
+ "chrome,dialog=no,outerHeight=170,outerWidth=200");
+ waitForSuccess(function() { return win.mozPaintCount },
+ "No paint received", checkInitialSize);
+ }
+
+ function checkInitialSize() {
+ let runtime = Components.classes["@mozilla.org/xre/app-info;1"]
+ .getService(Components.interfaces.nsIXULRuntime);
+ let test = runtime.widgetToolkit == "windows" ? todo_is : is; // bug 602745
+ test(win.outerHeight,170, "initial outerHeight");
+ test(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_secure_input.html b/widget/tests/test_secure_input.html
new file mode 100644
index 000000000..2c3ee38db
--- /dev/null
+++ b/widget/tests/test_secure_input.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for secure input mode</title>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript"
+ 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\"");
+
+ otherWindow =
+ window.open("data:text/html,<input id=\"text\" type\"text\"><input id=\"password\" type\"password\">",
+ "_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.xul b/widget/tests/test_sizemode_events.xul
new file mode 100644
index 000000000..e6ab940b2
--- /dev/null
+++ b/widget/tests/test_sizemode_events.xul
@@ -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 type="application/javascript"
+ 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";
+
+gWindow = null;
+
+gSizeModeDidChange = false;
+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.indexOf("Lin") != -1) {
+ 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 = open('empty_window.xul', '_blank', 'chrome,screenX=50,screenY=50,width=200,height=200,resizable');
+ SimpleTest.waitForFocus(runTest, gWindow);
+}
+
+function runTest() {
+ // Install event handler.
+ gWindow.addEventListener("sizemodechange", sizemodeChanged, false);
+
+ // 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, false);
+ gWindow.close();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(startTest);
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_standalone_native_menu.xul b/widget/tests/test_standalone_native_menu.xul
new file mode 100644
index 000000000..bac7a352e
--- /dev/null
+++ b/widget/tests/test_standalone_native_menu.xul
@@ -0,0 +1,30 @@
+<?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 type="application/javascript"
+ 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.open("standalone_native_menu_window.xul", "StandaloneNativeMenuWindow",
+ "chrome,width=600,height=600");
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/test_system_status_bar.xul b/widget/tests/test_system_status_bar.xul
new file mode 100644
index 000000000..784620fc8
--- /dev/null
+++ b/widget/tests/test_system_status_bar.xul
@@ -0,0 +1,57 @@
+<?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 type="application/javascript"
+ 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 Cc = Components.classes;
+ let Ci = Components.interfaces;
+
+ let systemStatusBar = Cc["@mozilla.org/widget/macsystemstatusbar;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.xul b/widget/tests/test_taskbar_progress.xul
new file mode 100644
index 000000000..e4ff533a0
--- /dev/null
+++ b/widget/tests/test_taskbar_progress.xul
@@ -0,0 +1,126 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script class="testbody" type="application/javascript">
+ <![CDATA[
+ let Cc = Components.classes;
+ let Ci = Components.interfaces;
+ let Cu = Components.utils;
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ let TP = Ci.nsITaskbarProgress;
+
+ function IsWin7OrHigher() {
+ try {
+ var sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ var ver = parseFloat(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:
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+ let win = wm.getMostRecentWindow("navigator:browser");
+ let docShell = win.document.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_wheeltransaction.xul b/widget/tests/test_wheeltransaction.xul
new file mode 100644
index 000000000..dadd46629
--- /dev/null
+++ b/widget/tests/test_wheeltransaction.xul
@@ -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="Wheel scroll transaction tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ 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.open("window_wheeltransaction.xul", "_blank",
+ "chrome,width=600,height=600");
+
+]]>
+</script>
+</window>
diff --git a/widget/tests/unit/test_macwebapputils.js b/widget/tests/unit/test_macwebapputils.js
new file mode 100644
index 000000000..0701bedf0
--- /dev/null
+++ b/widget/tests/unit/test_macwebapputils.js
@@ -0,0 +1,44 @@
+/* -*- 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
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+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);
+ do_print("TextEdit path: " + path + "\n");
+ do_check_neq(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);
+ do_check_eq(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 000000000..d145683eb
--- /dev/null
+++ b/widget/tests/unit/test_taskbar_jumplistitems.js
@@ -0,0 +1,261 @@
+/* -*- 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.
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+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);
+
+ do_check_false(item.equals(sep));
+ do_check_false(item.equals(shortcut));
+ do_check_false(item.equals(link));
+
+ do_check_false(sep.equals(item));
+ do_check_false(sep.equals(shortcut));
+ do_check_false(sep.equals(link));
+
+ do_check_false(shortcut.equals(item));
+ do_check_false(shortcut.equals(sep));
+ do_check_false(shortcut.equals(link));
+
+ do_check_false(link.equals(item));
+ do_check_false(link.equals(sep));
+ do_check_false(link.equals(shortcut));
+
+ do_check_true(item.equals(item));
+ do_check_true(sep.equals(sep));
+ do_check_true(link.equals(link));
+ do_check_true(shortcut.equals(shortcut));
+}
+
+function test_separator()
+{
+ // separators:
+
+ var item = Cc["@mozilla.org/windows-jumplistseparator;1"].
+ createInstance(Ci.nsIJumpListSeparator);
+
+ do_check_true(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;1"]
+ .createInstance(Ci.nsIURI);
+ var uri2 = Cc["@mozilla.org/network/simple-uri;1"]
+ .createInstance(Ci.nsIURI);
+
+ uri1.spec = "http://www.123.com/";
+ uri2.spec = "http://www.123.com/";
+
+ link.uri = uri1;
+
+ do_check_true(link.compareHash(uri2))
+ uri2.spec = "http://www.456.com/";
+ do_check_false(link.compareHash(uri2))
+ uri2.spec = "http://www.123.com/";
+ do_check_true(link.compareHash(uri2))
+ uri2.spec = "https://www.123.com/";
+ do_check_false(link.compareHash(uri2))
+ uri2.spec = "http://www.123.com/test/";
+ do_check_false(link.compareHash(uri2))
+ uri1.spec = "http://www.123.com/test/";
+ uri2.spec = "http://www.123.com/test/";
+ do_check_true(link.compareHash(uri2))
+ uri1.spec = "https://www.123.com/test/";
+ uri2.spec = "https://www.123.com/test/";
+ do_check_true(link.compareHash(uri2))
+ uri2.spec = "ftp://www.123.com/test/";
+ do_check_false(link.compareHash(uri2))
+ uri2.spec = "http://123.com/test/";
+ do_check_false(link.compareHash(uri2))
+ uri1.spec = "https://www.123.com/test/";
+ uri2.spec = "https://www.123.com/Test/";
+ do_check_false(link.compareHash(uri2))
+
+ uri1.spec = "http://www.123.com/";
+ do_check_eq(link.uriHash, "QGLmWuwuTozr3tOfXSf5mg==");
+ uri1.spec = "http://www.123.com/test/";
+ do_check_eq(link.uriHash, "AG87Ls+GmaUYSUJFETRr3Q==");
+ uri1.spec = "https://www.123.com/";
+ do_check_eq(link.uriHash, "iSx6UH1a9enVPzUA9JZ42g==");
+
+ var uri3 = Cc["@mozilla.org/network/simple-uri;1"]
+ .createInstance(Ci.nsIURI);
+ link.uri = uri3;
+ do_check_eq(link.uriHash, "hTrpDwNRMkvXPqYV5kh1Fw==");
+}
+
+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;1"]
+ .createInstance(Ci.nsIURI);
+ var uri2 = Cc["@mozilla.org/network/simple-uri;1"]
+ .createInstance(Ci.nsIURI);
+
+ uri1.spec = "http://www.test.com/";
+ uri2.spec = "http://www.test.com/";
+
+ link1.uri = uri1;
+ link1.uriTitle = "Test";
+ link2.uri = uri2;
+ link2.uriTitle = "Test";
+
+ do_check_true(link1.equals(link2));
+
+ link2.uriTitle = "Testing";
+
+ do_check_false(link1.equals(link2));
+
+ link2.uriTitle = "Test";
+ uri2.spec = "http://www.testing.com/";
+
+ do_check_false(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;
+ do_check_eq(sc.iconIndex, 1);
+
+ var faviconPageUri = Cc["@mozilla.org/network/simple-uri;1"]
+ .createInstance(Ci.nsIURI);
+ faviconPageUri.spec = "http://www.123.com/";
+ sc.faviconPageUri = faviconPageUri;
+ do_check_eq(sc.faviconPageUri, faviconPageUri);
+
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ QueryInterface(Ci.nsIDirectoryService);
+ var notepad = dirSvc.get("WinD", Ci.nsIFile);
+ notepad.append("notepad.exe");
+ if (notepad.exists()) {
+ handlerApp.executable = notepad;
+ sc.app = handlerApp;
+ do_check_eq(sc.app.detailedDescription, "TestApp detailed description.");
+ do_check_eq(sc.app.name, "TestApp");
+ do_check_true(sc.app.parameterExists("-test"));
+ do_check_false(sc.app.parameterExists("-notset"));
+ }
+}
+
+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();
+
+ do_check_neq(builder, null);
+
+ // Win7 and up only
+ try {
+ var sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ var ver = parseFloat(sysInfo.getProperty("version"));
+ if (ver < 6.1) {
+ do_check_false(builder.available, false);
+ return;
+ }
+ } catch (ex) { }
+
+ do_check_true(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 dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ QueryInterface(Ci.nsIDirectoryService);
+ var notepad = dirSvc.get("WinD", Ci.nsIFile);
+ notepad.append("notepad.exe");
+ if (notepad.exists()) {
+ handlerApp.executable = notepad;
+ sc.app = handlerApp;
+ items.appendElement(sc, false);
+
+ var removed = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ do_check_true(builder.initListBuild(removed));
+ do_check_true(builder.addListToBuild(builder.JUMPLIST_CATEGORY_TASKS, items));
+ do_check_true(builder.addListToBuild(builder.JUMPLIST_CATEGORY_RECENT));
+ do_check_true(builder.addListToBuild(builder.JUMPLIST_CATEGORY_FREQUENT));
+ do_check_true(builder.commitListBuild());
+
+ builder.deleteActiveList();
+
+ do_check_true(builder.initListBuild(removed));
+ do_check_true(builder.addListToBuild(builder.JUMPLIST_CATEGORY_CUSTOM, items, "Custom List"));
+ do_check_true(builder.commitListBuild());
+
+ builder.deleteActiveList();
+ }
+}
+
+function run_test()
+{
+ if (mozinfo.os != "win") {
+ return;
+ }
+ test_basics();
+ test_separator();
+ test_hashes();
+ test_links();
+ test_shortcuts();
+ test_jumplist();
+}
diff --git a/widget/tests/unit/xpcshell.ini b/widget/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..d0e8f8701
--- /dev/null
+++ b/widget/tests/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+head =
+tail =
+
+[test_taskbar_jumplistitems.js]
+[test_macwebapputils.js]
+skip-if = os != "mac"
diff --git a/widget/tests/utils.js b/widget/tests/utils.js
new file mode 100644
index 000000000..3796c7d2b
--- /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.xul b/widget/tests/window_bug429954.xul
new file mode 100644
index 000000000..6604e91b4
--- /dev/null
+++ b/widget/tests/window_bug429954.xul
@@ -0,0 +1,45 @@
+<?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 type="application/javascript"
+ 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.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.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.opener.wrappedJSObject.SimpleTest.finish();
+ window.close();
+}
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug478536.xul b/widget/tests/window_bug478536.xul
new file mode 100644
index 000000000..0a07777b4
--- /dev/null
+++ b/widget/tests/window_bug478536.xul
@@ -0,0 +1,215 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+ <script type="application/javascript"
+ 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.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.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 = Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+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, false);
+gBody.addEventListener("MozMouseScrollTransactionTimeout",
+ onTransactionTimeout, false);
+
+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.opener.wrappedJSObject.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.xul b/widget/tests/window_bug522217.xul
new file mode 100644
index 000000000..8fbb21037
--- /dev/null
+++ b/widget/tests/window_bug522217.xul
@@ -0,0 +1,72 @@
+<?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 type="application/javascript"
+ 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.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+function executeSoon(aFct)
+{
+ window.opener.wrappedJSObject.SimpleTest.executeSoon(aFct);
+}
+
+function start() {
+ window.onfocus = function () {
+ window.onfocus = null;
+ var oldOuterWidth = window.outerWidth, oldOuterHeight = window.outerHeight;
+ var oldInnerWidth = window.innerWidth, oldInnerHeight = window.innerHeight;
+ document.documentElement.setAttribute("drawintitlebar", "true");
+
+ executeSoon(function() {
+ 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");
+ window.fullScreen = true;
+ window.fullScreen = false;
+ 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");
+
+ executeSoon(function() {
+ 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.opener.wrappedJSObject.SimpleTest.finish();
+ window.close();
+ });
+ });
+ }
+}
+
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_bug538242.xul b/widget/tests/window_bug538242.xul
new file mode 100644
index 000000000..fb878b138
--- /dev/null
+++ b/widget/tests/window_bug538242.xul
@@ -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.xul b/widget/tests/window_bug593307_centerscreen.xul
new file mode 100644
index 000000000..24d708760
--- /dev/null
+++ b/widget/tests/window_bug593307_centerscreen.xul
@@ -0,0 +1,27 @@
+<?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 type="application/javascript"
+ 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.xul b/widget/tests/window_bug593307_offscreen.xul
new file mode 100644
index 000000000..0857c73a6
--- /dev/null
+++ b/widget/tests/window_bug593307_offscreen.xul
@@ -0,0 +1,34 @@
+<?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 type="application/javascript"
+ 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.xul','', 'chrome,centerscreen,dependent,dialog=no');
+}
+
+function finished() {
+ centerscreen.close();
+ finish();
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_composition_text_querycontent.xul b/widget/tests/window_composition_text_querycontent.xul
new file mode 100644
index 000000000..a5b9e2655
--- /dev/null
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -0,0 +1,6992 @@
+<?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"
+ onunload="onunload();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+ <panel id="panel" hidden="true"
+ orient="vertical"
+ onpopupshown="onPanelShown(event);"
+ onpopuphidden="onPanelHidden(event);">
+ <vbox id="vbox">
+ <textbox id="textbox" onfocus="onFocusPanelTextbox(event);"
+ multiline="true" 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;" 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/>
+<input id="input" type="text"/><br/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTest, window);
+
+function ok(aCondition, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.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);
+}
+
+function finish()
+{
+ window.close();
+}
+
+function onunload()
+{
+ window.opener.wrappedJSObject.SimpleTest.finish();
+}
+
+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 input = document.getElementById("input");
+var textareaInFrame;
+
+const nsITextInputProcessorCallback = Components.interfaces.nsITextInputProcessorCallback;
+const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
+const nsIEditorIMESupport = Components.interfaces.nsIEditorIMESupport;
+const nsIInterfaceRequestor = Components.interfaces.nsIInterfaceRequestor;
+const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
+const nsIDocShell = Components.interfaces.nsIDocShell;
+
+function hitEventLoop(aFunc, aTimes)
+{
+ if (--aTimes) {
+ setTimeout(hitEventLoop, 0, aFunc, aTimes);
+ } else {
+ setTimeout(aFunc, 20);
+ }
+}
+
+function getEditorIMESupport(aNode)
+{
+ return aNode.QueryInterface(nsIDOMNSEditableElement).
+ editor.
+ QueryInterface(nsIEditorIMESupport);
+}
+
+function getHTMLEditorIMESupport(aWindow)
+{
+ return aWindow.QueryInterface(nsIInterfaceRequestor).
+ getInterface(nsIWebNavigation).
+ QueryInterface(nsIDocShell).
+ 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 = "";
+ }
+ var 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 + "): "
+ var 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 = "";
+ }
+ var 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 + ")";
+ var 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");
+ var 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 checkRectArray(aQueryTextRectArrayResult, aExpectedTextRectArray, aMessage)
+{
+ for (var i = 1; i < aExpectedTextRectArray.length; ++i) {
+ var 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)
+{
+ var container = { left: Math.ceil(aContainer.left),
+ top: Math.ceil(aContainer.top),
+ width: Math.floor(aContainer.width),
+ height: Math.floor(aContainer.height) };
+
+ var 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;
+}
+
+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 }
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u306D\u3053",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u732B",
+ "clauses":
+ [
+ { "length": 1,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u307E",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ // cancel the composition
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3080",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3080\u3059",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3080\u3059\u3081",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u5A18",
+ "clauses":
+ [
+ { "length": 1,
+ "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ synthesizeKey(" ", {});
+ synthesizeKey("m", {});
+ synthesizeKey("e", {});
+ synthesizeKey("a", {});
+ synthesizeKey("n", {});
+ synthesizeKey("t", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("s", {});
+ synthesizeKey(" ", {});
+ synthesizeKey("\"", {});
+ synthesizeKey("c", {});
+ synthesizeKey("a", {});
+ synthesizeKey("t", {});
+ synthesizeKey("-", {});
+ synthesizeKey("g", {});
+ synthesizeKey("i", {});
+ synthesizeKey("r", {});
+ synthesizeKey("l", {});
+ synthesizeKey("\"", {});
+ synthesizeKey(".", {});
+ synthesizeKey(" ", {});
+ synthesizeKey("VK_SHIFT", { type: "keydown" });
+ synthesizeKey("S", { shiftKey: true });
+ synthesizeKey("VK_SHIFT", { type: "keyup" });
+ synthesizeKey("h", {});
+ synthesizeKey("e", {});
+ synthesizeKey(" ", {});
+ synthesizeKey("i", {});
+ synthesizeKey("s", {});
+ synthesizeKey(" ", {});
+ synthesizeKey("a", {});
+ synthesizeKey(" ", {});
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088\u3046",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088\u3046\u304b",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3088\u3046\u304b\u3044",
+ "clauses":
+ [
+ { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 4, "length": 0 }
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u5996\u602a",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+ synthesizeKey("VK_BACK_SPACE", {});
+
+ var 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)) {
+ return;
+ }
+}
+
+function runCompositionCommitAsIsTest()
+{
+ textarea.focus();
+
+ var result = {};
+ function clearResult()
+ {
+ result = { compositionupdate: false, compositionend: false, text: false, input: false }
+ }
+
+ function handler(aEvent)
+ {
+ result[aEvent.type] = true;
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", 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 }
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #1");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ is(result.compositionupdate, false, "runCompositionCommitAsIsTest: compositionupdate shouldn't be fired after dispatching compositioncommitasis #1");
+ is(result.compositionend, true, "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #1");
+ is(result.text, true, "runCompositionCommitAsIsTest: text should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
+ is(result.input, true, "runCompositionCommitAsIsTest: input should be fired 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 }
+ });
+ 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 }
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ is(result.compositionupdate, false, "runCompositionCommitAsIsTest: compositionupdate shouldn't be fired after dispatching compositioncommitasis #2");
+ is(result.compositionend, true, "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #2");
+ is(result.text, false, "runCompositionCommitAsIsTest: text shouldn't be fired after dispatching compositioncommitasis because it's dispatched when there is already committed string #2");
+ is(result.input, true, "runCompositionCommitAsIsTest: input should be fired 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 }
+ });
+ 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 }
+ });
+ is(textarea.value, "", "runCompositionCommitAsIsTest: textarea has non-empty composition string #3");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ is(result.compositionupdate, false, "runCompositionCommitAsIsTest: compositionupdate shouldn't be fired after dispatching compositioncommitasis #3");
+ is(result.compositionend, true, "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #3");
+ is(result.text, false, "runCompositionCommitAsIsTest: text shouldn't be fired after dispatching compositioncommitasis because it's dispatched when there is empty composition string #3");
+ is(result.input, true, "runCompositionCommitAsIsTest: input should be fired 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("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runCompositionCommitTest()
+{
+ textarea.focus();
+
+ var result = {};
+ function clearResult()
+ {
+ result = { compositionupdate: false, compositionend: false, text: false, input: false }
+ }
+
+ function handler(aEvent)
+ {
+ result[aEvent.type] = true;
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", 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 }
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #1");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3043" });
+
+ is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #1");
+ is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #1");
+ is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit because it's dispatched when there is compoing string #1");
+ is(result.input, true, "runCompositionCommitTest: input should be fired 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 }
+ });
+ 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 }
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have committed string #2");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3043" });
+
+ is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #2");
+ is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #2");
+ is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit #2");
+ is(result.input, true, "runCompositionCommitTest: input should be fired 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 }
+ });
+ 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 }
+ });
+ is(textarea.value, "", "runCompositionCommitTest: textarea has non-empty composition string #3");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3043" });
+
+ is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #3");
+ is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #3");
+ is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit #3");
+ is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
+ is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");
+
+ // 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 }
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #4");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #4");
+ is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #4");
+ is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit #4");
+ is(result.input, true, "runCompositionCommitTest: input should be fired 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" });
+
+ is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #5");
+ is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
+ is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit #5");
+ is(result.input, true, "runCompositionCommitTest: input should be fired 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 }
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #5");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3042" });
+
+ is(result.compositionupdate, false, "runCompositionCommitTest: compositionupdate shouldn't be fired after dispatching compositioncommit #5");
+ is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
+ is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit because there was composition string #5");
+ is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #5");
+
+ // 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 }
+ });
+ 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 }
+ });
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
+
+ clearResult();
+ synthesizeComposition({ type: "compositioncommit", data: "\u3042" });
+
+ is(result.compositionupdate, false, "runCompositionCommitTest: compositionupdate shouldn't be fired after dispatching compositioncommit #6");
+ is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #6");
+ is(result.text, false, "runCompositionCommitTest: text shouldn't be fired after dispatching compositioncommit because there was already committed string #6");
+ is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
+ is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runCompositionTest()
+{
+ textarea.value = "";
+ textarea.focus();
+ var caretRects = [];
+
+ var caretRect = synthesizeQueryCaretRect(0);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #0")) {
+ return false;
+ }
+ caretRects[0] = caretRect;
+
+ // input first character
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ if (!checkContent("\u3089", "runCompositionTest", "#1-1") ||
+ !checkSelection(1, "", "runCompositionTest", "#1-1")) {
+ return;
+ }
+
+ caretRect = synthesizeQueryCaretRect(1);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-1")) {
+ return false;
+ }
+ caretRects[1] = caretRect;
+
+ // 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", "runCompositionTest", "#1-2") ||
+ !checkSelection(2, "", "runCompositionTest", "#1-2")) {
+ return;
+ }
+
+ caretRect = synthesizeQueryCaretRect(2);
+ if (!checkQueryContentResult(caretRect,
+ "runCompositionTest: synthesizeQueryCaretRect #1-2")) {
+ return false;
+ }
+ 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 }
+ });
+
+ 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 false;
+ }
+ 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 }
+ });
+
+ 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 false;
+ }
+
+ 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 + 1,
+ "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 }
+ });
+
+ 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 false;
+ }
+
+ 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 + 1,
+ "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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
+ "runCompositionTest", "#1-12") ||
+ !checkSelection(5, "", "runCompositionTest", "#1-12")) {
+ return;
+ }
+
+
+ var textRect1 = synthesizeQueryTextRect(0, 1);
+ var textRect2 = synthesizeQueryTextRect(1, 1);
+ if (!checkQueryContentResult(textRect1,
+ "runCompositionTest: synthesizeQueryTextRect #1-12-1") ||
+ !checkQueryContentResult(textRect2,
+ "runCompositionTest: synthesizeQueryTextRect #1-12-2")) {
+ return false;
+ }
+
+ // 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;
+ }
+
+ var textRect3 = synthesizeQueryTextRect(0, 1);
+ var textRect4 = synthesizeQueryTextRect(1, 1);
+
+ if (!checkQueryContentResult(textRect3,
+ "runCompositionTest: synthesizeQueryTextRect #1-13-1") ||
+ !checkQueryContentResult(textRect4,
+ "runCompositionTest: synthesizeQueryTextRect #1-13-2")) {
+ return false;
+ }
+
+ 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 }
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3057",
+ "runCompositionTest", "#2-1") ||
+ !checkSelection(8 + 1, "", "runCompositionTest", "#2-1")) {
+ return;
+ }
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3058",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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" });
+
+ 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
+ var 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3046",
+ "runCompositionTest", "#3-4") ||
+ !checkSelection(4 + 1, "", "runCompositionTest", "#3-4")) {
+ return;
+ }
+
+ // cancel the composition
+ synthesizeComposition({ type: "compositioncommit", data: "" });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-2") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-2")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommit", data: "\u6700" });
+ 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 }
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
+ "runCompositionTest", "#4-5") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-5")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ 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("VK_LEFT", { shiftKey: true });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "",
+ "clauses":
+ [
+ { "length": 0, "attr": 0 }
+ ]
+ },
+ "caret": { "start": 0, "length": 0 }
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#4-8") ||
+ !checkSelection(4, "", "runCompositionTest", "#4-8")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommit", data: "\u9AD8" });
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
+ "runCompositionTest", "#4-9") ||
+ !checkSelection(5, "", "runCompositionTest", "#4-9")) {
+ return;
+ }
+
+ synthesizeKey("VK_BACK_SPACE", {});
+ 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 }
+ });
+
+ 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 }
+ });
+
+ if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
+ "runCompositionTest", "#5-2") ||
+ !checkSelection(4, "", "runCompositionTest", "#5-2")) {
+ return;
+ }
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+ 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")) {
+ return;
+ }
+}
+
+function runCompositionEventTest()
+{
+ const kDescription = "runCompositionEventTest: ";
+ const kEvents = ["compositionstart", "compositionupdate", "compositionend",
+ "input"];
+
+ input.value = "";
+ input.focus();
+
+ var windowEventCounts = [], windowEventData = [], windowEventLocale = [];
+ var inputEventCounts = [], inputEventData = [], inputEventLocale = [];
+ var preventDefault = false;
+ var stopPropagation = false;
+
+ function initResults()
+ {
+ for (var 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 }
+ });
+
+ 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 }
+ });
+
+ 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" });
+
+ 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("VK_LEFT", { shiftKey: true });
+
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "\u3089",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ 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 }
+ });
+
+ 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");
+
+ prevnetDefault = 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 }
+ });
+
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ 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
+
+ var 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");
+
+ var 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");
+
+ var 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");
+
+ var 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);
+}
+
+function runQueryTextRectInContentEditableTest()
+{
+ contenteditable.focus();
+
+ contenteditable.innerHTML = "<p>abc</p><p>def</p>";
+ // \n 0 123 4 567
+ // \r\n 01 234 56 789
+
+ var description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
+
+ // "a"
+ var a = synthesizeQueryTextRect(kLFLen, 1);
+ if (!checkQueryContentResult(a, description + "rect for 'a'")) {
+ return;
+ }
+
+ // "b"
+ var 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"
+ var 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
+ var 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')
+ var 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
+ var 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
+ var 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
+ var 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"
+ var 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"
+ var 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"
+ var 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
+ var 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')
+ var 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
+ var 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
+ var 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
+ var 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')
+ var 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
+ var 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
+ var 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
+ var 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>
+ var 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
+ var 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
+ var 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
+ var 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>
+ var 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
+ var 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
+ 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
+ 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>
+ var 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
+ var 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
+ var 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
+ var 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')
+ var 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
+ var 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
+ var 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
+ var 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>
+ var 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
+ var 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
+ 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
+ var 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>
+ var 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
+ var 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
+ var 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
+ var br2_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
+ if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <br>") ||
+ !checkRectArray(br2_2AsArray, [br2_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>
+ var 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
+ var 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")) {
+ return;
+ }
+}
+
+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];
+
+ var editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect,
+ "runCharAtPointTest (" + aTargetName + "): editorRect")) {
+ return;
+ }
+
+ for (var i = 0; i < kTestingOffset.length; i++) {
+ var 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.
+ var 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.
+ var 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.
+ var 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.
+ var 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";
+ var editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect,
+ "runCharAtPointAtOutsideTest: editorRect")) {
+ return;
+ }
+ // Check on a text node which is at the outside of editor.
+ var 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();
+
+ var 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();
+
+ var 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 runQueryIMESelectionTest()
+{
+ textarea.focus();
+ textarea.value = "before after";
+ var 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 runQueryContentEventRelativeToInsertionPoint()
+{
+ textarea.focus();
+ textarea.value = "0123456789";
+
+ var 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;
+ }
+
+ 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;
+ }
+
+ 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 }
+ });
+
+ 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": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+
+ 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 runCSSTransformTest()
+{
+ textarea.focus();
+ textarea.value = "some text";
+ textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
+ var editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect,
+ "runCSSTransformTest: editorRect")) {
+ return;
+ }
+ var firstCharRect = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRect,
+ "runCSSTransformTest: firstCharRect")) {
+ return;
+ }
+ var lastCharRect = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRect,
+ "runCSSTransformTest: lastCharRect")) {
+ return;
+ }
+ var caretRect = synthesizeQueryCaretRect(textarea.selectionStart);
+ if (!checkQueryContentResult(caretRect,
+ "runCSSTransformTest: caretRect")) {
+ return;
+ }
+ var caretRectBeforeFirstChar = synthesizeQueryCaretRect(0);
+ if (!checkQueryContentResult(caretRectBeforeFirstChar,
+ "runCSSTransformTest: caretRectBeforeFirstChar")) {
+ return;
+ }
+
+ try {
+ textarea.style.transform = "translate(10px, 15px)";
+ function movedRect(aRect, aX, aY)
+ {
+ return { left: aRect.left + aX, top: aRect.top + aY, width: aRect.width, height: aRect.height }
+ }
+
+ var editorRectTranslated = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRectTranslated,
+ "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform) ||
+ !checkRect(editorRectTranslated, movedRect(editorRect, 10, 15),
+ "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ var firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslated,
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform) ||
+ !checkRect(firstCharRectTranslated, movedRect(firstCharRect, 10, 15),
+ "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ var lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
+ if (!checkQueryContentResult(lastCharRectTranslated,
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform) ||
+ !checkRect(lastCharRectTranslated, movedRect(lastCharRect, 10, 15),
+ "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ var caretRectTranslated = synthesizeQueryCaretRect(textarea.selectionStart);
+ if (!checkQueryContentResult(caretRectTranslated,
+ "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform) ||
+ !checkRect(caretRectTranslated, movedRect(caretRect, 10, 15),
+ "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ var caretRectBeforeFirstCharTranslated = synthesizeQueryCaretRect(0);
+ if (!checkQueryContentResult(caretRectBeforeFirstCharTranslated,
+ "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform) ||
+ !checkRect(caretRectBeforeFirstCharTranslated, movedRect(caretRectBeforeFirstChar, 10, 15),
+ "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform)) {
+ return;
+ }
+ var firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
+ if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
+ !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
+ return;
+ }
+ var 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
+
+ var firstLine = synthesizeQueryTextRect(0, 1);
+ if (!checkQueryContentResult(firstLine,
+ "runBug722639Test: firstLine")) {
+ return;
+ }
+ ok(true, "runBug722639Test: 1st line, top=" + firstLine.top + ", left=" + firstLine.left);
+ var 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) {
+ var 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");
+ is(firstLineLF.height, firstLine.height, "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");
+ var 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;
+ }
+ }
+ var secondLine = synthesizeQueryTextRect(kLFLen, 1);
+ if (!checkQueryContentResult(secondLine,
+ "runBug722639Test: secondLine")) {
+ return;
+ }
+ ok(true, "runBug722639Test: 2nd line, top=" + secondLine.top + ", left=" + secondLine.left);
+ var 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) {
+ var 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");
+ is(secondLineLF.height, secondLine.height, "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");
+ var 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;
+ }
+ }
+ var 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");
+ is(secondLine.height, firstLine.height,
+ "runBug722639Test: the height must be always same value");
+ var previousTop = secondLine.top;
+ for (var i = 3; i <= textarea.value.length + 1; i++) {
+ var 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);
+ var 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");
+ is(currentLine.height, firstLine.height,
+ "runBug722639Test: " + i + "th line's height is unexpected");
+ if (kLFLen > 1) {
+ var 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");
+ is(currentLineLF.height, currentLine.height, "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");
+ var 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()
+{
+ var events;
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ }
+ window.addEventListener("compositionstart", eventHandler, true);
+ window.addEventListener("compositionupdate", eventHandler, true);
+ window.addEventListener("compositionend", 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, 4,
+ "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, "input",
+ "runForceCommitTest: the 4th event must be input #1");
+
+ events = [];
+ synthesizeMouseAtCenter(textarea, {});
+
+ is(events.length, 3,
+ "runForceCommitTest: wrong event count #2");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #2");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #2");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #2");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #2");
+ is(events[0].target, textarea,
+ "runForceCommitTest: The 1st event was fired on wrong event target #2");
+ is(events[1].target, textarea,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #2");
+ is(events[2].target, textarea,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #2");
+ ok(!getEditorIMESupport(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, 3,
+ "runForceCommitTest: wrong event count #3");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #3");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #3");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #3");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #3");
+ is(events[0].target, textarea,
+ "runForceCommitTest: The 1st event was fired on wrong event target #3");
+ is(events[1].target, textarea,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #3");
+ is(events[2].target, textarea,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #3");
+ ok(!getEditorIMESupport(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #3");
+ ok(!getEditorIMESupport(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, 3,
+ "runForceCommitTest: wrong event count #4");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #4");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #4");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #4");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #4");
+ is(events[0].target, textarea,
+ "runForceCommitTest: The 1st event was fired on wrong event target #4");
+ is(events[1].target, textarea,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #4");
+ is(events[2].target, textarea,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #4");
+ ok(!getEditorIMESupport(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, 3,
+ "runForceCommitTest: wrong event count #5");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #5");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #5");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #5");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #5");
+ is(events[0].target, textarea,
+ "runForceCommitTest: The 1st event was fired on wrong event target #5");
+ is(events[1].target, textarea,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #5");
+ is(events[2].target, textarea,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #5");
+ ok(!getEditorIMESupport(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #5");
+ ok(!getEditorIMESupport(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, 3,
+ "runForceCommitTest: wrong event count #6");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #6");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #6");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #6");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #6");
+ is(events[0].target, textarea,
+ "runForceCommitTest: The 1st event was fired on wrong event target #6");
+ is(events[1].target, textarea,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #6");
+ is(events[2].target, textarea,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #6");
+ ok(!getEditorIMESupport(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #6");
+ ok(!getEditorIMESupport(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, 3,
+ "runForceCommitTest: wrong event count #7");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #7");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #7");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #7");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #7");
+ is(events[0].target, textarea,
+ "runForceCommitTest: The 1st event was fired on wrong event target #7");
+ is(events[1].target, textarea,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #7");
+ is(events[2].target, textarea,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #7");
+ ok(!getEditorIMESupport(textarea).isComposing,
+ "runForceCommitTest: the textarea still has composition #7");
+ ok(!getEditorIMESupport(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";
+ var 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, 3,
+ "runForceCommitTest: wrong event count #8");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #8");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #8");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #8");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #8");
+ is(events[0].target, textarea,
+ "runForceCommitTest: The 1st event was fired on wrong event target #8");
+ is(events[1].target, textarea,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #8");
+ is(events[2].target, textarea,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #8");
+ ok(!getEditorIMESupport(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, 3,
+ "runForceCommitTest: wrong event count #9");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #9");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #9");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #9");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #9");
+ is(events[0].target, iframe2.contentDocument.body,
+ "runForceCommitTest: The 1st event was fired on wrong event target #9");
+ is(events[1].target, iframe2.contentDocument.body,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #9");
+ is(events[2].target, iframe2.contentDocument.body,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #9");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document still has composition #9");
+ ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
+ iframe2.contentDocument.body.innerHTML.indexOf("\u306E") >= 0,
+ "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, 3,
+ "runForceCommitTest: wrong event count #10");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #10");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #10");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #10");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #10");
+ is(events[0].target, iframe2.contentDocument.body,
+ "runForceCommitTest: The 1st event was fired on wrong event target #10");
+ is(events[1].target, iframe2.contentDocument.body,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #10");
+ is(events[2].target, iframe2.contentDocument.body,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #10");
+ ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
+ "runForceCommitTest: the editable document still has composition #10");
+ ok(!getEditorIMESupport(textarea).isComposing,
+ "runForceCommitTest: the textarea has composition #10");
+ ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
+ iframe2.contentDocument.body.innerHTML.indexOf("\u306E") >= 0,
+ "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";
+ 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, 3,
+ "runForceCommitTest: wrong event count #11");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #11");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #11");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #11");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #11");
+ is(events[0].target, iframe2.contentDocument.body,
+ "runForceCommitTest: The 1st event was fired on wrong event target #11");
+ is(events[1].target, iframe2.contentDocument.body,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #11");
+ is(events[2].target, iframe2.contentDocument.body,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #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.indexOf("\u306E") >= 0,
+ "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, 3,
+ "runForceCommitTest: wrong event count #12");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #12");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #12");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #12");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #12");
+ is(events[0].target, input,
+ "runForceCommitTest: The 1st event was fired on wrong event target #12");
+ is(events[1].target, input,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #12");
+ is(events[2].target, input,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #12");
+ ok(!getEditorIMESupport(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, 3,
+ "runForceCommitTest: wrong event count #13");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #13");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #13");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #13");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #13");
+ is(events[0].target, textarea,
+ "runForceCommitTest: The 1st event was fired on wrong event target #13");
+ is(events[1].target, textarea,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #13");
+ is(events[2].target, textarea,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #13");
+ ok(!getEditorIMESupport(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, 3,
+ "runForceCommitTest: wrong event count #14");
+ is(events[0].type, "text",
+ "runForceCommitTest: the 1st event must be text #14");
+ is(events[1].type, "compositionend",
+ "runForceCommitTest: the 2nd event must be compositionend #14");
+ is(events[2].type, "input",
+ "runForceCommitTest: the 3rd event must be input #14");
+ is(events[1].data, "\u306E",
+ "runForceCommitTest: compositionend has wrong data #14");
+ is(events[0].target, input,
+ "runForceCommitTest: The 1st event was fired on wrong event target #14");
+ is(events[1].target, input,
+ "runForceCommitTest: The 2nd event was fired on wrong event target #14");
+ is(events[2].target, input,
+ "runForceCommitTest: The 3rd event was fired on wrong event target #14");
+ ok(!getEditorIMESupport(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("input", eventHandler, true);
+ window.removeEventListener("text", eventHandler, true);
+}
+
+function runNestedSettingValue()
+{
+ var isTesting = false;
+ var 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("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, 3,
+ "runNestedSettingValue: wrong event count #1");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #1");
+ is(events[1].type, "compositionend",
+ "runNestedSettingValue: the 2nd event must be compositionend #1");
+ is(events[2].type, "input",
+ "runNestedSettingValue: the 3rd event must be input #1");
+ is(events[1].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #1");
+ is(events[0].target, textarea,
+ "runNestedSettingValue: The 1st event was fired on wrong event target #1");
+ is(events[1].target, textarea,
+ "runNestedSettingValue: The 2nd event was fired on wrong event target #1");
+ is(events[2].target, textarea,
+ "runNestedSettingValue: The 3rd event was fired on wrong event target #1");
+ ok(!getEditorIMESupport(textarea).isComposing,
+ "runNestedSettingValue: the textarea still has composition #1");
+ is(textarea.value, "first setting value, text, 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, 3,
+ "runNestedSettingValue: wrong event count #2");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #2");
+ is(events[1].type, "compositionend",
+ "runNestedSettingValue: the 2nd event must be compositionend #2");
+ is(events[2].type, "input",
+ "runNestedSettingValue: the 3rd event must be input #2");
+ is(events[1].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #2");
+ is(events[0].target, input,
+ "runNestedSettingValue: The 1st event was fired on wrong event target #2");
+ is(events[1].target, input,
+ "runNestedSettingValue: The 2nd event was fired on wrong event target #2");
+ is(events[2].target, input,
+ "runNestedSettingValue: The 3rd event was fired on wrong event target #2");
+ ok(!getEditorIMESupport(input).isComposing,
+ "runNestedSettingValue: the input still has composition #2");
+ is(textarea.value, "first setting value, text, 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, 3,
+ "runNestedSettingValue: wrong event count #3");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #3");
+ is(events[1].type, "compositionend",
+ "runNestedSettingValue: the 2nd event must be compositionend #3");
+ is(events[2].type, "input",
+ "runNestedSettingValue: the 3rd event must be input #3");
+ is(events[1].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #3");
+ is(events[0].target, textarea,
+ "runNestedSettingValue: The 1st event was fired on wrong event target #3");
+ is(events[1].target, textarea,
+ "runNestedSettingValue: The 2nd event was fired on wrong event target #3");
+ is(events[2].target, textarea,
+ "runNestedSettingValue: The 3rd event was fired on wrong event target #3");
+ ok(!getEditorIMESupport(textarea).isComposing,
+ "runNestedSettingValue: the textarea still has composition #3");
+ is(textarea.value, "\u306Efirst setting value, text, 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, 3,
+ "runNestedSettingValue: wrong event count #4");
+ is(events[0].type, "text",
+ "runNestedSettingValue: the 1st event must be text #4");
+ is(events[1].type, "compositionend",
+ "runNestedSettingValue: the 2nd event must be compositionend #4");
+ is(events[2].type, "input",
+ "runNestedSettingValue: the 3rd event must be input #4");
+ is(events[1].data, "\u306E",
+ "runNestedSettingValue: compositionend has wrong data #4");
+ is(events[0].target, input,
+ "runNestedSettingValue: The 1st event was fired on wrong event target #4");
+ is(events[1].target, input,
+ "runNestedSettingValue: The 2nd event was fired on wrong event target #4");
+ is(events[2].target, input,
+ "runNestedSettingValue: The 3rd event was fired on wrong event target #4");
+ ok(!getEditorIMESupport(input).isComposing,
+ "runNestedSettingValue: the input still has composition #4");
+ is(textarea.value, "\u306Efirst setting value, text, 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("input", eventHandler, true);
+ window.removeEventListener("text", eventHandler, true);
+
+}
+
+function runAsyncForceCommitTest(aNextTest)
+{
+ var 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.
+ var commitRequested = false;
+ function callback(aTIP, aNotification)
+ {
+ ok(true, aNotification.type);
+ if (aNotification.type != "request-to-commit") {
+ return true;
+ }
+ commitRequested = true;
+ setTimeout(function () {
+ events = [];
+ aTIP.commitComposition();
+
+ is(events.length, 0,
+ "runAsyncForceCommitTest: composition events shouldn't been fired by asynchronous call of nsITextInputProcessor.commitComposition()");
+
+ window.removeEventListener("compositionstart", eventHandler, true);
+ window.removeEventListener("compositionupdate", eventHandler, true);
+ window.removeEventListener("compositionend", eventHandler, true);
+ window.removeEventListener("input", eventHandler, true);
+ window.removeEventListener("text", eventHandler, true);
+
+ SimpleTest.executeSoon(aNextTest);
+ }, 1);
+ return true;
+ };
+
+ window.addEventListener("compositionstart", eventHandler, true);
+ window.addEventListener("compositionupdate", eventHandler, true);
+ window.addEventListener("compositionend", 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, 4,
+ "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, "input",
+ "runAsyncForceCommitTest: the 4th event must be input #1");
+
+ events = [];
+ commitRequested = false;
+ synthesizeMouseAtCenter(textarea, {});
+
+ ok(commitRequested,
+ "runAsyncForceCommitTest: \"request-to-commit\" should've been notified");
+ is(events.length, 3,
+ "runAsyncForceCommitTest: wrong event count #2");
+ is(events[0].type, "text",
+ "runAsyncForceCommitTest: the 1st event must be text #2");
+ is(events[1].type, "compositionend",
+ "runAsyncForceCommitTest: the 2nd event must be compositionend #2");
+ is(events[2].type, "input",
+ "runAsyncForceCommitTest: the 3rd event must be input #2");
+ is(events[1].data, "\u306E",
+ "runAsyncForceCommitTest: compositionend has wrong data #2");
+ is(events[0].target, textarea,
+ "runAsyncForceCommitTest: The 1st event was fired on wrong event target #2");
+ is(events[1].target, textarea,
+ "runAsyncForceCommitTest: The 2nd event was fired on wrong event target #2");
+ is(events[2].target, textarea,
+ "runAsyncForceCommitTest: The 3rd event was fired on wrong event target #2");
+ ok(!getEditorIMESupport(textarea).isComposing,
+ "runAsyncForceCommitTest: the textarea still has composition #2");
+ is(textarea.value, "\u306E",
+ "runAsyncForceCommitTest: the textarea doesn't have the committed text #2");
+}
+
+function runBug811755Test()
+{
+ iframe2.contentDocument.body.innerHTML = "<div>content<br/></div>";
+ iframe2.contentWindow.focus();
+ // Query everything
+ var textContent = synthesizeQueryTextContent(0, 10);
+ if (!checkQueryContentResult(textContent, "runBug811755Test: synthesizeQueryTextContent #1")) {
+ return false;
+ }
+ // 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.
+ var queryContent = synthesizeQueryTextContent(0, textContent.text.length);
+ if (!checkQueryContentResult(queryContent, "runBug811755Test: synthesizeQueryTextContent #2")) {
+ return false;
+ }
+ is(queryContent.text, textContent.text, "runBug811755Test: two queried texts don't match");
+ return queryContent.text == textContent.text;
+}
+
+function runIsComposingTest()
+{
+ var expectedIsComposing = false;
+ var descriptionBase = "runIsComposingTest: ";
+ var description = "";
+
+ function eventHandler(aEvent)
+ {
+ if (aEvent.type == "keydown" || aEvent.type == "keyup") {
+ is(aEvent.isComposing, expectedIsComposing,
+ "runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
+ } else {
+ is(aEvent.isComposing, expectedIsComposing,
+ "runIsComposingTest: " + description + " (type=" + aEvent.type + ")");
+ }
+ }
+
+ 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("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).
+ SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+ description = "events before dispatching compositionstart";
+ synthesizeKey("VK_LEFT", {});
+
+ 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("VK_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("VK_RETURN", { type: "keyup" });
+
+ SpecialPowers.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+ textarea.removeEventListener("keydown", eventHandler, true);
+ textarea.removeEventListener("keypress", eventHandler, true);
+ textarea.removeEventListener("keyup", eventHandler, true);
+ textarea.removeEventListener("input", eventHandler, true);
+ textarea.removeEventListener("compositionstart", onComposition, true);
+ textarea.removeEventListener("compositionend", onComposition, true);
+
+ textarea.value = "";
+}
+
+function runRedundantChangeTest()
+{
+ textarea.focus();
+
+ var result = {};
+ function clearResult()
+ {
+ result = { compositionupdate: false, compositionend: false, text: false, input: false, inputaftercompositionend: false };
+ }
+
+ function handler(aEvent)
+ {
+ if (aEvent.type == "input" && result.compositionend) {
+ result.inputaftercompositionend = true;
+ return;
+ }
+ result[aEvent.type] = true;
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", 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.compositionupdate, true, "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #1");
+ is(result.compositionend, false, "runRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change #1");
+ is(result.text, true, "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #1");
+ is(result.input, true, "runRedundantChangeTest: input should be fired 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.compositionupdate, true, "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #2");
+ is(result.compositionend, false, "runRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change #2");
+ is(result.text, true, "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #2");
+ is(result.input, true, "runRedundantChangeTest: input should be fired 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.compositionupdate, false, "runRedundantChangeTest: compositionupdate shouldn't be fired after synthesizing composition change again");
+ is(result.compositionend, false, "runRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change again");
+ is(result.text, false, "runRedundantChangeTest: text shouldn't be fired after synthesizing composition change again because it's dispatched when there is composing string");
+ is(result.input, false, "runRedundantChangeTest: input shouldn't 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.compositionupdate, false, "runRedundantChangeTest: compositionupdate shouldn't be fired after synthesizing composition commit-as-is");
+ is(result.compositionend, true, "runRedundantChangeTest: compositionend should be fired after synthesizing composition commit-as-is");
+ is(result.text, true, "runRedundantChangeTest: text shouldn't be fired after synthesizing composition commit-as-is for removing the ranges");
+ is(result.input, false, "runRedundantChangeTest: input shouldn't be fired before compositionend at synthesizing commit-as-is");
+ is(result.inputaftercompositionend, true, "runRedundantChangeTest: input should be fired after synthesizing composition commit-as-is after compositionend");
+ is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has the commit string");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+ textarea.removeEventListener("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runNotRedundantChangeTest()
+{
+ textarea.focus();
+
+ var result = {};
+ function clearResult()
+ {
+ result = { compositionupdate: false, compositionend: false, text: false, input: false, inputaftercompositionend: false };
+ }
+
+ function handler(aEvent)
+ {
+ if (aEvent.type == "input" && result.compositionend) {
+ result.inputaftercompositionend = true;
+ return;
+ }
+ result[aEvent.type] = true;
+ }
+
+ textarea.addEventListener("compositionupdate", handler, true);
+ textarea.addEventListener("compositionend", 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.compositionupdate, true, "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges");
+ is(result.compositionend, false, "runNotRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change with non-null ranges");
+ is(result.text, true, "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges");
+ is(result.input, true, "runNotRedundantChangeTest: input should be fired 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.compositionupdate, false, "runNotRedundantChangeTest: compositionupdate shouldn't be fired after synthesizing composition change with null ranges after non-null ranges");
+ is(result.compositionend, false, "runNotRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change with null ranges after non-null ranges");
+ is(result.text, true, "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.input, true, "runNotRedundantChangeTest: input should be fired 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.compositionupdate, false, "runNotRedundantChangeTest: compositionupdate shouldn't be fired after synthesizing composition change with non-null ranges after null ranges");
+ is(result.compositionend, false, "runNotRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change with non-null ranges after null ranges");
+ is(result.text, true, "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.input, true, "runNotRedundantChangeTest: input should be fired 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.compositionupdate, true, "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(result.compositionend, false, "runNotRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
+ is(result.text, true, "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.input, true, "runNotRedundantChangeTest: input should be fired 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.compositionupdate, true, "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(result.compositionend, false, "runNotRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
+ is(result.text, true, "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.input, true, "runNotRedundantChangeTest: input should be fired 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.compositionupdate, true, "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition commit with empty data after non-empty data");
+ is(result.compositionend, true, "runNotRedundantChangeTest: compositionend should be fired after synthesizing composition commit with empty data after non-empty data");
+ is(result.text, true, "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.input, false, "runNotRedundantChangeTest: input shouldn't be fired before compositionend after synthesizing composition change with empty data after non-empty data");
+ is(result.inputaftercompositionend, true, "runNotRedundantChangeTest: input should be fired after compositionend 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("input", handler, true);
+ textarea.removeEventListener("text", handler, true);
+}
+
+function runControlCharTest()
+{
+ textarea.focus();
+
+ var 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 = "";
+
+ var controlChars = String.fromCharCode.apply(null, Object.keys(Array.from({length:0x20}))) + "\x7F";
+ var allowedChars = "\t";
+
+ var data = "AB" + controlChars + "CD" + controlChars + "EF";
+ var removedData = "AB" + allowedChars + "CD" + allowedChars + "EF";
+
+ var DIndex = data.indexOf("D");
+ var 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, "", "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: 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();
+
+ SpecialPowers.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 - 1 + kLFLen, "", "runControlCharTest", "#3")
+
+ is(result.compositionupdate, data, "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: data });
+
+ is(result.compositionend, data, "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");
+
+ SpecialPowers.clearUserPref("dom.compositionevent.allow_control_characters");
+
+ textarea.removeEventListener("compositionupdate", handler, true);
+ textarea.removeEventListener("compositionend", handler, true);
+}
+
+function runRemoveContentTest(aCallback)
+{
+ var events = [];
+ function eventHandler(aEvent)
+ {
+ events.push(aEvent);
+ }
+ textarea.addEventListener("compositionstart", eventHandler, true);
+ textarea.addEventListener("compositionupdate", eventHandler, true);
+ textarea.addEventListener("compositionend", 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 }
+ });
+
+ var nextSibling = textarea.nextSibling;
+ var parent = textarea.parentElement;
+
+ events = [];
+ parent.removeChild(textarea);
+
+ hitEventLoop(function () {
+ // XXX Currently, "input" event isn't 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[1].type, "text",
+ "runRemoveContentTest: the 2nd event must be text #1");
+ is(events[2].type, "compositionend",
+ "runRemoveContentTest: the 3rd event must be compositionend #1");
+ is(events[0].data, "",
+ "runRemoveContentTest: compositionupdate has wrong data #1");
+ is(events[2].data, "",
+ "runRemoveContentTest: compositionend has wrong data #1");
+ is(events[0].target, textarea,
+ "runRemoveContentTest: The 1st event was fired on wrong event target #1");
+ is(events[1].target, textarea,
+ "runRemoveContentTest: The 2nd event was fired on wrong event target #1");
+ is(events[2].target, textarea,
+ "runRemoveContentTest: The 3rd event was fired on wrong event target #1");
+ ok(!getEditorIMESupport(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);
+
+ hitEventLoop(function () {
+ // XXX Currently, "input" event isn't fired on removed content.
+ is(events.length, 1,
+ "runRemoveContentTest: wrong event count #2");
+ is(events[0].type, "compositionend",
+ "runRemoveContentTest: the 1st event must be compositionend #2");
+ is(events[0].data, "",
+ "runRemoveContentTest: compositionupdate has wrong data #2");
+ is(events[0].target, textarea,
+ "runRemoveContentTest: The 1st event was fired on wrong event target #2");
+ ok(!getEditorIMESupport(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("input", eventHandler, true);
+ textarea.removeEventListener("text", eventHandler, true);
+
+ SimpleTest.executeSoon(aCallback);
+ }, 50);
+ }, 50);
+}
+
+function runTestOnAnotherContext(aPanelOrFrame, aFocusedEditor, aTestName)
+{
+ aFocusedEditor.value = "";
+
+ var editorRect = synthesizeQueryEditorRect();
+ if (!checkQueryContentResult(editorRect, aTestName + ": editorRect")) {
+ return;
+ }
+
+ var r = aPanelOrFrame.getBoundingClientRect();
+ var parentRect = { "left": r.left, "top": r.top, "width": r.right - r.left,
+ "height": r.bottom - r.top };
+ 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;
+ }
+
+ var textRect = synthesizeQueryTextRect(0, 1);
+ var 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");
+}
+
+var gPanelShown = false;
+var gPanelFocused = false;
+function onPanelShown(aEvent)
+{
+ gPanelShown = true;
+ textbox.focus();
+ setTimeout(doPanelTest, 0);
+}
+
+function onFocusPanelTextbox(aEvent)
+{
+ gPanelFocused = true;
+ setTimeout(doPanelTest, 0);
+}
+
+var gIsPanelHiding = false;
+var gIsRunPanelTestInternal = false;
+function doPanelTest()
+{
+ if (!gPanelFocused || !gPanelShown) {
+ return;
+ }
+ if (gIsRunPanelTestInternal) {
+ return;
+ }
+ gIsRunPanelTestInternal = true;
+ runTestOnAnotherContext(panel, textbox, "runPanelTest");
+ runCharAtPointTest(textbox, "textbox in the panel");
+ gIsPanelHiding = true;
+ panel.hidePopup();
+}
+
+function onPanelHidden(aEvent)
+{
+ panel.hidden = true;
+ ok(gIsPanelHiding, "runPanelTest: the panel is hidden unexpectedly");
+ finish();
+}
+
+function runPanelTest()
+{
+ panel.hidden = false;
+ panel.openPopupAtScreen(window.screenX + window.outerWidth, 0, false);
+}
+
+function runMaxLengthTest()
+{
+ input.maxLength = 1;
+ input.value = "";
+ input.focus();
+
+ var 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")) {
+ return;
+ }
+}
+
+function runEditorReframeTests(aCallback)
+{
+ function runEditorReframeTest(aEditor, aWindow, aEventType, aNextTest)
+ {
+ function getValue()
+ {
+ return aEditor == contenteditable ?
+ aEditor.innerHTML.replace("<br>", "") : aEditor.value;
+ }
+
+ var description = "runEditorReframeTest(" + aEditor.id + ", \"" + aEventType + "\"): ";
+
+ var tests = [
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "a",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "a", description + "Typing 'a'");
+ },
+ },
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ab",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "ab", description + "Typing 'b' next to 'a'");
+ },
+ },
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "abc",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "abc", description + "Typing 'c' next to 'ab'");
+ },
+ },
+ { test: function () {
+ 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: function () {
+ is(getValue(aEditor), "abc", description + "Starting to convert 'ab][c'");
+ },
+ },
+ { test: function () {
+ 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: function () {
+ is(getValue(aEditor), "ABc", description + "Starting to convert 'AB][c'");
+ },
+ },
+ { test: function () {
+ 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: function () {
+ is(getValue(aEditor), "ABC", description + "Starting to convert 'AB][C'");
+ },
+ },
+ { test: function () {
+ // Commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABC", description + "Committed as 'ABC'");
+ },
+ },
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "d",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABCd", description + "Typing 'd' next to ABC");
+ },
+ },
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "de",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABCde", description + "Typing 'e' next to ABCd");
+ },
+ },
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "def",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABCdef", description + "Typing 'f' next to ABCde");
+ },
+ },
+ { test: function () {
+ // Commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABCdef", description + "Commit 'def' without convert");
+ },
+ },
+ { test: function () {
+ // Select "Cd"
+ synthesizeKey("KEY_ArrowLeft", { code: "ArrowLeft" });
+ synthesizeKey("KEY_ArrowLeft", { code: "ArrowLeft" });
+ synthesizeKey("KEY_Shift", { type: "keydown", code: "ShiftLeft", shiftKey: true });
+ synthesizeKey("KEY_ArrowLeft", { code: "ArrowLeft", shiftKey: true });
+ synthesizeKey("KEY_ArrowLeft", { code: "ArrowLeft", shiftKey: true });
+ synthesizeKey("KEY_Shift", { type: "keyup", code: "ShiftLeft" });
+ },
+ check: function () {
+ },
+ },
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "g",
+ "clauses":
+ [
+ { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 1, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABgef", description + "Typing 'g' next to AB");
+ },
+ },
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "gh",
+ "clauses":
+ [
+ { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 2, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABghef", description + "Typing 'h' next to ABg");
+ },
+ },
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "ghi",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABghief", description + "Typing 'i' next to ABgh");
+ },
+ },
+ { test: function () {
+ synthesizeCompositionChange(
+ { "composition":
+ { "string": "GHI",
+ "clauses":
+ [
+ { "length": 3, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ "caret": { "start": 3, "length": 0 }
+ });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABGHIef", description + "Convert 'ghi' to 'GHI'");
+ },
+ },
+ { test: function () {
+ // Commit composition
+ synthesizeComposition({ type: "compositioncommitasis" });
+ },
+ check: function () {
+ is(getValue(aEditor), "ABGHIef", description + "Commit 'GHI'");
+ },
+ },
+ ];
+
+ var index = 0;
+ function doReframe(aEvent)
+ {
+ aEvent.target.style.overflow =
+ aEvent.target.style.overflow != "hidden" ? "hidden" : "auto";
+ }
+ aEditor.focus();
+ aEditor.addEventListener(aEventType, doReframe);
+
+ function doNext()
+ {
+ if (tests.length <= index) {
+ aEditor.style.overflow = "auto";
+ aEditor.removeEventListener(aEventType, doReframe);
+ requestAnimationFrame(function() { setTimeout(aNextTest); });
+ return;
+ }
+ tests[index].test();
+ hitEventLoop(function () {
+ tests[index].check();
+ index++;
+ setTimeout(doNext, 0);
+ }, 20);
+ }
+ doNext();
+ }
+
+ input.value = "";
+ runEditorReframeTest(input, window, "input", function () {
+ input.value = "";
+ runEditorReframeTest(input, window, "compositionupdate", function () {
+ textarea.value = "";
+ runEditorReframeTest(textarea, window, "input", function () {
+ textarea.value = "";
+ runEditorReframeTest(textarea, window, "compositionupdate", function () {
+ contenteditable.innerHTML = "";
+ runEditorReframeTest(contenteditable, windowOfContenteditable, "input", function () {
+ contenteditable.innerHTML = "";
+ runEditorReframeTest(contenteditable, windowOfContenteditable, "compositionupdate", function () {
+ aCallback();
+ });
+ });
+ });
+ });
+ });
+ });
+}
+
+function runTest()
+{
+ contenteditable = document.getElementById("iframe4").contentDocument.getElementById("contenteditable");
+ windowOfContenteditable = document.getElementById("iframe4").contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+
+ runUndoRedoTest();
+ runCompositionCommitAsIsTest();
+ runCompositionCommitTest();
+ runCompositionTest();
+ runCompositionEventTest();
+ runQueryTextRectInContentEditableTest();
+ runCharAtPointTest(textarea, "textarea in the document");
+ runCharAtPointAtOutsideTest();
+ runSetSelectionEventTest();
+ runQueryTextContentEventTest();
+ runQueryIMESelectionTest();
+ runQueryContentEventRelativeToInsertionPoint();
+ runCSSTransformTest();
+ runBug722639Test();
+ runForceCommitTest();
+ runNestedSettingValue();
+ runBug811755Test();
+ runIsComposingTest();
+ runRedundantChangeTest();
+ runNotRedundantChangeTest();
+ runControlCharTest();
+ runEditorReframeTests(function () {
+ runAsyncForceCommitTest(function () {
+ runRemoveContentTest(function () {
+ runFrameTest();
+ runPanelTest();
+ runMaxLengthTest();
+ });
+ });
+ });
+}
+
+]]>
+</script>
+
+</window>
diff --git a/widget/tests/window_imestate_iframes.html b/widget/tests/window_imestate_iframes.html
new file mode 100644
index 000000000..064cf19a5
--- /dev/null
+++ b/widget/tests/window_imestate_iframes.html
@@ -0,0 +1,380 @@
+<html>
+<head>
+ <title>Test for IME state controling and focus moving for iframes</title>
+ <script type="text/javascript"
+ 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">
+
+window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTests, window);
+
+function ok(aCondition, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function onUnload()
+{
+ window.opener.wrappedJSObject.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.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ var fm =
+ Components.classes["@mozilla.org/focus-manager;1"]
+ .getService(Components.interfaces.nsIFocusManager);
+
+ var iframe, editor, root, input;
+ 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 =
+ Components.interfaces.nsIPlaintextEditor.eEditorReadonlyMask;
+ var description = "testOnEditorFlagChange: " + aDescription;
+ resetFocusToParentHTML(description);
+ var htmlEditor =
+ iframe.contentWindow.
+ QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIWebNavigation).
+ QueryInterface(Components.interfaces.nsIDocShell).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 000000000..4a83e23ef
--- /dev/null
+++ b/widget/tests/window_mouse_scroll_win.html
@@ -0,0 +1,1531 @@
+<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 type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ 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.opener.wrappedJSObject.SimpleTest.waitForFocus(prepareTests, window);
+
+const nsIDOMWindowUtils = Components.interfaces.nsIDOMWindowUtils;
+
+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 = Components.interfaces.nsIDOMMouseScrollEvent.VERTICAL_AXIS;
+const kHAxis = Components.interfaces.nsIDOMMouseScrollEvent.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.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+function todo_is(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.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.opener.wrappedJSObject.SimpleTest.finish();
+}
+
+function getWindowUtils(aWindow)
+{
+ if (!aWindow) {
+ aWindow = window;
+ }
+ return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(nsIDOMWindowUtils);
+}
+
+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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent: function (aEvent) {
+ return true;
+ },
+ onPixelScrollEvent: function (aEvent) {
+ gLineHeight = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kVAxis, lines: 1, pixels: 1,
+ },
+ init: function () {
+ SpecialPowers.setIntPref(kVAmountPref, 1);
+ SpecialPowers.setIntPref(kHAmountPref, 1);
+ },
+ },
+ { description: "Preparing gCharWidth",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent: function (aEvent) {
+ return true;
+ },
+ onPixelScrollEvent: function (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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent: function (aEvent) {
+ return true;
+ },
+ onPixelScrollEvent: function (aEvent) {
+ gPageHeight = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kHAxis, lines: 1, pixels: 1,
+ },
+ init: function () {
+ SpecialPowers.setIntPref(kVAmountPref, 0xFFFF);
+ SpecialPowers.setIntPref(kHAmountPref, 0xFFFF);
+ },
+ },
+ { description: "Preparing gPageWidth",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ onLineScrollEvent: function (aEvent) {
+ return true;
+ },
+ onPixelScrollEvent: function (aEvent) {
+ gPageWidth = aEvent.detail;
+ return true;
+ },
+ expected: {
+ axis: kHAxis, lines: 1, pixels: 1,
+ },
+ finish: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 3, pixels: function () { return gLineHeight * 3; },
+ },
+ init: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -3, pixels: function () { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 3, pixels: function () { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -3, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { return gLineHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -20, 0.5 lines",
+ message: WM_MOUSEWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -1, pixels: function () { return -gLineHeight / 2; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 20, -0.5 lines",
+ message: WM_MOUSEWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: function () { return gLineHeight / -2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 20, 0.5 chars",
+ message: WM_MOUSEHWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { return gCharWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 20, 0.5 chars",
+ message: WM_MOUSEHWHEEL, delta: 20,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -1, pixels: function () { return -gCharWidth / 2; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -20, -0.5 chars",
+ message: WM_MOUSEHWHEEL, delta: -20,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 3, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -3, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 3, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -3, pixels: function () { 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: window,
+ modifiers: SHIFT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with right Shift",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: SHIFT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with left Ctrl",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: CTRL_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with right Ctrl",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: CTRL_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with left Alt",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: ALT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { return gLineHeight; },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -40, 1 line with right Alt",
+ message: WM_MOUSEWHEEL, delta: -40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: ALT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { return gLineHeight; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with left Shift",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: SHIFT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with right Shift",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: SHIFT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with left Ctrl",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: CTRL_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with right Ctrl",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: CTRL_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with left Alt",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: ALT_L,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { return gCharWidth; },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 40, 1 character with right Alt",
+ message: WM_MOUSEHWHEEL, delta: 40,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: ALT_R,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { return gCharWidth; },
+ },
+
+ finish: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { return ((gPageHeight / 2) + (gPageHeight % 2)); },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, -60, 0.5 pages",
+ message: WM_MOUSEWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { return -((gPageHeight / 2) + (gPageHeight % 2)); },
+ },
+ },
+ { description: "WM_MOUSEWHEEL, 60, -0.5 pages",
+ message: WM_MOUSEWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: function () { return gPageHeight / -2; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 60, 0.5 pages",
+ message: WM_MOUSEHWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { return ((gPageWidth / 2) + (gPageWidth % 2)); },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, 60, 0.5 pages",
+ message: WM_MOUSEHWHEEL, delta: 60,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { return -((gCharWidth / 2) + (gCharWidth % 2)); },
+ },
+ },
+ { description: "WM_MOUSEHWHEEL, -60, -0.5 pages",
+ message: WM_MOUSEHWHEEL, delta: -60,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 0, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 0, pixels: 0,
+ },
+ init: function () {
+ 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: 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: 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: 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: -1, pixels: function () { return -gLineHeight; },
+ },
+ init: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: -1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -1, pixels: function () { return -gLineHeight; },
+ },
+ init: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: DOM_PAGE_SCROLL_DELTA,
+ pixels: function () { 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: window,
+ modifiers: SHIFT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { return gLineHeight; },
+ },
+ init: function () {
+ 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: window,
+ modifiers: SHIFT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: ALT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: ALT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: SHIFT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: SHIFT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: CTRL_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: ALT_L,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: ALT_R,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { return gCharWidth; },
+ },
+
+ finish: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 3, pixels: function () { return gLineHeight * 3; },
+ },
+ init: function () {
+ SpecialPowers.setIntPref(kVAmountPref, 3);
+ SpecialPowers.setIntPref(kHAmountPref, 3);
+ },
+ onLineScrollEvent: function (aEvent) {
+ var fm = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+ 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -3, pixels: function () { return gLineHeight * -3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, 120, 3 chars, window is deactive",
+ message: WM_MOUSEHWHEEL, delta: 120,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 3, pixels: function () { return gCharWidth * 3; },
+ },
+ },
+
+ { description: "WM_MOUSEHWHEEL, -120, -3 chars, window is deactive",
+ message: WM_MOUSEHWHEEL, delta: -120,
+ target: gP1, x: 10, y: 10, window: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -3, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: 3, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: -3, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: 3, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: -3, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: -1, pixels: function () { return -gLineHeight; },
+ },
+ init: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: -1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: -1, pixels: function () { return -gLineHeight; },
+ },
+ init: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: -1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: 0,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: -1, pixels: function () { return -gLineHeight; },
+ },
+ init: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: -1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL |
+ nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: -1, pixels: function () { return -gLineHeight; },
+ },
+ init: function () {
+ 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kVAxis, lines: 1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: -1, pixels: function () { 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: window,
+ modifiers: 0,
+ additionalFlags: nsIDOMWindowUtils.MOUSESCROLL_PREFER_WIDGET_AT_POINT,
+ expected: {
+ axis: kHAxis, lines: 1, pixels: function () { return gCharWidth; },
+ },
+
+ finish: function () {
+ gOtherWindow.close();
+ gOtherWindow = null;
+ window.close();
+ }
+ },
+];
+
+function runDeactiveWindowTests()
+{
+ gOtherWindow = window.open("data:text/html,", "_blank",
+ "chrome,width=100,height=100,top=700,left=700");
+
+ window.opener.wrappedJSObject.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_picker_no_crash_child.html b/widget/tests/window_picker_no_crash_child.html
new file mode 100644
index 000000000..51bf1b1e6
--- /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.xul b/widget/tests/window_state_windows.xul
new file mode 100644
index 000000000..9643e1dad
--- /dev/null
+++ b/widget/tests/window_state_windows.xul
@@ -0,0 +1,87 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript">
+ <![CDATA[
+
+ let Cc = Components.classes;
+ let Ci = Components.interfaces;
+ let Cu = Components.utils;
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ function onLoad() {
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+ var win = 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.height > 0, "document height should not be zero for a minimized window!");
+ ok(document.width > 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.xul b/widget/tests/window_wheeltransaction.xul
new file mode 100644
index 000000000..8573eb3a4
--- /dev/null
+++ b/widget/tests/window_wheeltransaction.xul
@@ -0,0 +1,1560 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+ <script type="application/javascript"
+ 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.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+ window.opener.wrappedJSObject.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 = Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+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, false);
+gRootView.addEventListener("MozMouseScrollTransactionTimeout",
+ onTransactionTimeout, false);
+
+function finish()
+{
+ window.close();
+}
+
+function onload()
+{
+ runNextTestList();
+}
+
+function onunload()
+{
+ resetTimeoutPrefs();
+ gPrefSvc.clearUserPref(kPrefSmoothScroll);
+ gPrefSvc.clearUserPref(kPrefTestEventsAsyncEnabled);
+ disableNonTestMouseEvents(false);
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+ window.opener.wrappedJSObject.SimpleTest.finish();
+}
+
+const kSubView1Offset = { x: 20, y: 20 };
+const kSubView2Offset = { x: 20, y: 20 + 100 + 20 };
+const kSubView3Offset = { x: 20, y: 20 + (100 + 20) * 2 };
+
+function _getSubViewTestPtForV(aPt)
+{
+ return { x: aPt.x + 10, y: aPt.y + 10 };
+}
+
+const kPtInRootViewForV = { x: kSubView1Offset.x + 10,
+ y: kSubView1Offset.y - 10 };
+const kPtInSubView1ForV = _getSubViewTestPtForV(kSubView1Offset);
+const kPtInSubView2ForV = _getSubViewTestPtForV(kSubView2Offset);
+const kPtInSubView3ForV = _getSubViewTestPtForV(kSubView3Offset);
+
+function _convertTestPtForH(aPt)
+{
+ return { x: aPt.y, y: aPt.x };
+}
+
+const kPtInRootViewForH = _convertTestPtForH(kPtInRootViewForV);
+const kPtInSubView1ForH = _convertTestPtForH(kPtInSubView1ForV);
+const kPtInSubView2ForH = _convertTestPtForH(kPtInSubView2ForV);
+const kPtInSubView3ForH = _convertTestPtForH(kPtInSubView3ForV);
+
+/**
+ * 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: kPtInRootViewForV,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: kPtInRootViewForV,
+ 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: kPtInRootViewForH,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for root view (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: kPtInRootViewForH,
+ 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: kPtInSubView1ForV,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: kPtInSubView1ForV,
+ 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: kPtInSubView1ForH,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ description: "Continuous scrolling test for sub view 1 (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: kPtInSubView1ForH,
+ 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: kPtInSubView2ForV,
+ isForward: true, isVertical: true, expectedView: gSubView2,
+ description: "Continuous scrolling test for sub view 2 (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: kPtInSubView2ForV,
+ 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: kPtInSubView2ForH,
+ isForward: true, isVertical: false, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 2 (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: kPtInSubView2ForH,
+ 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: kPtInSubView3ForV,
+ isForward: true, isVertical: true, expectedView: gRootView,
+ description: "Continuous scrolling test for sub view 3 (vertical/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: kPtInSubView3ForV,
+ 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: kPtInSubView3ForH,
+ isForward: true, isVertical: false, expectedView: gSubView3,
+ description: "Continuous scrolling test for sub view 3 (horizontal/forward)" },
+ { func: testContinuousScroll, delay: 0, offset: kPtInSubView3ForH,
+ 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: kPtInRootViewForV,
+ 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: kPtInRootViewForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInRootViewForH,
+ 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: kPtInRootViewForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView2ForV,
+ 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: kPtInSubView2ForV,
+ 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: kPtInSubView3ForV,
+ 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: kPtInSubView3ForV,
+ 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: kPtInRootViewForV,
+ 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: kPtInRootViewForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInRootViewForH,
+ 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: kPtInRootViewForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInRootViewForV,
+ 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: kPtInRootViewForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInRootViewForH,
+ 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: kPtInRootViewForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInRootViewForV,
+ 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: kPtInRootViewForV,
+ 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: kPtInSubView1ForV,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: kPtInSubView1ForV,
+ 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: kPtInRootViewForH,
+ 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: kPtInRootViewForH,
+ 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: kPtInSubView1ForH,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: 0, offset: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ description: "sendMouseMoveEvent" },
+ { func: testOneTimeScroll, delay: gEnoughForIgnoreMoveDelay,
+ offset: kPtInSubView1ForH,
+ 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: kPtInSubView1ForV,
+ 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: kPtInRootViewForV,
+ description: "sendMouseMoveEvent" },
+ // Send Wheel event over |gRootView| which should be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: kPtInRootViewForV,
+ 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: kPtInSubView1ForH,
+ 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: kPtInRootViewForH,
+ description: "sendMouseMoveEvent" },
+ // Send Wheel event over |gRootView| which should be scrolled.
+ { func: testOneTimeScroll, delay: 0, offset: kPtInRootViewForH,
+ 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: kPtInRootViewForV,
+ 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: kPtInRootViewForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInRootViewForH,
+ 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: kPtInRootViewForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInSubView1ForV,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView1ForH,
+ 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: kPtInSubView1ForV,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (v-1)" },
+ { func: testOneTimeScroll, delay: 0, offset: kPtInSubView1ForV,
+ isForward: true, isVertical: true, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (v-2)" },
+ { func: testOneTimeScroll, delay: 0, offset: kPtInSubView1ForV,
+ isForward: true, isVertical: false, expectedView: gSubView1,
+ delta: 5000,
+ description: "Very large delta scrolling (h-1)" },
+ { func: testOneTimeScroll, delay: 0, offset: kPtInSubView1ForV,
+ 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;
+ }
+
+ function initInRootView(aElement, aPt)
+ {
+ aElement.offset =
+ gCurrentTest.forVertical ? aPt : { x: aPt.y, y: aPt.x };
+ }
+
+ 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()
+{
+ 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
+ * 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
+ * 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
+ * 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 000000000..2aea3b5ea
--- /dev/null
+++ b/widget/uikit/GfxInfo.cpp
@@ -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 "GfxInfo.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace widget {
+
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+GfxInfo::GfxInfo()
+{
+}
+
+GfxInfo::~GfxInfo()
+{
+}
+
+nsresult
+GfxInfo::GetD2DEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+GfxInfo::GetDWriteEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString & aDwriteVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString & aCleartypeParams)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString & aAdapterDescription)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString & aAdapterDescription)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(nsAString & aAdapterRAM)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & aAdapterRAM)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString & aAdapterDriver)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString & aAdapterDriver)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString & aAdapterDriverDate)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString & aAdapterDriverDate)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString & aAdapterVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID)
+{
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString & aAdapterDeviceID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active)
+{
+ return NS_ERROR_FAILURE;
+}
+
+const nsTArray<GfxDriverInfo>&
+GfxInfo::GetGfxDriverInfo()
+{
+ if (mDriverInfo->IsEmpty()) {
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Ios,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAll), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_STATUS_OK,
+ DRIVER_COMPARISON_IGNORED, GfxDriverInfo::allDriverVersions );
+ }
+
+ return *mDriverInfo;
+}
+
+nsresult
+GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ OperatingSystem* aOS /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ if (aOS)
+ *aOS = OperatingSystem::Ios;
+
+ // OpenGL layers are never blacklisted on iOS.
+ // This early return is so we avoid potentially slow
+ // GLStrings initialization on startup when we initialize GL layers.
+ if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS ||
+ aFeature == nsIGfxInfo::FEATURE_WEBGL_OPENGL ||
+ aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA) {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ return NS_OK;
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aOS);
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString & aVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString & aDeviceID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString & aDriverVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+#endif
+
+}
+}
diff --git a/widget/uikit/GfxInfo.h b/widget/uikit/GfxInfo.h
new file mode 100644
index 000000000..16a224251
--- /dev/null
+++ b/widget/uikit/GfxInfo.h
@@ -0,0 +1,78 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __mozilla_widget_GfxInfo_h__
+#define __mozilla_widget_GfxInfo_h__
+
+#include "GfxInfoBase.h"
+#include "GfxDriverInfo.h"
+
+#include "nsString.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+}
+
+namespace widget {
+
+class GfxInfo : public GfxInfoBase
+{
+private:
+ ~GfxInfo();
+
+public:
+ GfxInfo();
+
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool *aD2DEnabled);
+ NS_IMETHOD GetDWriteEnabled(bool *aDWriteEnabled);
+ NS_IMETHOD GetDWriteVersion(nsAString & aDwriteVersion);
+ NS_IMETHOD GetCleartypeParameters(nsAString & aCleartypeParams);
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription);
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver);
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID);
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID);
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID);
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM);
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion);
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate);
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription);
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver);
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID);
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID);
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID);
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM);
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion);
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate);
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active);
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+protected:
+
+ virtual nsresult GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ OperatingSystem* aOS = nullptr);
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/uikit/moz.build b/widget/uikit/moz.build
new file mode 100644
index 000000000..50aed405b
--- /dev/null
+++ b/widget/uikit/moz.build
@@ -0,0 +1,19 @@
+# -*- 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 += [
+ '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 000000000..a88fa8b4f
--- /dev/null
+++ b/widget/uikit/nsAppShell.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Runs the main native UIKit run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#ifndef nsAppShell_h_
+#define nsAppShell_h_
+
+#include "nsBaseAppShell.h"
+#include "nsTArray.h"
+
+#include <Foundation/NSAutoreleasePool.h>
+#include <CoreFoundation/CFRunLoop.h>
+#include <UIKit/UIWindow.h>
+
+@class AppShellDelegate;
+
+class nsAppShell : public nsBaseAppShell
+{
+public:
+ NS_IMETHOD ResumeNative(void);
+
+ nsAppShell();
+
+ nsresult Init();
+
+ NS_IMETHOD Run(void);
+ NS_IMETHOD Exit(void);
+ // Called by the application delegate
+ void WillTerminate(void);
+
+ static nsAppShell* gAppShell;
+ static UIWindow* gWindow;
+ static NSMutableArray* gTopLevelViews;
+
+protected:
+ virtual ~nsAppShell();
+
+ static void ProcessGeckoEvents(void* aInfo);
+ virtual void ScheduleNativeEventCallback();
+ virtual bool ProcessNextNativeEvent(bool aMayWait);
+
+ NSAutoreleasePool* mAutoreleasePool;
+ AppShellDelegate* mDelegate;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mCFRunLoopSource;
+
+ bool mTerminated;
+ bool mNotifiedWillTerminate;
+};
+
+#endif // nsAppShell_h_
diff --git a/widget/uikit/nsAppShell.mm b/widget/uikit/nsAppShell.mm
new file mode 100644
index 000000000..ac007132f
--- /dev/null
+++ b/widget/uikit/nsAppShell.mm
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <UIKit/UIApplication.h>
+#import <UIKit/UIScreen.h>
+#import <UIKit/UIWindow.h>
+#import <UIKit/UIViewController.h>
+
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "nsIWindowMediator.h"
+#include "nsMemoryPressure.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIWebBrowserChrome.h"
+
+nsAppShell *nsAppShell::gAppShell = NULL;
+UIWindow *nsAppShell::gWindow = nil;
+NSMutableArray *nsAppShell::gTopLevelViews = [[NSMutableArray alloc] init];
+
+#define ALOG(args...) fprintf(stderr, args); fprintf(stderr, "\n")
+
+// ViewController
+@interface ViewController : UIViewController
+@end
+
+
+@implementation ViewController
+
+- (void)loadView {
+ ALOG("[ViewController loadView]");
+ CGRect r = {{0, 0}, {100, 100}};
+ self.view = [[UIView alloc] initWithFrame:r];
+ [self.view setBackgroundColor:[UIColor lightGrayColor]];
+ // add all of the top level views as children
+ for (UIView* v in nsAppShell::gTopLevelViews) {
+ ALOG("[ViewController.view addSubView:%p]", v);
+ [self.view addSubview:v];
+ }
+ [nsAppShell::gTopLevelViews release];
+ nsAppShell::gTopLevelViews = nil;
+}
+@end
+
+// AppShellDelegate
+//
+// Acts as a delegate for the UIApplication
+
+@interface AppShellDelegate : NSObject <UIApplicationDelegate> {
+}
+@property (strong, nonatomic) UIWindow *window;
+@end
+
+@implementation AppShellDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ ALOG("[AppShellDelegate application:didFinishLaunchingWithOptions:]");
+ // We only create one window, since we can only display one window at
+ // a time anyway. Also, iOS 4 fails to display UIWindows if you
+ // create them before calling UIApplicationMain, so this makes more sense.
+ nsAppShell::gWindow = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] retain];
+ self.window = nsAppShell::gWindow;
+
+ self.window.rootViewController = [[ViewController alloc] init];
+
+ // just to make things more visible for now
+ nsAppShell::gWindow.backgroundColor = [UIColor blueColor];
+ [nsAppShell::gWindow makeKeyAndVisible];
+
+ return YES;
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application
+{
+ ALOG("[AppShellDelegate applicationWillTerminate:]");
+ nsAppShell::gAppShell->WillTerminate();
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application
+{
+ ALOG("[AppShellDelegate applicationDidBecomeActive:]");
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application
+{
+ ALOG("[AppShellDelegate applicationWillResignActive:]");
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
+{
+ ALOG("[AppShellDelegate applicationDidReceiveMemoryWarning:]");
+ NS_DispatchMemoryPressure(MemPressure_New);
+}
+@end
+
+// nsAppShell implementation
+
+NS_IMETHODIMP
+nsAppShell::ResumeNative(void)
+{
+ return nsBaseAppShell::ResumeNative();
+}
+
+nsAppShell::nsAppShell()
+ : mAutoreleasePool(NULL),
+ mDelegate(NULL),
+ mCFRunLoop(NULL),
+ mCFRunLoopSource(NULL),
+ mTerminated(false),
+ mNotifiedWillTerminate(false)
+{
+ gAppShell = this;
+}
+
+nsAppShell::~nsAppShell()
+{
+ if (mAutoreleasePool) {
+ [mAutoreleasePool release];
+ mAutoreleasePool = NULL;
+ }
+
+ if (mCFRunLoop) {
+ if (mCFRunLoopSource) {
+ ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
+ kCFRunLoopCommonModes);
+ ::CFRelease(mCFRunLoopSource);
+ }
+ ::CFRelease(mCFRunLoop);
+ }
+
+ gAppShell = NULL;
+}
+
+// Init
+//
+// public
+nsresult
+nsAppShell::Init()
+{
+ mAutoreleasePool = [[NSAutoreleasePool alloc] init];
+
+ // Add a CFRunLoopSource to the main native run loop. The source is
+ // responsible for interrupting the run loop when Gecko events are ready.
+
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ NS_ENSURE_STATE(mCFRunLoop);
+ ::CFRetain(mCFRunLoop);
+
+ CFRunLoopSourceContext context;
+ bzero(&context, sizeof(context));
+ // context.version = 0;
+ context.info = this;
+ context.perform = ProcessGeckoEvents;
+
+ mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
+ NS_ENSURE_STATE(mCFRunLoopSource);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
+
+ return nsBaseAppShell::Init();
+}
+
+// ProcessGeckoEvents
+//
+// The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
+// signalled from ScheduleNativeEventCallback.
+//
+// protected static
+void
+nsAppShell::ProcessGeckoEvents(void* aInfo)
+{
+ nsAppShell* self = static_cast<nsAppShell*> (aInfo);
+ self->NativeEventCallback();
+ self->Release();
+}
+
+// WillTerminate
+//
+// public
+void
+nsAppShell::WillTerminate()
+{
+ mNotifiedWillTerminate = true;
+ if (mTerminated)
+ return;
+ mTerminated = true;
+ // We won't get another chance to process events
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+
+ // Unless we call nsBaseAppShell::Exit() here, it might not get called
+ // at all.
+ nsBaseAppShell::Exit();
+}
+
+// ScheduleNativeEventCallback
+//
+// protected virtual
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ if (mTerminated)
+ return;
+
+ NS_ADDREF_THIS();
+
+ // This will invoke ProcessGeckoEvents on the main thread.
+ ::CFRunLoopSourceSignal(mCFRunLoopSource);
+ ::CFRunLoopWakeUp(mCFRunLoop);
+}
+
+// ProcessNextNativeEvent
+//
+// protected virtual
+bool
+nsAppShell::ProcessNextNativeEvent(bool aMayWait)
+{
+ if (mTerminated)
+ return false;
+
+ NSString* currentMode = nil;
+ NSDate* waitUntil = nil;
+ if (aMayWait)
+ waitUntil = [NSDate distantFuture];
+ NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
+
+ BOOL eventProcessed = NO;
+ do {
+ currentMode = [currentRunLoop currentMode];
+ if (!currentMode)
+ currentMode = NSDefaultRunLoopMode;
+
+ if (aMayWait)
+ eventProcessed = [currentRunLoop runMode:currentMode beforeDate:waitUntil];
+ else
+ [currentRunLoop acceptInputForMode:currentMode beforeDate:waitUntil];
+ } while(eventProcessed && aMayWait);
+
+ return false;
+}
+
+// Run
+//
+// public
+NS_IMETHODIMP
+nsAppShell::Run(void)
+{
+ ALOG("nsAppShell::Run");
+ char argv[1][4] = {"app"};
+ UIApplicationMain(1, (char**)argv, nil, @"AppShellDelegate");
+ // UIApplicationMain doesn't exit. :-(
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void)
+{
+ if (mTerminated)
+ return NS_OK;
+
+ mTerminated = true;
+ return nsBaseAppShell::Exit();
+}
diff --git a/widget/uikit/nsLookAndFeel.h b/widget/uikit/nsLookAndFeel.h
new file mode 100644
index 000000000..91c0c2d73
--- /dev/null
+++ b/widget/uikit/nsLookAndFeel.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "nsXPLookAndFeel.h"
+
+class nsLookAndFeel: public nsXPLookAndFeel
+{
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(const ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel);
+ virtual char16_t GetPasswordCharacterImpl()
+ {
+ // unicode value for the bullet character, used for password textfields.
+ return 0x2022;
+ }
+
+ static bool UseOverlayScrollbars()
+ {
+ return true;
+ }
+};
+
+#endif
diff --git a/widget/uikit/nsLookAndFeel.mm b/widget/uikit/nsLookAndFeel.mm
new file mode 100644
index 000000000..bb593eb51
--- /dev/null
+++ b/widget/uikit/nsLookAndFeel.mm
@@ -0,0 +1,401 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <UIKit/UIColor.h>
+#import <UIKit/UIInterface.h>
+
+#include "nsLookAndFeel.h"
+#include "nsStyleConsts.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel()
+{
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+}
+
+static nscolor GetColorFromUIColor(UIColor* aColor)
+{
+ CGColorRef cgColor = [aColor CGColor];
+ CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(cgColor));
+ const CGFloat* components = CGColorGetComponents(cgColor);
+ if (model == kCGColorSpaceModelRGB) {
+ return NS_RGB((unsigned int)(components[0] * 255.0),
+ (unsigned int)(components[1] * 255.0),
+ (unsigned int)(components[2] * 255.0));
+ }
+ else if (model == kCGColorSpaceModelMonochrome) {
+ unsigned int val = (unsigned int)(components[0] * 255.0);
+ return NS_RGBA(val, val, val,
+ (unsigned int)(components[1] * 255.0));
+ }
+ NS_NOTREACHED("Unhandled color space!");
+ return 0;
+}
+
+nsresult
+nsLookAndFeel::NativeGetColor(const ColorID aID, nscolor &aResult)
+{
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case eColorID_WindowBackground:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_WindowForeground:
+ aResult = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_WidgetBackground:
+ aResult = NS_RGB(0xdd,0xdd,0xdd);
+ break;
+ case eColorID_WidgetForeground:
+ aResult = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_WidgetSelectBackground:
+ aResult = NS_RGB(0x80,0x80,0x80);
+ break;
+ case eColorID_WidgetSelectForeground:
+ aResult = NS_RGB(0x00,0x00,0x80);
+ break;
+ case eColorID_Widget3DHighlight:
+ aResult = NS_RGB(0xa0,0xa0,0xa0);
+ break;
+ case eColorID_Widget3DShadow:
+ aResult = NS_RGB(0x40,0x40,0x40);
+ break;
+ case eColorID_TextBackground:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_TextForeground:
+ aResult = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_TextSelectBackground:
+ case eColorID_highlight: // CSS2 color
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_menuhover:
+ aResult = NS_RGB(0xee,0xee,0xee);
+ break;
+ case eColorID_TextSelectForeground:
+ case eColorID_highlighttext: // CSS2 color
+ case eColorID__moz_menuhovertext:
+ GetColor(eColorID_TextSelectBackground, aResult);
+ if (aResult == 0x000000)
+ aResult = NS_RGB(0xff,0xff,0xff);
+ else
+ aResult = NS_DONT_CHANGE_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aResult = NS_TRANSPARENT;
+ break;
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aResult = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aResult = NS_40PERCENT_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aResult = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_SpellCheckerUnderline:
+ aResult = NS_RGB(0xff, 0, 0);
+ break;
+
+ //
+ // css2 system colors http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ //
+ case eColorID_buttontext:
+ case eColorID__moz_buttonhovertext:
+ case eColorID_captiontext:
+ case eColorID_menutext:
+ case eColorID_infotext:
+ case eColorID__moz_menubartext:
+ case eColorID_windowtext:
+ aResult = GetColorFromUIColor([UIColor darkTextColor]);
+ break;
+ case eColorID_activecaption:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_activeborder:
+ aResult = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_appworkspace:
+ aResult = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ case eColorID_background:
+ aResult = NS_RGB(0x63,0x63,0xCE);
+ break;
+ case eColorID_buttonface:
+ case eColorID__moz_buttonhoverface:
+ aResult = NS_RGB(0xF0,0xF0,0xF0);
+ break;
+ case eColorID_buttonhighlight:
+ aResult = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ case eColorID_buttonshadow:
+ aResult = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID_graytext:
+ aResult = NS_RGB(0x44,0x44,0x44);
+ break;
+ case eColorID_inactiveborder:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_inactivecaption:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID_inactivecaptiontext:
+ aResult = NS_RGB(0x45,0x45,0x45);
+ break;
+ case eColorID_scrollbar:
+ aResult = NS_RGB(0,0,0); //XXX
+ break;
+ case eColorID_threeddarkshadow:
+ aResult = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID_threedshadow:
+ aResult = NS_RGB(0xE0,0xE0,0xE0);
+ break;
+ case eColorID_threedface:
+ aResult = NS_RGB(0xF0,0xF0,0xF0);
+ break;
+ case eColorID_threedhighlight:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_threedlightshadow:
+ aResult = NS_RGB(0xDA,0xDA,0xDA);
+ break;
+ case eColorID_menu:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_infobackground:
+ aResult = NS_RGB(0xFF,0xFF,0xC7);
+ break;
+ case eColorID_windowframe:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID_window:
+ case eColorID__moz_field:
+ case eColorID__moz_combobox:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID__moz_fieldtext:
+ case eColorID__moz_comboboxtext:
+ aResult = GetColorFromUIColor([UIColor darkTextColor]);
+ break;
+ case eColorID__moz_dialog:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_dialogtext:
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aResult = GetColorFromUIColor([UIColor darkTextColor]);
+ break;
+ case eColorID__moz_dragtargetzone:
+ case eColorID__moz_mac_chrome_active:
+ case eColorID__moz_mac_chrome_inactive:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_mac_focusring:
+ aResult = NS_RGB(0x3F,0x98,0xDD);
+ break;
+ case eColorID__moz_mac_menushadow:
+ aResult = NS_RGB(0xA3,0xA3,0xA3);
+ break;
+ case eColorID__moz_mac_menutextdisable:
+ aResult = NS_RGB(0x88,0x88,0x88);
+ break;
+ case eColorID__moz_mac_menutextselect:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_mac_disabledtoolbartext:
+ aResult = NS_RGB(0x3F,0x3F,0x3F);
+ break;
+ case eColorID__moz_mac_menuselect:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_buttondefault:
+ aResult = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ case eColorID__moz_mac_secondaryhighlight:
+ // For inactive list selection
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_eventreerow:
+ // Background color of even list rows.
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID__moz_oddtreerow:
+ // Background color of odd list rows.
+ aResult = NS_TRANSPARENT;
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ // There appears to be no available system defined color. HARDCODING to the appropriate color.
+ aResult = NS_RGB(0x14,0x4F,0xAE);
+ break;
+ default:
+ NS_WARNING("Someone asked nsILookAndFeel for a color I don't know about");
+ aResult = NS_RGB(0xff,0xff,0xff);
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return res;
+}
+
+NS_IMETHODIMP
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ aResult = 567;
+ break;
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by nsEventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case eIntID_SubmenuDelay:
+ aResult = 200;
+ break;
+ case eIntID_MenusCanOverlapOSBar:
+ // xul popups are not allowed to overlap the menubar.
+ aResult = 0;
+ break;
+ case eIntID_SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case eIntID_DragThresholdX:
+ case eIntID_DragThresholdY:
+ aResult = 4;
+ break;
+ case eIntID_ScrollArrowStyle:
+ aResult = eScrollArrow_None;
+ break;
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case eIntID_TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case eIntID_TreeScrollDelay:
+ aResult = 100;
+ break;
+ case eIntID_TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case eIntID_DWMCompositor:
+ case eIntID_WindowsClassic:
+ case eIntID_WindowsDefaultTheme:
+ case eIntID_TouchEnabled:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_MacGraphiteTheme:
+ aResult = 0;
+ break;
+ case eIntID_TabFocusModel:
+ aResult = 1; // default to just textboxes
+ break;
+ case eIntID_ScrollToClick:
+ aResult = 0;
+ break;
+ case eIntID_ChosenMenuItemsShouldBlink:
+ aResult = 1;
+ break;
+ case eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DOTTED;
+ break;
+ case eIntID_ContextMenuOffsetVertical:
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+NS_IMETHODIMP
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+bool
+nsLookAndFeel::GetFontImpl(FontID aID, nsString &aFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ // hack for now
+ if (aID == eFont_Window || aID == eFont_Document) {
+ aFontStyle.style = NS_FONT_STYLE_NORMAL;
+ aFontStyle.weight = NS_FONT_WEIGHT_NORMAL;
+ aFontStyle.stretch = NS_FONT_STRETCH_NORMAL;
+ aFontStyle.size = 14 * aDevPixPerCSSPixel;
+ aFontStyle.systemFont = true;
+
+ aFontName.AssignLiteral("sans-serif");
+ return true;
+ }
+
+ //TODO: implement more here?
+ return false;
+}
diff --git a/widget/uikit/nsScreenManager.h b/widget/uikit/nsScreenManager.h
new file mode 100644
index 000000000..1ff6a87ec
--- /dev/null
+++ b/widget/uikit/nsScreenManager.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsScreenManager_h_
+#define nsScreenManager_h_
+
+#include "nsBaseScreen.h"
+#include "nsIScreenManager.h"
+#include "nsCOMPtr.h"
+#include "nsRect.h"
+
+@class UIScreen;
+
+class UIKitScreen : public nsBaseScreen
+{
+public:
+ explicit UIKitScreen (UIScreen* screen);
+ ~UIKitScreen () {}
+
+ NS_IMETHOD GetId(uint32_t* outId) {
+ *outId = 0;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor);
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor)
+ {
+ return GetContentsScaleFactor(aScaleFactor);
+ }
+
+private:
+ UIScreen* mScreen;
+};
+
+class UIKitScreenManager : public nsIScreenManager
+{
+public:
+ UIKitScreenManager ();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSISCREENMANAGER
+
+ static LayoutDeviceIntRect GetBounds();
+
+private:
+ virtual ~UIKitScreenManager () {}
+ //TODO: support >1 screen, iPad supports external displays
+ nsCOMPtr<nsIScreen> mScreen;
+};
+
+#endif // nsScreenManager_h_
diff --git a/widget/uikit/nsScreenManager.mm b/widget/uikit/nsScreenManager.mm
new file mode 100644
index 000000000..601c911cd
--- /dev/null
+++ b/widget/uikit/nsScreenManager.mm
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <UIKit/UIScreen.h>
+
+#include "gfxPoint.h"
+#include "nsScreenManager.h"
+#include "nsAppShell.h"
+
+static LayoutDeviceIntRect gScreenBounds;
+static bool gScreenBoundsSet = false;
+
+UIKitScreen::UIKitScreen(UIScreen* aScreen)
+{
+ mScreen = [aScreen retain];
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ return GetRectDisplayPix(outX, outY, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetAvailRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ return GetAvailRectDisplayPix(outX, outY, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ nsIntRect rect = UIKitScreenManager::GetBounds();
+ *outX = rect.x;
+ *outY = rect.y;
+ *outWidth = rect.width;
+ *outHeight = rect.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetAvailRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ CGRect rect = [mScreen applicationFrame];
+ CGFloat scale = [mScreen scale];
+
+ *outX = rect.origin.x * scale;
+ *outY = rect.origin.y * scale;
+ *outWidth = rect.size.width * scale;
+ *outHeight = rect.size.height * scale;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetPixelDepth(int32_t *aPixelDepth)
+{
+ // Close enough.
+ *aPixelDepth = 24;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetColorDepth(int32_t *aColorDepth)
+{
+ return GetPixelDepth(aColorDepth);
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetContentsScaleFactor(double* aContentsScaleFactor)
+{
+ *aContentsScaleFactor = [mScreen scale];
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UIKitScreenManager, nsIScreenManager)
+
+UIKitScreenManager::UIKitScreenManager()
+: mScreen(new UIKitScreen([UIScreen mainScreen]))
+{
+}
+
+LayoutDeviceIntRect
+UIKitScreenManager::GetBounds()
+{
+ if (!gScreenBoundsSet) {
+ CGRect rect = [[UIScreen mainScreen] bounds];
+ CGFloat scale = [[UIScreen mainScreen] scale];
+ gScreenBounds.x = rect.origin.x * scale;
+ gScreenBounds.y = rect.origin.y * scale;
+ gScreenBounds.width = rect.size.width * scale;
+ gScreenBounds.height = rect.size.height * scale;
+ gScreenBoundsSet = true;
+ }
+ printf("UIKitScreenManager::GetBounds: %d %d %d %d\n",
+ gScreenBounds.x, gScreenBounds.y, gScreenBounds.width, gScreenBounds.height);
+ return gScreenBounds;
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::GetPrimaryScreen(nsIScreen** outScreen)
+{
+ NS_IF_ADDREF(*outScreen = mScreen.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::ScreenForRect(int32_t inLeft,
+ int32_t inTop,
+ int32_t inWidth,
+ int32_t inHeight,
+ nsIScreen** outScreen)
+{
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::ScreenForId(uint32_t id,
+ nsIScreen** outScreen)
+{
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::ScreenForNativeWidget(void* aWidget, nsIScreen** outScreen)
+{
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::GetNumberOfScreens(uint32_t* aNumberOfScreens)
+{
+ //TODO: support multiple screens
+ *aNumberOfScreens = 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::GetSystemDefaultScale(float* aScale)
+{
+ *aScale = [UIScreen mainScreen].scale;
+ return NS_OK;
+}
diff --git a/widget/uikit/nsWidgetFactory.mm b/widget/uikit/nsWidgetFactory.mm
new file mode 100644
index 000000000..9e4f028ff
--- /dev/null
+++ b/widget/uikit/nsWidgetFactory.mm
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFactory.h"
+#include "nsISupports.h"
+#include "nsIComponentManager.h"
+#include "mozilla/ModuleUtils.h"
+
+#include "nsWidgetsCID.h"
+
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsLookAndFeel.h"
+#include "nsScreenManager.h"
+#include "nsWindow.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(UIKitScreenManager)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
+
+#include "GfxInfo.h"
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+}
+}
+
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_CHILD_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor },
+ { &kNS_SCREENMANAGER_CID, false, nullptr, UIKitScreenManagerConstructor },
+ { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/uikit;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/childwindow/uikit;1", &kNS_CHILD_CID },
+ { "@mozilla.org/widget/appshell/uikit;1", &kNS_APPSHELL_CID },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { nullptr }
+};
+
+static void
+nsWidgetUIKitModuleDtor()
+{
+ nsLookAndFeel::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ nullptr,
+ nullptr,
+ nsAppShellInit,
+ nsWidgetUIKitModuleDtor
+};
+
+NSMODULE_DEFN(nsWidgetUIKitModule) = &kWidgetModule;
diff --git a/widget/uikit/nsWindow.h b/widget/uikit/nsWindow.h
new file mode 100644
index 000000000..cb18c0906
--- /dev/null
+++ b/widget/uikit/nsWindow.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSWINDOW_H_
+#define NSWINDOW_H_
+
+#include "nsBaseWidget.h"
+#include "gfxPoint.h"
+
+#include "nsTArray.h"
+
+@class UIWindow;
+@class UIView;
+@class ChildView;
+
+class nsWindow :
+ public nsBaseWidget
+{
+ typedef nsBaseWidget Inherited;
+
+public:
+ nsWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ //
+ // nsIWidget
+ //
+
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+ virtual void Destroy() override;
+ NS_IMETHOD Show(bool aState) override;
+ NS_IMETHOD Enable(bool aState) override {
+ return NS_OK;
+ }
+ virtual bool IsEnabled() const override {
+ return true;
+ }
+ virtual bool IsVisible() const override {
+ return mVisible;
+ }
+ NS_IMETHOD SetFocus(bool aState=false) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+
+ virtual void SetBackgroundColor(const nscolor &aColor) override;
+ virtual void* GetNativeData(uint32_t aDataType) override;
+
+ NS_IMETHOD Move(double aX, double aY) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ void EnteredFullScreen(bool aFullScreen);
+ NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ void ReportMoveEvent();
+ void ReportSizeEvent();
+ void ReportSizeModeEvent(nsSizeMode aMode);
+
+ CGFloat BackingScaleFactor();
+ void BackingScaleFactorChanged();
+ virtual float GetDPI() override {
+ //XXX: terrible
+ return 326.0f;
+ }
+ virtual double GetDefaultScaleInternal() override {
+ return BackingScaleFactor();
+ }
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override {
+ return NS_OK;
+ }
+
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+
+ void WillPaintWindow();
+ bool PaintWindow(LayoutDeviceIntRegion aRegion);
+
+ bool HasModalDescendents() { return false; }
+
+ //NS_IMETHOD NotifyIME(const IMENotification& aIMENotification) override;
+ NS_IMETHOD_(void) SetInputContext(
+ const InputContext& aContext,
+ const InputContextAction& aAction);
+ NS_IMETHOD_(InputContext) GetInputContext();
+ /*
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) override;
+ */
+
+protected:
+ virtual ~nsWindow();
+ void BringToFront();
+ nsWindow *FindTopLevel();
+ bool IsTopLevel();
+ nsresult GetCurrentOffset(uint32_t &aOffset, uint32_t &aLength);
+ nsresult DeleteRange(int aOffset, int aLen);
+
+ void TearDownView();
+
+ ChildView* mNativeView;
+ bool mVisible;
+ nsTArray<nsWindow*> mChildren;
+ nsWindow* mParent;
+ InputContext mInputContext;
+
+ void OnSizeChanged(const mozilla::gfx::IntSize& aSize);
+
+ static void DumpWindows();
+ static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
+ static void LogWindow(nsWindow *win, int index, int indent);
+};
+
+#endif /* NSWINDOW_H_ */
diff --git a/widget/uikit/nsWindow.mm b/widget/uikit/nsWindow.mm
new file mode 100644
index 000000000..874626237
--- /dev/null
+++ b/widget/uikit/nsWindow.mm
@@ -0,0 +1,862 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <UIKit/UIEvent.h>
+#import <UIKit/UIGraphics.h>
+#import <UIKit/UIInterface.h>
+#import <UIKit/UIScreen.h>
+#import <UIKit/UITapGestureRecognizer.h>
+#import <UIKit/UITouch.h>
+#import <UIKit/UIView.h>
+#import <UIKit/UIViewController.h>
+#import <UIKit/UIWindow.h>
+#import <QuartzCore/QuartzCore.h>
+
+#include <algorithm>
+
+#include "nsWindow.h"
+#include "nsScreenManager.h"
+#include "nsAppShell.h"
+
+#include "nsWidgetsCID.h"
+#include "nsGfxCIID.h"
+
+#include "gfxQuartzSurface.h"
+#include "gfxUtils.h"
+#include "gfxImageSurface.h"
+#include "gfxContext.h"
+#include "nsRegion.h"
+#include "Layers.h"
+#include "nsTArray.h"
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/Unused.h"
+
+#include "GeckoProfiler.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+
+#define ALOG(args...) fprintf(stderr, args); fprintf(stderr, "\n")
+
+static LayoutDeviceIntPoint
+UIKitPointsToDevPixels(CGPoint aPoint, CGFloat aBackingScale)
+{
+ return LayoutDeviceIntPoint(NSToIntRound(aPoint.x * aBackingScale),
+ NSToIntRound(aPoint.y * aBackingScale));
+}
+
+static CGRect
+DevPixelsToUIKitPoints(const LayoutDeviceIntRect& aRect, CGFloat aBackingScale)
+{
+ return CGRectMake((CGFloat)aRect.x / aBackingScale,
+ (CGFloat)aRect.y / aBackingScale,
+ (CGFloat)aRect.width / aBackingScale,
+ (CGFloat)aRect.height / aBackingScale);
+}
+
+// Used to retain a Cocoa object for the remainder of a method's execution.
+class nsAutoRetainUIKitObject {
+public:
+nsAutoRetainUIKitObject(id anObject)
+{
+ mObject = [anObject retain];
+}
+~nsAutoRetainUIKitObject()
+{
+ [mObject release];
+}
+private:
+ id mObject; // [STRONG]
+};
+
+@interface ChildView : UIView
+{
+@public
+ nsWindow* mGeckoChild; // weak ref
+ BOOL mWaitingForPaint;
+ CFMutableDictionaryRef mTouches;
+ int mNextTouchID;
+}
+// sets up our view, attaching it to its owning gecko view
+- (id)initWithFrame:(CGRect)inFrame geckoChild:(nsWindow*)inChild;
+// Our Gecko child was Destroy()ed
+- (void)widgetDestroyed;
+// Tear down this ChildView
+- (void)delayedTearDown;
+- (void)sendMouseEvent:(EventMessage) aType point:(LayoutDeviceIntPoint)aPoint widget:(nsWindow*)aWindow;
+- (void)handleTap:(UITapGestureRecognizer *)sender;
+- (BOOL)isUsingMainThreadOpenGL;
+- (void)drawUsingOpenGL;
+- (void)drawUsingOpenGLCallback;
+- (void)sendTouchEvent:(EventMessage) aType touches:(NSSet*)aTouches widget:(nsWindow*)aWindow;
+// Event handling (UIResponder)
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
+@end
+
+@implementation ChildView
++ (Class)layerClass {
+ return [CAEAGLLayer class];
+}
+
+- (id)initWithFrame:(CGRect)inFrame geckoChild:(nsWindow*)inChild
+{
+ self.multipleTouchEnabled = YES;
+ if ((self = [super initWithFrame:inFrame])) {
+ mGeckoChild = inChild;
+ }
+ ALOG("[ChildView[%p] initWithFrame:] (mGeckoChild = %p)", (void*)self, (void*)mGeckoChild);
+ self.opaque = YES;
+ self.alpha = 1.0;
+
+ UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
+ initWithTarget:self action:@selector(handleTap:)];
+ tapRecognizer.numberOfTapsRequired = 1;
+ [self addGestureRecognizer:tapRecognizer];
+
+ mTouches = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
+ mNextTouchID = 0;
+ return self;
+}
+
+- (void)widgetDestroyed
+{
+ mGeckoChild = nullptr;
+ CFRelease(mTouches);
+}
+
+- (void)delayedTearDown
+{
+ [self removeFromSuperview];
+ [self release];
+}
+
+- (void)sendMouseEvent:(EventMessage) aType point:(LayoutDeviceIntPoint)aPoint widget:(nsWindow*)aWindow
+{
+ WidgetMouseEvent event(true, aType, aWindow,
+ WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
+
+ event.mRefPoint = aPoint;
+ event.mClickCount = 1;
+ event.button = WidgetMouseEvent::eLeftButton;
+ event.mTime = PR_IntervalNow();
+ event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
+
+ nsEventStatus status;
+ aWindow->DispatchEvent(&event, status);
+}
+
+- (void)handleTap:(UITapGestureRecognizer *)sender
+{
+ if (sender.state == UIGestureRecognizerStateEnded) {
+ ALOG("[ChildView[%p] handleTap]", self);
+ LayoutDeviceIntPoint lp = UIKitPointsToDevPixels([sender locationInView:self], [self contentScaleFactor]);
+ [self sendMouseEvent:eMouseMove point:lp widget:mGeckoChild];
+ [self sendMouseEvent:eMouseDown point:lp widget:mGeckoChild];
+ [self sendMouseEvent:eMouseUp point:lp widget:mGeckoChild];
+ }
+}
+
+- (void)sendTouchEvent:(EventMessage) aType touches:(NSSet*)aTouches widget:(nsWindow*)aWindow
+{
+ WidgetTouchEvent event(true, aType, aWindow);
+ //XXX: I think nativeEvent.timestamp * 1000 is probably usable here but
+ // I don't care that much right now.
+ event.mTime = PR_IntervalNow();
+ event.mTouches.SetCapacity(aTouches.count);
+ for (UITouch* touch in aTouches) {
+ LayoutDeviceIntPoint loc = UIKitPointsToDevPixels([touch locationInView:self], [self contentScaleFactor]);
+ LayoutDeviceIntPoint radius = UIKitPointsToDevPixels([touch majorRadius], [touch majorRadius]);
+ void* value;
+ if (!CFDictionaryGetValueIfPresent(mTouches, touch, (const void**)&value)) {
+ // This shouldn't happen.
+ NS_ASSERTION(false, "Got a touch that we didn't know about");
+ continue;
+ }
+ int id = reinterpret_cast<int>(value);
+ RefPtr<Touch> t = new Touch(id, loc, radius, 0.0f, 1.0f);
+ event.mRefPoint = loc;
+ event.mTouches.AppendElement(t);
+ }
+ aWindow->DispatchInputEvent(&event);
+}
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ ALOG("[ChildView[%p] touchesBegan", self);
+ if (!mGeckoChild)
+ return;
+
+ for (UITouch* touch : touches) {
+ CFDictionaryAddValue(mTouches, touch, (void*)mNextTouchID);
+ mNextTouchID++;
+ }
+ [self sendTouchEvent:eTouchStart
+ touches:[event allTouches]
+ widget:mGeckoChild];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ ALOG("[ChildView[%p] touchesCancelled", self);
+ [self sendTouchEvent:eTouchCancel touches:touches widget:mGeckoChild];
+ for (UITouch* touch : touches) {
+ CFDictionaryRemoveValue(mTouches, touch);
+ }
+ if (CFDictionaryGetCount(mTouches) == 0) {
+ mNextTouchID = 0;
+ }
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ ALOG("[ChildView[%p] touchesEnded", self);
+ if (!mGeckoChild)
+ return;
+
+ [self sendTouchEvent:eTouchEnd touches:touches widget:mGeckoChild];
+ for (UITouch* touch : touches) {
+ CFDictionaryRemoveValue(mTouches, touch);
+ }
+ if (CFDictionaryGetCount(mTouches) == 0) {
+ mNextTouchID = 0;
+ }
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ ALOG("[ChildView[%p] touchesMoved", self);
+ if (!mGeckoChild)
+ return;
+
+ [self sendTouchEvent:eTouchMove
+ touches:[event allTouches]
+ widget:mGeckoChild];
+}
+
+- (void)setNeedsDisplayInRect:(CGRect)aRect
+{
+ if ([self isUsingMainThreadOpenGL]) {
+ // Draw without calling drawRect. This prevent us from
+ // needing to access the normal window buffer surface unnecessarily, so we
+ // waste less time synchronizing the two surfaces.
+ if (!mWaitingForPaint) {
+ mWaitingForPaint = YES;
+ // Use NSRunLoopCommonModes instead of the default NSDefaultRunLoopMode
+ // so that the timer also fires while a native menu is open.
+ [self performSelector:@selector(drawUsingOpenGLCallback)
+ withObject:nil
+ afterDelay:0
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+ }
+ }
+}
+
+- (BOOL)isUsingMainThreadOpenGL
+{
+ if (!mGeckoChild || ![self window])
+ return NO;
+
+ return mGeckoChild->GetLayerManager(nullptr)->GetBackendType() == mozilla::layers::LayersBackend::LAYERS_OPENGL;
+}
+
+- (void)drawUsingOpenGL
+{
+ ALOG("drawUsingOpenGL");
+ PROFILER_LABEL("ChildView", "drawUsingOpenGL",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ if (!mGeckoChild->IsVisible())
+ return;
+
+ mWaitingForPaint = NO;
+
+ LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();
+ LayoutDeviceIntRegion region(geckoBounds);
+
+ mGeckoChild->PaintWindow(region);
+}
+
+// Called asynchronously after setNeedsDisplay in order to avoid entering the
+// normal drawing machinery.
+- (void)drawUsingOpenGLCallback
+{
+ if (mWaitingForPaint) {
+ [self drawUsingOpenGL];
+ }
+}
+
+// The display system has told us that a portion of our view is dirty. Tell
+// gecko to paint it
+- (void)drawRect:(CGRect)aRect
+{
+ CGContextRef cgContext = UIGraphicsGetCurrentContext();
+ [self drawRect:aRect inContext:cgContext];
+}
+
+- (void)drawRect:(CGRect)aRect inContext:(CGContextRef)aContext
+{
+#ifdef DEBUG_UPDATE
+ LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();
+
+ fprintf (stderr, "---- Update[%p][%p] [%f %f %f %f] cgc: %p\n gecko bounds: [%d %d %d %d]\n",
+ self, mGeckoChild,
+ aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height, aContext,
+ geckoBounds.x, geckoBounds.y, geckoBounds.width, geckoBounds.height);
+
+ CGAffineTransform xform = CGContextGetCTM(aContext);
+ fprintf (stderr, " xform in: [%f %f %f %f %f %f]\n", xform.a, xform.b, xform.c, xform.d, xform.tx, xform.ty);
+#endif
+
+ if (true) {
+ // For Gecko-initiated repaints in OpenGL mode, drawUsingOpenGL is
+ // directly called from a delayed perform callback - without going through
+ // drawRect.
+ // Paints that come through here are triggered by something that Cocoa
+ // controls, for example by window resizing or window focus changes.
+
+ // Do GL composition and return.
+ [self drawUsingOpenGL];
+ return;
+ }
+ PROFILER_LABEL("ChildView", "drawRect",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ // The CGContext that drawRect supplies us with comes with a transform that
+ // scales one user space unit to one Cocoa point, which can consist of
+ // multiple dev pixels. But Gecko expects its supplied context to be scaled
+ // to device pixels, so we need to reverse the scaling.
+ double scale = mGeckoChild->BackingScaleFactor();
+ CGContextSaveGState(aContext);
+ CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale);
+
+ CGSize viewSize = [self bounds].size;
+ gfx::IntSize backingSize(viewSize.width * scale, viewSize.height * scale);
+
+ CGContextSaveGState(aContext);
+
+ LayoutDeviceIntRegion region =
+ LayoutDeviceIntRect(NSToIntRound(aRect.origin.x * scale),
+ NSToIntRound(aRect.origin.y * scale),
+ NSToIntRound(aRect.size.width * scale),
+ NSToIntRound(aRect.size.height * scale));
+
+ // Create Cairo objects.
+ RefPtr<gfxQuartzSurface> targetSurface;
+
+ RefPtr<gfxContext> targetContext;
+ if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(gfx::BackendType::CAIRO)) {
+ // This is dead code unless you mess with prefs, but keep it around for
+ // debugging.
+ targetSurface = new gfxQuartzSurface(aContext, backingSize);
+ targetSurface->SetAllowUseAsSource(false);
+ RefPtr<gfx::DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(targetSurface,
+ backingSize);
+ if (!dt || !dt->IsValid()) {
+ gfxDevCrash(mozilla::gfx::LogReason::InvalidContext) << "Window context problem 2 " << backingSize;
+ return;
+ }
+ dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr);
+ targetContext = gfxContext::CreateOrNull(dt);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("COREGRAPHICS is the only supported backend");
+ }
+ MOZ_ASSERT(targetContext); // already checked for valid draw targets above
+
+ // Set up the clip region.
+ targetContext->NewPath();
+ for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
+ const LayoutDeviceIntRect& r = iter.Get();
+ targetContext->Rectangle(gfxRect(r.x, r.y, r.width, r.height));
+ }
+ targetContext->Clip();
+
+ //nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ bool painted = false;
+ if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ nsBaseWidget::AutoLayerManagerSetup
+ setupLayerManager(mGeckoChild, targetContext, BufferMode::BUFFER_NONE);
+ painted = mGeckoChild->PaintWindow(region);
+ } else if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
+ // We only need this so that we actually get DidPaintWindow fired
+ painted = mGeckoChild->PaintWindow(region);
+ }
+
+ targetContext = nullptr;
+ targetSurface = nullptr;
+
+ CGContextRestoreGState(aContext);
+
+ // Undo the scale transform so that from now on the context is in
+ // CocoaPoints again.
+ CGContextRestoreGState(aContext);
+ if (!painted && [self isOpaque]) {
+ // Gecko refused to draw, but we've claimed to be opaque, so we have to
+ // draw something--fill with white.
+ CGContextSetRGBFillColor(aContext, 1, 1, 1, 1);
+ CGContextFillRect(aContext, aRect);
+ }
+
+#ifdef DEBUG_UPDATE
+ fprintf (stderr, "---- update done ----\n");
+
+#if 0
+ CGContextSetRGBStrokeColor (aContext,
+ ((((unsigned long)self) & 0xff)) / 255.0,
+ ((((unsigned long)self) & 0xff00) >> 8) / 255.0,
+ ((((unsigned long)self) & 0xff0000) >> 16) / 255.0,
+ 0.5);
+#endif
+ CGContextSetRGBStrokeColor(aContext, 1, 0, 0, 0.8);
+ CGContextSetLineWidth(aContext, 4.0);
+ CGContextStrokeRect(aContext, aRect);
+#endif
+}
+@end
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, Inherited)
+
+nsWindow::nsWindow()
+: mNativeView(nullptr),
+ mVisible(false),
+ mParent(nullptr)
+{
+}
+
+nsWindow::~nsWindow()
+{
+ [mNativeView widgetDestroyed]; // Safe if mNativeView is nil.
+ TearDownView(); // Safe if called twice.
+}
+
+void nsWindow::TearDownView()
+{
+ if (!mNativeView)
+ return;
+
+ [mNativeView performSelectorOnMainThread:@selector(delayedTearDown) withObject:nil waitUntilDone:false];
+ mNativeView = nil;
+}
+
+bool
+nsWindow::IsTopLevel()
+{
+ return mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_invisible;
+}
+
+//
+// nsIWidget
+//
+
+nsresult
+nsWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ ALOG("nsWindow[%p]::Create %p/%p [%d %d %d %d]", (void*)this, (void*)aParent, (void*)aNativeParent, aRect.x, aRect.y, aRect.width, aRect.height);
+ nsWindow* parent = (nsWindow*) aParent;
+ ChildView* nativeParent = (ChildView*)aNativeParent;
+
+ if (parent == nullptr && nativeParent)
+ parent = nativeParent->mGeckoChild;
+ if (parent && nativeParent == nullptr)
+ nativeParent = parent->mNativeView;
+
+ // for toplevel windows, bounds are fixed to full screen size
+ if (parent == nullptr) {
+ if (nsAppShell::gWindow == nil) {
+ mBounds = UIKitScreenManager::GetBounds();
+ } else {
+ CGRect cgRect = [nsAppShell::gWindow bounds];
+ mBounds.x = cgRect.origin.x;
+ mBounds.y = cgRect.origin.y;
+ mBounds.width = cgRect.size.width;
+ mBounds.height = cgRect.size.height;
+ }
+ } else {
+ mBounds = aRect;
+ }
+
+ ALOG("nsWindow[%p]::Create bounds: %d %d %d %d", (void*)this,
+ mBounds.x, mBounds.y, mBounds.width, mBounds.height);
+
+ // Set defaults which can be overriden from aInitData in BaseCreate
+ mWindowType = eWindowType_toplevel;
+ mBorderStyle = eBorderStyle_default;
+
+ Inherited::BaseCreate(aParent, aInitData);
+
+ NS_ASSERTION(IsTopLevel() || parent, "non top level window doesn't have a parent!");
+
+ mNativeView = [[ChildView alloc] initWithFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor()) geckoChild:this];
+ mNativeView.hidden = YES;
+
+ if (parent) {
+ parent->mChildren.AppendElement(this);
+ mParent = parent;
+ }
+
+ if (nativeParent) {
+ [nativeParent addSubview:mNativeView];
+ } else if (nsAppShell::gWindow) {
+ [nsAppShell::gWindow.rootViewController.view addSubview:mNativeView];
+ }
+ else {
+ [nsAppShell::gTopLevelViews addObject:mNativeView];
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindow::Destroy()
+{
+ for (uint32_t i = 0; i < mChildren.Length(); ++i) {
+ // why do we still have children?
+ mChildren[i]->SetParent(nullptr);
+ }
+
+ if (mParent)
+ mParent->mChildren.RemoveElement(this);
+
+ [mNativeView widgetDestroyed];
+
+ nsBaseWidget::Destroy();
+
+ //ReportDestroyEvent();
+
+ TearDownView();
+
+ nsBaseWidget::OnDestroy();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>& config)
+{
+ for (uint32_t i = 0; i < config.Length(); ++i) {
+ nsWindow *childWin = (nsWindow*) config[i].mChild.get();
+ childWin->Resize(config[i].mBounds.x,
+ config[i].mBounds.y,
+ config[i].mBounds.width,
+ config[i].mBounds.height,
+ false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Show(bool aState)
+{
+ if (aState != mVisible) {
+ mNativeView.hidden = aState ? NO : YES;
+ if (aState) {
+ UIView* parentView = mParent ? mParent->mNativeView : nsAppShell::gWindow.rootViewController.view;
+ [parentView bringSubviewToFront:mNativeView];
+ [mNativeView setNeedsDisplay];
+ }
+ mVisible = aState;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Move(double aX, double aY)
+{
+ if (!mNativeView || (mBounds.x == aX && mBounds.y == aY))
+ return NS_OK;
+
+ //XXX: handle this
+ // The point we have is in Gecko coordinates (origin top-left). Convert
+ // it to Cocoa ones (origin bottom-left).
+ mBounds.x = aX;
+ mBounds.y = aY;
+
+ mNativeView.frame = DevPixelsToUIKitPoints(mBounds, BackingScaleFactor());
+
+ if (mVisible)
+ [mNativeView setNeedsDisplay];
+
+ ReportMoveEvent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aX, double aY,
+ double aWidth, double aHeight,
+ bool aRepaint)
+{
+ BOOL isMoving = (mBounds.x != aX || mBounds.y != aY);
+ BOOL isResizing = (mBounds.width != aWidth || mBounds.height != aHeight);
+ if (!mNativeView || (!isMoving && !isResizing))
+ return NS_OK;
+
+ if (isMoving) {
+ mBounds.x = aX;
+ mBounds.y = aY;
+ }
+ if (isResizing) {
+ mBounds.width = aWidth;
+ mBounds.height = aHeight;
+ }
+
+ [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
+
+ if (mVisible && aRepaint)
+ [mNativeView setNeedsDisplay];
+
+ if (isMoving)
+ ReportMoveEvent();
+
+ if (isResizing)
+ ReportSizeEvent();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindow::Resize(double aWidth, double aHeight, bool aRepaint)
+{
+ if (!mNativeView || (mBounds.width == aWidth && mBounds.height == aHeight))
+ return NS_OK;
+
+ mBounds.width = aWidth;
+ mBounds.height = aHeight;
+
+ [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
+
+ if (mVisible && aRepaint)
+ [mNativeView setNeedsDisplay];
+
+ ReportSizeEvent();
+
+ return NS_OK;
+}
+
+void
+nsWindow::SetSizeMode(nsSizeMode aMode)
+{
+ if (aMode == static_cast<int32_t>(mSizeMode)) {
+ return;
+ }
+
+ mSizeMode = static_cast<nsSizeMode>(aMode);
+ if (aMode == nsSizeMode_Maximized || aMode == nsSizeMode_Fullscreen) {
+ // Resize to fill screen
+ nsBaseWidget::InfallibleMakeFullScreen(true);
+ }
+ ReportSizeModeEvent(aMode);
+}
+
+NS_IMETHODIMP
+nsWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ if (!mNativeView || !mVisible)
+ return NS_OK;
+
+ MOZ_RELEASE_ASSERT(GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_CLIENT,
+ "Shouldn't need to invalidate with accelerated OMTC layers!");
+
+
+ [mNativeView setNeedsLayout];
+ [mNativeView setNeedsDisplayInRect:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::SetFocus(bool aRaise)
+{
+ [[mNativeView window] makeKeyWindow];
+ [mNativeView becomeFirstResponder];
+ return NS_OK;
+}
+
+void nsWindow::WillPaintWindow()
+{
+ if (mWidgetListener) {
+ mWidgetListener->WillPaintWindow(this);
+ }
+}
+
+bool nsWindow::PaintWindow(LayoutDeviceIntRegion aRegion)
+{
+ if (!mWidgetListener)
+ return false;
+
+ bool returnValue = false;
+ returnValue = mWidgetListener->PaintWindow(this, aRegion);
+
+ if (mWidgetListener) {
+ mWidgetListener->DidPaintWindow();
+ }
+
+ return returnValue;
+}
+
+void nsWindow::ReportMoveEvent()
+{
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+}
+
+void nsWindow::ReportSizeModeEvent(nsSizeMode aMode)
+{
+ if (mWidgetListener) {
+ // This is terrible.
+ nsSizeMode theMode;
+ switch (aMode) {
+ case nsSizeMode_Maximized:
+ theMode = nsSizeMode_Maximized;
+ break;
+ case nsSizeMode_Fullscreen:
+ theMode = nsSizeMode_Fullscreen;
+ break;
+ default:
+ return;
+ }
+ mWidgetListener->SizeModeChanged(theMode);
+ }
+}
+
+void nsWindow::ReportSizeEvent()
+{
+ if (mWidgetListener) {
+ LayoutDeviceIntRect innerBounds = GetClientBounds();
+ mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
+ }
+}
+
+LayoutDeviceIntRect
+nsWindow::GetScreenBounds()
+{
+ return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
+}
+
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset()
+{
+ LayoutDeviceIntPoint offset(0, 0);
+ if (mParent) {
+ offset = mParent->WidgetToScreenOffset();
+ }
+
+ CGPoint temp = [mNativeView convertPoint:temp toView:nil];
+
+ if (!mParent && nsAppShell::gWindow) {
+ // convert to screen coords
+ temp = [nsAppShell::gWindow convertPoint:temp toWindow:nil];
+ }
+
+ offset.x += temp.x;
+ offset.y += temp.y;
+
+ return offset;
+}
+
+NS_IMETHODIMP
+nsWindow::DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus)
+{
+ aStatus = nsEventStatus_eIgnore;
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(aEvent->mWidget);
+
+ if (mWidgetListener)
+ aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ //TODO: actually show VKB
+ mInputContext = aContext;
+}
+
+NS_IMETHODIMP_(mozilla::widget::InputContext)
+nsWindow::GetInputContext()
+{
+ return mInputContext;
+}
+
+void
+nsWindow::SetBackgroundColor(const nscolor &aColor)
+{
+ mNativeView.backgroundColor = [UIColor colorWithRed:NS_GET_R(aColor)
+ green:NS_GET_G(aColor)
+ blue:NS_GET_B(aColor)
+ alpha:NS_GET_A(aColor)];
+}
+
+void* nsWindow::GetNativeData(uint32_t aDataType)
+{
+ void* retVal = nullptr;
+
+ switch (aDataType)
+ {
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_DISPLAY:
+ retVal = (void*)mNativeView;
+ break;
+
+ case NS_NATIVE_WINDOW:
+ retVal = [mNativeView window];
+ break;
+
+ case NS_NATIVE_GRAPHIC:
+ NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a UIKit child view!");
+ break;
+
+ case NS_NATIVE_OFFSETX:
+ retVal = 0;
+ break;
+
+ case NS_NATIVE_OFFSETY:
+ retVal = 0;
+ break;
+
+ case NS_NATIVE_PLUGIN_PORT:
+ // not implemented
+ break;
+
+ case NS_RAW_NATIVE_IME_CONTEXT:
+ retVal = GetPseudoIMEContext();
+ if (retVal) {
+ break;
+ }
+ retVal = NS_ONLY_ONE_NATIVE_IME_CONTEXT;
+ break;
+ }
+
+ return retVal;
+}
+
+CGFloat
+nsWindow::BackingScaleFactor()
+{
+ if (mNativeView) {
+ return [mNativeView contentScaleFactor];
+ }
+ return [UIScreen mainScreen].scale;
+}
+
+int32_t
+nsWindow::RoundsWidgetCoordinatesTo()
+{
+ if (BackingScaleFactor() == 2.0) {
+ return 2;
+ }
+ return 1;
+}
diff --git a/widget/windows/AudioSession.cpp b/widget/windows/AudioSession.cpp
new file mode 100644
index 000000000..11e5ba50c
--- /dev/null
+++ b/widget/windows/AudioSession.cpp
@@ -0,0 +1,453 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIXULAppInfo.h"
+
+//#include "AudioSession.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Attributes.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();
+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;
+
+ 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()
+{
+ 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(u"brandFullName",
+ getter_Copies(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;
+ }
+
+ hr = manager->GetAudioSessionControl(&GUID_NULL,
+ 0,
+ getter_AddRefs(mAudioSessionControl));
+
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ 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;
+ }
+
+ // Increments refcount of 'this'.
+ hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ mState = STARTED;
+
+ return NS_OK;
+}
+
+void
+AudioSession::StopInternal()
+{
+ if (mAudioSessionControl &&
+ (mState == STARTED || mState == STOPPED)) {
+ // Decrement refcount of 'this'
+ mAudioSessionControl->UnregisterAudioSessionNotification(this);
+ }
+ mAudioSessionControl = nullptr;
+}
+
+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);
+
+ 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;
+}
+
+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(this, &AudioSession::OnSessionDisconnectedInternal);
+ NS_DispatchToMainThread(runnable);
+ return S_OK;
+}
+
+nsresult
+AudioSession::OnSessionDisconnectedInternal()
+{
+ if (!mAudioSessionControl)
+ return NS_OK;
+
+ // 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);
+ 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 000000000..d7c1d3787
--- /dev/null
+++ b/widget/windows/AudioSession.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/. */
+
+#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 000000000..55d71d21e
--- /dev/null
+++ b/widget/windows/CompositorWidgetChild.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 "CompositorWidgetChild.h"
+#include "mozilla/Unused.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+#include "nsBaseWidget.h"
+#include "VsyncDispatcher.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetChild::CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver)
+ : mVsyncDispatcher(aVsyncDispatcher),
+ mVsyncObserver(aVsyncObserver)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+}
+
+CompositorWidgetChild::~CompositorWidgetChild()
+{
+}
+
+void
+CompositorWidgetChild::EnterPresentLock()
+{
+ Unused << SendEnterPresentLock();
+}
+
+void
+CompositorWidgetChild::LeavePresentLock()
+{
+ Unused << SendLeavePresentLock();
+}
+
+void
+CompositorWidgetChild::OnDestroyWindow()
+{
+}
+
+void
+CompositorWidgetChild::UpdateTransparency(nsTransparencyMode aMode)
+{
+ Unused << SendUpdateTransparency(static_cast<int32_t>(aMode));
+}
+
+void
+CompositorWidgetChild::ClearTransparentWindow()
+{
+ Unused << SendClearTransparentWindow();
+}
+
+HDC
+CompositorWidgetChild::GetTransparentDC() const
+{
+ // Not supported in out-of-process mode.
+ return nullptr;
+}
+
+bool
+CompositorWidgetChild::RecvObserveVsync()
+{
+ mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
+ return true;
+}
+
+bool
+CompositorWidgetChild::RecvUnobserveVsync()
+{
+ mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CompositorWidgetChild.h b/widget/windows/CompositorWidgetChild.h
new file mode 100644
index 000000000..ee6e90ca9
--- /dev/null
+++ b/widget/windows/CompositorWidgetChild.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 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 {
+
+class CompositorWidgetChild final
+ : public PCompositorWidgetChild,
+ public CompositorWidgetDelegate
+{
+public:
+ CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver);
+ ~CompositorWidgetChild() override;
+
+ void EnterPresentLock() override;
+ void LeavePresentLock() override;
+ void OnDestroyWindow() override;
+ void UpdateTransparency(nsTransparencyMode aMode) override;
+ void ClearTransparentWindow() override;
+ HDC GetTransparentDC() const override;
+
+ bool RecvObserveVsync() override;
+ bool RecvUnobserveVsync() override;
+
+private:
+ RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher;
+ RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver;
+};
+
+} // 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 000000000..c784ff72e
--- /dev/null
+++ b/widget/windows/CompositorWidgetParent.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 "CompositorWidgetParent.h"
+
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetParent::CompositorWidgetParent(const CompositorWidgetInitData& aInitData)
+ : WinCompositorWidget(aInitData)
+{
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+}
+
+CompositorWidgetParent::~CompositorWidgetParent()
+{
+}
+
+bool
+CompositorWidgetParent::RecvEnterPresentLock()
+{
+ EnterPresentLock();
+ return true;
+}
+
+bool
+CompositorWidgetParent::RecvLeavePresentLock()
+{
+ LeavePresentLock();
+ return true;
+}
+
+bool
+CompositorWidgetParent::RecvUpdateTransparency(const int32_t& aMode)
+{
+ UpdateTransparency(static_cast<nsTransparencyMode>(aMode));
+ return true;
+}
+
+bool
+CompositorWidgetParent::RecvClearTransparentWindow()
+{
+ ClearTransparentWindow();
+ return true;
+}
+
+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::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CompositorWidgetParent.h b/widget/windows/CompositorWidgetParent.h
new file mode 100644
index 000000000..a2f63bd72
--- /dev/null
+++ b/widget/windows/CompositorWidgetParent.h
@@ -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/. */
+
+#ifndef _widget_windows_WinCompositorWidget_h__
+#define _widget_windows_WinCompositorWidget_h__
+
+#include "WinCompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetParent final
+ : public PCompositorWidgetParent,
+ public WinCompositorWidget
+{
+public:
+ CompositorWidgetParent(const CompositorWidgetInitData& aInitData);
+ ~CompositorWidgetParent() override;
+
+ bool RecvEnterPresentLock() override;
+ bool RecvLeavePresentLock() override;
+ bool RecvUpdateTransparency(const int32_t& aMode) override;
+ bool RecvClearTransparentWindow() override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsIWidget* RealWidget() override;
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ RefPtr<VsyncObserver> GetVsyncObserver() const override;
+
+private:
+ RefPtr<VsyncObserver> mVsyncObserver;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _widget_windows_WinCompositorWidget_h__
diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp
new file mode 100644
index 000000000..0cbd323de
--- /dev/null
+++ b/widget/windows/GfxInfo.cpp
@@ -0,0 +1,1450 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <setupapi.h>
+#include "gfxConfig.h"
+#include "gfxWindowsPlatform.h"
+#include "GfxInfo.h"
+#include "GfxInfoWebGL.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 "nsPrintfCString.h"
+#include "jsapi.h"
+
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#include "nsICrashReporter.h"
+#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+static const uint32_t allWindowsVersions = 0xffffffff;
+
+GfxInfo::GfxInfo()
+ : mWindowsVersion(0),
+ mHasDualGPU(false),
+ mIsGPU2Active(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;
+}
+
+#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;
+}
+
+static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, nsAString& destString, int type)
+{
+ 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) {
+ dValue = dValue / 1024 / 1024;
+ destString.AppendInt(int32_t(dValue));
+ } 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) {
+ qValue = qValue / 1024 / 1024;
+ destString.AppendInt(int32_t(qValue));
+ } else {
+ retval = NS_ERROR_FAILURE;
+ }
+ break;
+ }
+ case REG_MULTI_SZ: {
+ // 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;
+ }
+
+ break;
+ }
+ }
+ RegCloseKey(key);
+
+ return retval;
+}
+
+// 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);
+ }
+ 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,
+ kWindowsXP = 0x50001,
+ kWindowsServer2003 = 0x50002,
+ kWindowsVista = 0x60000,
+ kWindows7 = 0x60001,
+ kWindows8 = 0x60002,
+ kWindows8_1 = 0x60003,
+ kWindows10 = 0xA0000
+};
+
+static int32_t
+WindowsOSVersion()
+{
+ static int32_t winVersion = UNINITIALIZED_VALUE;
+
+ OSVERSIONINFO vinfo;
+
+ if (winVersion == UNINITIALIZED_VALUE) {
+ 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
+ winVersion = kWindowsUnknown;
+ } else {
+ winVersion = int32_t(vinfo.dwMajorVersion << 16) + vinfo.dwMinorVersion;
+ }
+ }
+
+ return winVersion;
+}
+
+/* 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();
+
+ 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", &mWindowsVersion);
+ } else {
+ mWindowsVersion = WindowsOSVersion();
+ }
+
+ mDeviceKeyDebug = NS_LITERAL_STRING("PrimarySearch");
+
+ while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0)) {
+ if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
+ mDeviceKeyDebug = NS_LITERAL_STRING("NullSearch");
+ 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 (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, ArrayLength(DEVICE_KEY_PREFIX)-1) != 0)
+ return rv;
+
+ // chop off DEVICE_KEY_PREFIX
+ mDeviceKey = displayDevice.DeviceKey + ArrayLength(DEVICE_KEY_PREFIX)-1;
+
+ mDeviceID = displayDevice.DeviceID;
+ mDeviceString = 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).
+ if (mWindowsVersion == kWindows8 &&
+ mDeviceID.Length() == 0 &&
+ mDeviceString.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);
+ mDriverDate.AssignLiteral("01-01-1970");
+
+ // 0x1414 is Microsoft; 0xfefe is an invented (and unused) code
+ mDeviceID.AssignLiteral("PCI\\VEN_1414&DEV_FEFE&SUBSYS_00000000");
+ }
+ }
+
+ /* create a device information set composed of the current display device */
+ HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, mDeviceID.get(), nullptr,
+ DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
+
+ 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);
+ NS_NAMED_LITERAL_STRING(driverKeyPre, "System\\CurrentControlSet\\Control\\Class\\");
+ /* 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 = value;
+ } else {
+ // If the entry wasn't found, assume the worst (0.0.0.0).
+ mDriverVersion.AssignLiteral("0.0.0.0");
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result == ERROR_SUCCESS) {
+ mDriverDate = value;
+ } else {
+ // Again, assume the worst
+ mDriverDate.AssignLiteral("01-01-1970");
+ }
+ RegCloseKey(key);
+ break;
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+
+ mAdapterVendorID.AppendPrintf("0x%04x", ParseIDFromDeviceID(mDeviceID, "VEN_", 4));
+ mAdapterDeviceID.AppendPrintf("0x%04x", ParseIDFromDeviceID(mDeviceID, "&DEV_", 4));
+ mAdapterSubsysID.AppendPrintf("%08x", ParseIDFromDeviceID(mDeviceID, "&SUBSYS_", 8));
+
+ // We now check for second display adapter.
+
+ // 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;
+ uint32_t adapterVendorID2;
+ uint32_t adapterDeviceID2;
+
+ NS_NAMED_LITERAL_STRING(driverKeyPre, "System\\CurrentControlSet\\Control\\Class\\");
+ /* 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;
+ nsAutoString adapterVendorID2String;
+ nsAutoString adapterDeviceID2String;
+ adapterVendorID2 = ParseIDFromDeviceID(deviceID2, "VEN_", 4);
+ adapterVendorID2String.AppendPrintf("0x%04x", adapterVendorID2);
+ adapterDeviceID2 = ParseIDFromDeviceID(deviceID2, "&DEV_", 4);
+ adapterDeviceID2String.AppendPrintf("0x%04x", adapterDeviceID2);
+ if (mAdapterVendorID == adapterVendorID2String &&
+ mAdapterDeviceID == adapterDeviceID2String) {
+ 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) {
+ mHasDualGPU = true;
+ mDeviceString2 = value;
+ mDeviceID2 = deviceID2;
+ mDeviceKey2 = driverKey2;
+ mDriverVersion2 = driverVersion2;
+ mDriverDate2 = driverDate2;
+ mAdapterVendorID2.AppendPrintf("0x%04x", adapterVendorID2);
+ mAdapterDeviceID2.AppendPrintf("0x%04x", adapterDeviceID2);
+ mAdapterSubsysID2.AppendPrintf("%08x", ParseIDFromDeviceID(mDeviceID2, "&SUBSYS_", 8));
+ break;
+ }
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+ }
+
+ mHasDriverVersionMismatch = false;
+ if (mAdapterVendorID == GfxDriverInfo::GetDeviceVendor(VendorIntel)) {
+ // 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.
+ bool is64bitApp = sizeof(void*) == 8;
+ const char16_t *dllFileName = is64bitApp
+ ? u"igd10umd64.dll"
+ : u"igd10umd32.dll",
+ *dllFileName2 = is64bitApp
+ ? u"igd10iumd64.dll"
+ : u"igd10iumd32.dll";
+ nsString dllVersion, dllVersion2;
+ gfxWindowsPlatform::GetDLLVersion((char16_t*)dllFileName, dllVersion);
+ gfxWindowsPlatform::GetDLLVersion((char16_t*)dllFileName2, dllVersion2);
+
+ uint64_t dllNumericVersion = 0, dllNumericVersion2 = 0,
+ driverNumericVersion = 0, knownSafeMismatchVersion = 0;
+ ParseDriverVersion(dllVersion, &dllNumericVersion);
+ ParseDriverVersion(dllVersion2, &dllNumericVersion2);
+
+ ParseDriverVersion(mDriverVersion, &driverNumericVersion);
+ ParseDriverVersion(NS_LITERAL_STRING("9.17.10.0"), &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;
+ gfxWarningOnce() << "Mismatched driver versions between the registry " << mDriverVersion.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(dllVersion).get() << " and " << NS_ConvertUTF16toUTF8(dllVersion2).get();
+ }
+ }
+
+ const char *spoofedDriverVersionString = PR_GetEnv("MOZ_GFX_SPOOF_DRIVER_VERSION");
+ if (spoofedDriverVersionString) {
+ mDriverVersion.AssignASCII(spoofedDriverVersionString);
+ }
+
+ const char *spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_VENDOR_ID");
+ if (spoofedVendor) {
+ mAdapterVendorID.AssignASCII(spoofedVendor);
+ }
+
+ const char *spoofedDevice = PR_GetEnv("MOZ_GFX_SPOOF_DEVICE_ID");
+ if (spoofedDevice) {
+ mAdapterDeviceID.AssignASCII(spoofedDevice);
+ }
+
+ AddCrashReportAnnotations();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString & aAdapterDescription)
+{
+ aAdapterDescription = mDeviceString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString & aAdapterDescription)
+{
+ aAdapterDescription = mDeviceString2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(nsAString & aAdapterRAM)
+{
+ if (NS_FAILED(GetKeyValue(mDeviceKey.get(), L"HardwareInformation.qwMemorySize", aAdapterRAM, REG_QWORD)) || aAdapterRAM.Length() == 0) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey.get(), L"HardwareInformation.MemorySize", aAdapterRAM, REG_DWORD))) {
+ aAdapterRAM = L"Unknown";
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & aAdapterRAM)
+{
+ if (!mHasDualGPU) {
+ aAdapterRAM.Truncate();
+ } else if (NS_FAILED(GetKeyValue(mDeviceKey2.get(), L"HardwareInformation.qwMemorySize", aAdapterRAM, REG_QWORD)) || aAdapterRAM.Length() == 0) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey2.get(), L"HardwareInformation.MemorySize", aAdapterRAM, REG_DWORD))) {
+ aAdapterRAM = L"Unknown";
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString & aAdapterDriver)
+{
+ if (NS_FAILED(GetKeyValue(mDeviceKey.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(mDeviceKey2.get(), L"InstalledDisplayDrivers", aAdapterDriver, REG_MULTI_SZ))) {
+ aAdapterDriver = L"Unknown";
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion)
+{
+ aAdapterDriverVersion = mDriverVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString & aAdapterDriverDate)
+{
+ aAdapterDriverDate = mDriverDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion)
+{
+ aAdapterDriverVersion = mDriverVersion2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString & aAdapterDriverDate)
+{
+ aAdapterDriverDate = mDriverDate2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString & aAdapterVendorID)
+{
+ aAdapterVendorID = mAdapterVendorID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID)
+{
+ aAdapterVendorID = mAdapterVendorID2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID)
+{
+ aAdapterDeviceID = mAdapterDeviceID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString & aAdapterDeviceID)
+{
+ aAdapterDeviceID = mAdapterDeviceID2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString & aAdapterSubsysID)
+{
+ aAdapterSubsysID = mAdapterSubsysID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString & aAdapterSubsysID)
+{
+ aAdapterSubsysID = mAdapterSubsysID2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active)
+{
+ *aIsGPU2Active = mIsGPU2Active;
+ return NS_OK;
+}
+
+#if defined(MOZ_CRASHREPORTER)
+/* 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(NS_LITERAL_CSTRING("Cisco VPN\n"));
+ }
+}
+#endif
+
+void
+GfxInfo::AddCrashReportAnnotations()
+{
+#if defined(MOZ_CRASHREPORTER)
+ CheckForCiscoVPN();
+
+ if (mHasDriverVersionMismatch) {
+ CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("DriverVersionMismatch\n"));
+ }
+
+ 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(NS_LITERAL_CSTRING("AdapterVendorID"),
+ narrowVendorID);
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDeviceID"),
+ narrowDeviceID);
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDriverVersion"),
+ narrowDriverVersion);
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterSubsysID"),
+ narrowSubsysID);
+
+ /* Add an App Note for now so that we get the data immediately. These
+ * can go away after we store the above in the socorro db */
+ nsAutoCString note;
+ /* AppendPrintf only supports 32 character strings, mrghh. */
+ note.AppendLiteral("AdapterVendorID: ");
+ note.Append(narrowVendorID);
+ note.AppendLiteral(", AdapterDeviceID: ");
+ note.Append(narrowDeviceID);
+ note.AppendLiteral(", AdapterSubsysID: ");
+ note.Append(narrowSubsysID);
+ note.AppendLiteral(", AdapterDriverVersion: ");
+ note.Append(NS_LossyConvertUTF16toASCII(driverVersion));
+
+ if (vendorID == GfxDriverInfo::GetDeviceVendor(VendorAll)) {
+ /* if we didn't find a valid vendorID lets append the mDeviceID string to try to find out why */
+ note.AppendLiteral(", ");
+ LossyAppendUTF16toASCII(mDeviceID, note);
+ note.AppendLiteral(", ");
+ LossyAppendUTF16toASCII(mDeviceKeyDebug, note);
+ LossyAppendUTF16toASCII(mDeviceKeyDebug, note);
+ }
+ note.Append("\n");
+
+ if (mHasDualGPU) {
+ nsString deviceID2, vendorID2, subsysID2;
+ nsAutoString adapterDriverVersionString2;
+ nsCString narrowDeviceID2, narrowVendorID2, narrowSubsysID2;
+
+ 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);
+
+#endif
+}
+
+static OperatingSystem
+WindowsVersionToOperatingSystem(int32_t aWindowsVersion)
+{
+ switch(aWindowsVersion) {
+ case kWindowsXP:
+ return OperatingSystem::WindowsXP;
+ case kWindowsServer2003:
+ return OperatingSystem::WindowsServer2003;
+ case kWindowsVista:
+ return OperatingSystem::WindowsVista;
+ 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;
+ }
+}
+
+const nsTArray<GfxDriverInfo>&
+GfxInfo::GetGfxDriverInfo()
+{
+ if (!mDriverInfo->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
+ * blacklisting call.
+ */
+
+ /*
+ * NVIDIA entries
+ */
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN_OR_EQUAL, V(6,14,11,8745), "FEATURE_FAILURE_NV_XP", "nVidia driver > 187.45" );
+
+ /*
+ * 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::WindowsVista,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN_OR_EQUAL, V(8,15,11,8745),
+ "FEATURE_FAILURE_NV_VISTA_15", "nVidia driver > 187.45" );
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ 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::WindowsVista,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8,16,10,0000), V(8,16,11,8745),
+ "FEATURE_FAILURE_NV_VISTA_16", "nVidia driver > 187.45" );
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ 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::WindowsVista,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8,17,10,0000), V(8,17,11,8745),
+ "FEATURE_FAILURE_NV_VISTA_17", "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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(8,56,1,15), "FEATURE_FAILURE_AMD1", "8.56.1.15" );
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(8,56,1,15), "FEATURE_FAILURE_AMD2", "8.56.1.15" );
+
+ // Bug 1099252
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(8,783,2,2000), "FEATURE_FAILURE_BUG_1118695");
+
+ // Bug 1198815
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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.*.*" );
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows8,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ 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_ATI", "!= 8.982.*.*" );
+
+ /* 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_OGL_ATI_DIS" );
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_OGL_AMD_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, \
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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, \
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(devFamily), \
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, \
+ DRIVER_BUILD_ID_LESS_THAN, driverVer, ruleId )
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA500, 1006, "FEATURE_FAILURE_594877_1");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_594877_2");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA950, 1504, "FEATURE_FAILURE_594877_3");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA3150, 2124, "FEATURE_FAILURE_594877_4");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMAX3000, 1666, "FEATURE_FAILURE_594877_5");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelHDGraphicsToSandyBridge, 2202, "FEATURE_FAILURE_594877_6");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA500, 2026, "FEATURE_FAILURE_594877_7");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_594877_8");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA950, 1930, "FEATURE_FAILURE_594877_9");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA3150, 2117, "FEATURE_FAILURE_594877_10");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMAX3000, 1930, "FEATURE_FAILURE_594877_11");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_1180379");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA500, V(3,0,20,3200), "FEATURE_FAILURE_INTEL_1");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA900, V(6,14,10,4764), "FEATURE_FAILURE_INTEL_2");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA950, V(6,14,10,4926), "FEATURE_FAILURE_INTEL_3");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA3150, V(6,14,10,5134), "FEATURE_FAILURE_INTEL_4");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMAX3000, V(6,14,10,5218), "FEATURE_FAILURE_INTEL_5");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMAX4500HD, V(6,14,10,4969), "FEATURE_FAILURE_INTEL_6");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelHDGraphicsToSandyBridge, V(6,14,10,4969), "FEATURE_FAILURE_INTEL_7");
+
+ // StrechRect seems to suffer from precision issues which leads to artifacting
+ // during content drawing starting with at least version 6.14.10.5082
+ // and going until 6.14.10.5218. See bug 919454 and bug 949275 for more info.
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::WindowsXP,
+ const_cast<nsAString&>(GfxDriverInfo::GetDeviceVendor(VendorIntel)),
+ const_cast<GfxDeviceFamily*>(GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD)),
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_EXCLUSIVE, V(6,14,10,5076), V(6,14,10,5218), "FEATURE_FAILURE_INTEL_8", "6.14.10.5218");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA500, V(3,0,20,3200), "FEATURE_FAILURE_INTEL_9");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_10");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA950, V(7,14,10,1504), "FEATURE_FAILURE_INTEL_11");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA3150, V(7,14,10,1910), "FEATURE_FAILURE_INTEL_12");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMAX3000, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_13");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMAX4500HD, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_14");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelHDGraphicsToSandyBridge, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_15");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA500, V(5,0,0,2026), "FEATURE_FAILURE_INTEL_16");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_17");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA950, V(8,15,10,1930), "FEATURE_FAILURE_INTEL_18");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA3150, V(8,14,10,1972), "FEATURE_FAILURE_INTEL_19");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMAX3000, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_20");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMAX4500HD, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_21");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelHDGraphicsToSandyBridge, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_22");
+
+ // Bug 1074378
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel),
+ (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel),
+ (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelHD3000),
+ 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 HD Graphics on driver <= 8.15.10.2302
+ * See bug 806786
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2( OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelMobileHDGraphics),
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN_OR_EQUAL, V(8,15,10,2302), "FEATURE_FAILURE_BUG_804144" );
+
+ /* 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(Bug1207665),
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1207665_2" );
+
+ /* Disable D2D on AMD Catalyst 14.4 until 14.6
+ * See bug 984488
+ */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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+");
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ 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_2", "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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorMicrosoft), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
+ nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1336710");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::WindowsXP,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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");
+
+ ////////////////////////////////////
+ // WebGL
+
+ // Older than 5-15-2016
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_DX_INTEROP2, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "DX_INTEROP2_AMD_CRASH");
+ }
+ return *mDriverInfo;
+}
+
+nsresult
+GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ OperatingSystem os = WindowsVersionToOperatingSystem(mWindowsVersion);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ if (aOS)
+ *aOS = os;
+
+ // 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 (!adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorIntel), nsCaseInsensitiveStringComparator()) &&
+ !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), nsCaseInsensitiveStringComparator()) &&
+ !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorAMD), nsCaseInsensitiveStringComparator()) &&
+ !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorATI), nsCaseInsensitiveStringComparator()) &&
+ !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorMicrosoft), 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"))
+ {
+ 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;
+ }
+
+ // special-case the WinXP test slaves: they have out-of-date drivers, but we still want to
+ // whitelist them, actually we do know that this combination of device and driver version
+ // works well.
+ if (mWindowsVersion == kWindowsXP &&
+ adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), nsCaseInsensitiveStringComparator()) &&
+ adapterDeviceID.LowerCaseEqualsLiteral("0x0861") && // GeForce 9400
+ driverVersion == V(6,14,11,7756))
+ {
+ *aStatus = FEATURE_STATUS_OK;
+ return NS_OK;
+ }
+
+ // Windows Server 2003 should be just like Windows XP for present purpose, but still has a different version number.
+ // OTOH Windows Server 2008 R1 and R2 already have the same version numbers as Vista and Seven respectively
+ if (os == OperatingSystem::WindowsServer2003)
+ os = OperatingSystem::WindowsXP;
+
+ 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 (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;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+ JS::Rooted<JS::Value> screenWidth(aCx, JS::Int32Value(mode.dmPelsWidth));
+ JS_SetProperty(aCx, obj, "screenWidth", screenWidth);
+
+ JS::Rooted<JS::Value> screenHeight(aCx, JS::Int32Value(mode.dmPelsHeight));
+ JS_SetProperty(aCx, obj, "screenHeight", screenHeight);
+
+ JS::Rooted<JS::Value> refreshRate(aCx, JS::Int32Value(mode.dmDisplayFrequency));
+ JS_SetProperty(aCx, obj, "refreshRate", refreshRate);
+
+ JS::Rooted<JS::Value> pseudoDisplay(aCx,
+ JS::BooleanValue(!!(device.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)));
+ 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::FeatureStatus d3d11 = gfxConfig::GetValue(Feature::D3D11_COMPOSITING);
+ if (!InitFeatureObject(aCx, aObj, "d3d11", FEATURE_DIRECT3D_11_ANGLE,
+ Some(d3d11), &obj)) {
+ return;
+ }
+ if (d3d11 == 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 blacklisted = false;
+ if (nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo()) {
+ int32_t status;
+ nsCString discardFailureId;
+ if (SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, discardFailureId, &status))) {
+ blacklisted = (status != nsIGfxInfo::FEATURE_STATUS_OK);
+ }
+ }
+
+ val = JS::BooleanValue(blacklisted);
+ JS_SetProperty(aCx, obj, "blacklisted", val);
+ }
+
+ gfx::FeatureStatus d2d = gfxConfig::GetValue(Feature::DIRECT2D);
+ if (!InitFeatureObject(aCx, aObj, "d2d", nsIGfxInfo::FEATURE_DIRECT2D,
+ Some(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 = aVendorID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString & aDeviceID)
+{
+ mAdapterDeviceID = aDeviceID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString & aDriverVersion)
+{
+ mDriverVersion = aDriverVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion)
+{
+ mWindowsVersion = aVersion;
+ return NS_OK;
+}
+
+#endif
diff --git a/widget/windows/GfxInfo.h b/widget/windows/GfxInfo.h
new file mode 100644
index 000000000..c7aa859a6
--- /dev/null
+++ b/widget/windows/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"
+
+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 GetCleartypeParameters(nsAString & aCleartypeParams) override;
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active) override;
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+ virtual nsresult Init() override;
+
+ virtual uint32_t OperatingSystemVersion() override { return mWindowsVersion; }
+
+ 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:
+
+ void AddCrashReportAnnotations();
+
+ nsString mDeviceString;
+ nsString mDeviceID;
+ nsString mDriverVersion;
+ nsString mDriverDate;
+ nsString mDeviceKey;
+ nsString mDeviceKeyDebug;
+ nsString mAdapterVendorID;
+ nsString mAdapterDeviceID;
+ nsString mAdapterSubsysID;
+ nsString mDeviceString2;
+ nsString mDriverVersion2;
+ nsString mDeviceID2;
+ nsString mDriverDate2;
+ nsString mDeviceKey2;
+ nsString mAdapterVendorID2;
+ nsString mAdapterDeviceID2;
+ nsString mAdapterSubsysID2;
+ uint32_t mWindowsVersion;
+ bool mHasDualGPU;
+ bool mIsGPU2Active;
+ bool mHasDriverVersionMismatch;
+};
+
+} // 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 000000000..4fa76258b
--- /dev/null
+++ b/widget/windows/IEnumFE.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 000000000..d3875865a
--- /dev/null
+++ b/widget/windows/IEnumFE.h
@@ -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/. */
+
+#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:
+ 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 000000000..9bd7d2e7a
--- /dev/null
+++ b/widget/windows/IMMHandler.cpp
@@ -0,0 +1,2910 @@
+/* -*- 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 "WinUtils.h"
+#include "KeyboardLayout.h"
+#include <algorithm>
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/TextEvents.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:
+ 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:
+ 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:
+ GetWritingModeName(const WritingMode& aWritingMode)
+ {
+ if (!aWritingMode.IsVertical()) {
+ Assign("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ Assign("Vertical (LR)");
+ return;
+ }
+ Assign("Vertical (RL)");
+ }
+ virtual ~GetWritingModeName() {}
+};
+
+class GetReconvertStringLog : public nsAutoCString
+{
+public:
+ 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;
+bool IMMHandler::sNativeCaretIsCreatedForPlugin = 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::IsComposingOnPlugin()
+{
+ return gIMMHandler && gIMMHandler->mIsComposingOnPlugin;
+}
+
+// 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::IsJapanist2003Active()
+{
+ return sIMEName.EqualsLiteral("Japanist 2003");
+}
+
+// static
+bool
+IMMHandler::IsGoogleJapaneseInputActive()
+{
+ // NOTE: Even on Windows for en-US, the name of Google Japanese Input is
+ // written in Japanese.
+ return sIMEName.Equals(L"Google \x65E5\x672C\x8A9E\x5165\x529B "
+ L"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB");
+}
+
+// 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, wwc(sIMEName.BeginWriting()),
+ 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()) {
+ sIMEName =
+ Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as");
+ }
+
+ // 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
+nsIMEUpdatePreference
+IMMHandler::GetIMEUpdatePreference()
+{
+ return nsIMEUpdatePreference(
+ nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE |
+ nsIMEUpdatePreference::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)
+ , mIsComposingOnPlugin(false)
+ , mNativeCaretIsCreated(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, "
+ "sNativeCaretIsCreatedForPlugin=%s",
+ GetBoolName(aFocus), aWindow, GetBoolName(sHasFocus),
+ GetBoolName(IsComposingWindow(aWindow)),
+ GetBoolName(aWindow->Destroyed()),
+ GetBoolName(sNativeCaretIsCreatedForPlugin)));
+
+ if (!aFocus) {
+ if (sNativeCaretIsCreatedForPlugin) {
+ ::DestroyCaret();
+ sNativeCaretIsCreatedForPlugin = false;
+ }
+ if (IsComposingWindow(aWindow) && aWindow->Destroyed()) {
+ CancelComposition(aWindow);
+ }
+ }
+ if (gIMMHandler) {
+ gIMMHandler->mSelection.Clear();
+ }
+ sHasFocus = aFocus;
+}
+
+// static
+void
+IMMHandler::OnUpdateComposition(nsWindow* aWindow)
+{
+ if (!gIMMHandler) {
+ return;
+ }
+
+ if (aWindow->PluginHasFocus()) {
+ 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.
+
+ // When a plug-in has focus, we should dispatch the IME events to
+ // the plug-in at first.
+ if (aWindow->PluginHasFocus()) {
+ bool ret = false;
+ if (ProcessMessageForPlugin(aWindow, msg, wParam, lParam, ret, aResult)) {
+ return ret;
+ }
+ }
+
+ 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;
+ };
+}
+
+// static
+bool
+IMMHandler::ProcessMessageForPlugin(nsWindow* aWindow,
+ UINT msg,
+ WPARAM& wParam,
+ LPARAM& lParam,
+ bool& aRet,
+ MSGResult& aResult)
+{
+ aResult.mResult = 0;
+ aResult.mConsumed = false;
+ switch (msg) {
+ case WM_INPUTLANGCHANGEREQUEST:
+ case WM_INPUTLANGCHANGE:
+ aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
+ aRet = ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
+ return true;
+ case WM_IME_CHAR:
+ EnsureHandlerInstance();
+ aRet = gIMMHandler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult);
+ return true;
+ case WM_IME_SETCONTEXT:
+ aRet = OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult);
+ return true;
+ case WM_CHAR:
+ if (!gIMMHandler) {
+ return true;
+ }
+ aRet = gIMMHandler->OnCharOnPlugin(aWindow, wParam, lParam, aResult);
+ return true;
+ case WM_IME_COMPOSITIONFULL:
+ case WM_IME_CONTROL:
+ case WM_IME_KEYDOWN:
+ case WM_IME_KEYUP:
+ case WM_IME_SELECT:
+ aResult.mConsumed =
+ aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
+ aRet = true;
+ return true;
+ case WM_IME_REQUEST:
+ // Our plugin implementation is alwasy OOP. So WM_IME_REQUEST doesn't
+ // allow that parameter is pointer and shouldn't handle into Gecko.
+ aRet = false;
+ return true;
+ }
+ 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;
+}
+
+/****************************************************************************
+ * message handlers for plug-in
+ ****************************************************************************/
+
+void
+IMMHandler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
+ aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
+ mIsComposingOnPlugin = true;
+ mDispatcher = GetTextEventDispatcherFor(aWindow);
+ mComposingWindow = aWindow;
+ IMEContext context(aWindow);
+ SetIMERelatedWindowsPosOnPlugin(aWindow, context);
+ // On widnowless plugin, we should assume that the focused editor is always
+ // in horizontal writing mode.
+ AdjustCompositionFont(aWindow, context, WritingMode());
+}
+
+void
+IMMHandler::OnIMECompositionOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, "
+ "mIsComposingOnPlugin=%s, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, "
+ "GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s",
+ aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposingOnPlugin),
+ GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
+ GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
+ GetBoolName(lParam & GCS_CURSORPOS)));
+ // We should end composition if there is a committed string.
+ if (IS_COMMITTING_LPARAM(lParam)) {
+ mIsComposingOnPlugin = false;
+ mComposingWindow = nullptr;
+ mDispatcher = nullptr;
+ return;
+ }
+ // Continue composition if there is still a string being composed.
+ if (IS_COMPOSING_LPARAM(lParam)) {
+ mIsComposingOnPlugin = true;
+ mDispatcher = GetTextEventDispatcherFor(aWindow);
+ mComposingWindow = aWindow;
+ IMEContext context(aWindow);
+ SetIMERelatedWindowsPosOnPlugin(aWindow, context);
+ }
+}
+
+void
+IMMHandler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
+ aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
+
+ mIsComposingOnPlugin = false;
+ mComposingWindow = nullptr;
+ mDispatcher = nullptr;
+
+ if (mNativeCaretIsCreated) {
+ ::DestroyCaret();
+ mNativeCaretIsCreated = false;
+ }
+}
+
+bool
+IMMHandler::OnIMECharOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x",
+ aWindow->GetWindowHandle(), wParam, lParam));
+
+ aResult.mConsumed =
+ aWindow->DispatchPluginEvent(WM_IME_CHAR, wParam, lParam, true);
+
+ if (!aResult.mConsumed) {
+ // Record the WM_CHAR messages which are going to be coming.
+ EnsureHandlerInstance();
+ EnqueueIMECharRecords(wParam, lParam);
+ }
+ return true;
+}
+
+// static
+bool
+IMMHandler::OnIMESetContextOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x",
+ aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
+
+ // If the IME context becomes active on a plug-in, we should commit
+ // our composition. And also we should cancel the composition on new
+ // window. Note that if IsTopLevelWindowOfComposition(aWindow) returns
+ // true, we should ignore the message here, see the comment in
+ // OnIMESetContext() for the detail.
+ if (wParam && gIMMHandler && !IsTopLevelWindowOfComposition(aWindow)) {
+ if (gIMMHandler->CommitCompositionOnPreviousWindow(aWindow)) {
+ CancelComposition(aWindow);
+ }
+ }
+
+ // Dispatch message to the plug-in.
+ // XXX When a windowless plug-in gets focus, we should send
+ // WM_IME_SETCONTEXT
+ aWindow->DispatchPluginEvent(WM_IME_SETCONTEXT, wParam, lParam, false);
+
+ // We should send WM_IME_SETCONTEXT to the DefWndProc here. It shouldn't
+ // be received on ancestor windows, see OnIMESetContext() for the detail.
+ aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
+ WM_IME_SETCONTEXT, wParam, lParam);
+
+ // Don't synchronously dispatch the pending events when we receive
+ // WM_IME_SETCONTEXT because we get it during plugin destruction.
+ // (bug 491848)
+ aResult.mConsumed = true;
+ return true;
+}
+
+bool
+IMMHandler::OnCharOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult)
+{
+ NS_WARNING("OnCharOnPlugin");
+ if (mIsComposing) {
+ aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
+ return true;
+ }
+
+ // We should never consume char message on windowless plugin.
+ aResult.mConsumed = false;
+ if (IsIMECharRecordsEmpty()) {
+ return false;
+ }
+
+ WPARAM recWParam;
+ LPARAM recLParam;
+ DequeueIMECharRecords(recWParam, recLParam);
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnCharOnPlugin, 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();
+ }
+ // WM_CHAR on plug-in is always handled by nsWindow.
+ return false;
+}
+
+/****************************************************************************
+ * others
+ ****************************************************************************/
+
+TextEventDispatcher*
+IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow)
+{
+ return aWindow == mComposingWindow && mDispatcher ?
+ mDispatcher.get() : aWindow->GetTextEventDispatcher();
+}
+
+void
+IMMHandler::HandleStartComposition(nsWindow* aWindow,
+ const IMEContext& aContext)
+{
+ NS_PRECONDITION(!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() : ""));
+
+ if (mNativeCaretIsCreated) {
+ ::DestroyCaret();
+ mNativeCaretIsCreated = false;
+ }
+
+ 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 editorRect(true, eQueryEditorRect, aWindow);
+ aWindow->InitEvent(editorRect);
+ DispatchEvent(aWindow, editorRect);
+ if (NS_WARN_IF(!editorRect.mSucceeded)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleQueryCharPosition, eQueryEditorRect failed"));
+ ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
+ } else {
+ LayoutDeviceIntRect editorRectInWindow = editorRect.mReply.mRect;
+ nsWindow* window = editorRect.mReply.mFocusedWidget ?
+ static_cast<nsWindow*>(editorRect.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 textContent(true, eQueryTextContent, aWindow);
+ textContent.InitForQueryTextContent(0, UINT32_MAX);
+ aWindow->InitEvent(textContent, &point);
+ DispatchEvent(aWindow, textContent);
+ if (!textContent.mSucceeded) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to eQueryTextContent failure"));
+ return false;
+ }
+
+ nsAutoString str(textContent.mReply.mString);
+ if (targetOffset > 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 nsAFlatString& 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 charRect(true, eQueryTextRect, aWindow);
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ charRect.InitForQueryTextRect(aOffset, 1, options);
+ aWindow->InitEvent(charRect, &point);
+ DispatchEvent(aWindow, charRect);
+ if (charRect.mSucceeded) {
+ aCharRect = charRect.mReply.mRect;
+ if (aWritingMode) {
+ *aWritingMode = charRect.GetWritingMode();
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Debug,
+ ("GetCharacterRectOfSelectedTextAt, Succeeded, aOffset=%u, "
+ "aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
+ "charRect.GetWritingMode()=%s",
+ aOffset, aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height,
+ GetWritingModeName(charRect.GetWritingMode()).get()));
+ return true;
+ }
+ }
+
+ return GetCaretRect(aWindow, aCharRect, aWritingMode);
+}
+
+bool
+IMMHandler::GetCaretRect(nsWindow* aWindow,
+ LayoutDeviceIntRect& aCaretRect,
+ WritingMode* aWritingMode)
+{
+ LayoutDeviceIntPoint point(0, 0);
+
+ WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWindow);
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ caretRect.InitForQueryCaretRect(0, options);
+ aWindow->InitEvent(caretRect, &point);
+ DispatchEvent(aWindow, caretRect);
+ if (!caretRect.mSucceeded) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("GetCaretRect, FAILED, due to eQueryCaretRect failure"));
+ return false;
+ }
+ aCaretRect = caretRect.mReply.mRect;
+ if (aWritingMode) {
+ *aWritingMode = caretRect.GetWritingMode();
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("GetCaretRect, SUCCEEDED, "
+ "aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
+ "caretRect.GetWritingMode()=%s",
+ aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height,
+ GetWritingModeName(caretRect.GetWritingMode()).get()));
+ return true;
+}
+
+bool
+IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow,
+ const IMEContext& aContext)
+{
+ LayoutDeviceIntRect r;
+ // Get first character rect of current a normal selected text or a composing
+ // string.
+ WritingMode writingMode;
+ bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r, &writingMode);
+ NS_ENSURE_TRUE(ret, false);
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ LayoutDeviceIntRect firstSelectedCharRect;
+ ResolveIMECaretPos(toplevelWindow, r, 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.
+ LayoutDeviceIntRect caretRect(firstSelectedCharRect);
+ if (GetCaretRect(aWindow, r)) {
+ ResolveIMECaretPos(toplevelWindow, r, aWindow, caretRect);
+ } else {
+ NS_WARNING("failed to get caret rect");
+ caretRect.width = 1;
+ }
+ if (!mNativeCaretIsCreated) {
+ mNativeCaretIsCreated = ::CreateCaret(aWindow->GetWindowHandle(), nullptr,
+ caretRect.width, caretRect.height);
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPos, mNativeCaretIsCreated=%s, "
+ "width=%ld, height=%ld",
+ GetBoolName(mNativeCaretIsCreated), caretRect.width, caretRect.height));
+ }
+ ::SetCaretPos(caretRect.x, caretRect.y);
+
+ 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::SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow,
+ const IMEContext& aContext)
+{
+ WidgetQueryContentEvent editorRectEvent(true, eQueryEditorRect, aWindow);
+ aWindow->InitEvent(editorRectEvent);
+ DispatchEvent(aWindow, editorRectEvent);
+ if (!editorRectEvent.mSucceeded) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPosOnPlugin, "
+ "FAILED, due to eQueryEditorRect failure"));
+ return;
+ }
+
+ // Clip the plugin rect by the client rect of the window because composition
+ // window needs to be specified the position in the client area.
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ LayoutDeviceIntRect pluginRectInScreen =
+ editorRectEvent.mReply.mRect + toplevelWindow->WidgetToScreenOffset();
+ LayoutDeviceIntRect winRectInScreen = aWindow->GetClientBounds();
+ // composition window cannot be positioned on the edge of client area.
+ winRectInScreen.width--;
+ winRectInScreen.height--;
+ LayoutDeviceIntRect clippedPluginRect;
+ clippedPluginRect.x =
+ std::min(std::max(pluginRectInScreen.x, winRectInScreen.x),
+ winRectInScreen.XMost());
+ clippedPluginRect.y =
+ std::min(std::max(pluginRectInScreen.y, winRectInScreen.y),
+ winRectInScreen.YMost());
+ int32_t xMost = std::min(pluginRectInScreen.XMost(), winRectInScreen.XMost());
+ int32_t yMost = std::min(pluginRectInScreen.YMost(), winRectInScreen.YMost());
+ clippedPluginRect.width = std::max(0, xMost - clippedPluginRect.x);
+ clippedPluginRect.height = std::max(0, yMost - clippedPluginRect.y);
+ clippedPluginRect -= aWindow->WidgetToScreenOffset();
+
+ // Cover the plugin with native caret. This prevents IME's window and plugin
+ // overlap.
+ if (mNativeCaretIsCreated) {
+ ::DestroyCaret();
+ }
+ mNativeCaretIsCreated =
+ ::CreateCaret(aWindow->GetWindowHandle(), nullptr,
+ clippedPluginRect.width, clippedPluginRect.height);
+ ::SetCaretPos(clippedPluginRect.x, clippedPluginRect.y);
+
+ // Set the composition window to bottom-left of the clipped plugin.
+ // As far as we know, there is no IME for RTL language. Therefore, this code
+ // must not need to take care of RTL environment.
+ COMPOSITIONFORM compForm;
+ compForm.dwStyle = CFS_POINT;
+ compForm.ptCurrentPos.x = clippedPluginRect.BottomLeft().x;
+ compForm.ptCurrentPos.y = clippedPluginRect.BottomLeft().y;
+ if (!::ImmSetCompositionWindow(aContext.get(), &compForm)) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPosOnPlugin, "
+ "FAILED, due to ::ImmSetCompositionWindow() failure"));
+ return;
+ }
+}
+
+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 =
+ Preferences::GetString("intl.imm.composition_font");
+
+ // 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";
+ sCompositionFontForJapanist2003 =
+ Preferences::GetString(kCompositionFontForJapanist2003);
+ // 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 (!aWindow->PluginHasFocus() &&
+ 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 WidgetMouseEventBase::eLeftButton:
+ button = IMEMOUSE_LDOWN;
+ break;
+ case WidgetMouseEventBase::eMiddleButton:
+ button = IMEMOUSE_MDOWN;
+ break;
+ case WidgetMouseEventBase::eRightButton:
+ 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;
+ }
+}
+
+// static
+void
+IMMHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm)
+{
+ // Hack for ATOK 2011 - 2016 (Japanese IME). They refer native caret
+ // position at deciding candidate window position. Note that we cannot
+ // check active IME since TIPs are wrapped and hidden by CUAS.
+ if (aWindow->PluginHasFocus()) {
+ // We cannot retrieve proper character height from plugin. Therefore,
+ // we should assume that the caret height is always 20px since if less than
+ // this height, candidate window may overlap with composition string when
+ // there is no enough space under composition string to show candidate
+ // window.
+ static const int32_t kCaretHeight = 20;
+ if (sNativeCaretIsCreatedForPlugin) {
+ ::DestroyCaret();
+ }
+ sNativeCaretIsCreatedForPlugin =
+ ::CreateCaret(aWindow->GetWindowHandle(), nullptr, 0, kCaretHeight);
+ if (sNativeCaretIsCreatedForPlugin) {
+ LayoutDeviceIntPoint caretPosition(aForm->ptCurrentPos.x,
+ aForm->ptCurrentPos.y - kCaretHeight);
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ if (toplevelWindow && toplevelWindow != aWindow) {
+ caretPosition += toplevelWindow->WidgetToScreenOffset();
+ caretPosition -= aWindow->WidgetToScreenOffset();
+ }
+ ::SetCaretPos(caretPosition.x, caretPosition.y);
+ }
+ }
+ IMEContext context(aWindow);
+ ::ImmSetCandidateWindow(context.get(), aForm);
+}
+
+// staitc
+void
+IMMHandler::DefaultProcOfPluginEvent(nsWindow* aWindow, const NPEvent* aEvent)
+{
+ switch (aEvent->event) {
+ case WM_IME_STARTCOMPOSITION:
+ EnsureHandlerInstance();
+ gIMMHandler->OnIMEStartCompositionOnPlugin(aWindow, aEvent->wParam,
+ aEvent->lParam);
+ break;
+
+ case WM_IME_COMPOSITION:
+ if (gIMMHandler) {
+ gIMMHandler->OnIMECompositionOnPlugin(aWindow, aEvent->wParam,
+ aEvent->lParam);
+ }
+ break;
+
+ case WM_IME_ENDCOMPOSITION:
+ if (gIMMHandler) {
+ gIMMHandler->OnIMEEndCompositionOnPlugin(aWindow, aEvent->wParam,
+ aEvent->lParam);
+ }
+ break;
+ }
+}
+
+/******************************************************************************
+ * 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 selection(true, eQuerySelectedText, aWindow);
+ LayoutDeviceIntPoint point(0, 0);
+ aWindow->InitEvent(selection, &point);
+ DispatchEvent(aWindow, selection);
+ if (NS_WARN_IF(!selection.mSucceeded)) {
+ 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;
+ }
+
+ mOffset = selection.mReply.mOffset;
+ mString = selection.mReply.mString;
+ mWritingMode = selection.GetWritingMode();
+ mIsValid = true;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("Selection::Init, selection={ mReply={ mOffset=%u, "
+ "mString.Length()=%u, mWritingMode=%s } }",
+ mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
+
+ 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 000000000..f3120cfec
--- /dev/null
+++ b/widget/windows/IMMHandler.h
@@ -0,0 +1,495 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 nsIMEUpdatePreference GetIMEUpdatePreference();
+
+ // 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);
+ static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm);
+ static void DefaultProcOfPluginEvent(nsWindow* aWindow,
+ const NPEvent* aEvent);
+
+protected:
+ static void EnsureHandlerInstance();
+
+ static bool IsComposingOnOurEditor();
+ static bool IsComposingOnPlugin();
+ static bool IsComposingWindow(nsWindow* aWindow);
+
+ static bool IsJapanist2003Active();
+ static bool IsGoogleJapaneseInputActive();
+
+ 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);
+ static bool ProcessMessageForPlugin(nsWindow* aWindow, UINT msg,
+ WPARAM &wParam, LPARAM &lParam,
+ bool &aRet, 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);
+ void OnIMEStartCompositionOnPlugin(nsWindow* aWindow,
+ WPARAM wParam, LPARAM lParam);
+ bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ void OnIMECompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam);
+ bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult);
+ void OnIMEEndCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam);
+ bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnIMECharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnCharOnPlugin(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 OnIMESetContextOnPlugin(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 nsAFlatString& aStr,
+ UINT aCodePage,
+ nsACString& aANSIStr);
+
+ bool SetIMERelatedWindowsPos(nsWindow* aWindow,
+ const IMEContext& aContext);
+ void SetIMERelatedWindowsPosOnPlugin(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;
+ InfallibleTArray<uint32_t> mClauseArray;
+ InfallibleTArray<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;
+ bool mIsComposingOnPlugin;
+ bool mNativeCaretIsCreated;
+
+ static mozilla::WritingMode sWritingModeOfCompositionFont;
+ static nsString sIMEName;
+ static UINT sCodePage;
+ static DWORD sIMEProperty;
+ static DWORD sIMEUIProperty;
+ static bool sAssumeVerticalWritingModeNotSupported;
+ static bool sHasFocus;
+ static bool sNativeCaretIsCreatedForPlugin;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // IMMHandler_h_
diff --git a/widget/windows/InProcessWinCompositorWidget.cpp b/widget/windows/InProcessWinCompositorWidget.cpp
new file mode 100644
index 000000000..685eaf5ca
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.cpp
@@ -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/. */
+
+#include "InProcessWinCompositorWidget.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+/* static */ RefPtr<CompositorWidget>
+CompositorWidget::CreateLocal(const CompositorWidgetInitData& aInitData, nsIWidget* aWidget)
+{
+ return new InProcessWinCompositorWidget(aInitData, static_cast<nsWindow*>(aWidget));
+}
+
+InProcessWinCompositorWidget::InProcessWinCompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow)
+ : WinCompositorWidget(aInitData),
+ mWindow(aWindow)
+{
+ MOZ_ASSERT(mWindow);
+}
+
+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 000000000..2ce6ba0be
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.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 widget_windows_InProcessCompositorWidgetParent_h
+#define widget_windows_InProcessCompositorWidgetParent_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:
+ InProcessWinCompositorWidget(const CompositorWidgetInitData& aInitData, nsWindow* aWindow);
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ nsIWidget* RealWidget() override;
+
+private:
+ nsWindow* mWindow;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_InProcessCompositorWidgetParent_h
diff --git a/widget/windows/InkCollector.cpp b/widget/windows/InkCollector.cpp
new file mode 100644
index 000000000..5383dda7c
--- /dev/null
+++ b/widget/windows/InkCollector.cpp
@@ -0,0 +1,253 @@
+/* -*- 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);
+}
+
+// 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 000000000..9570eea2a
--- /dev/null
+++ b/widget/windows/InkCollector.h
@@ -0,0 +1,98 @@
+/* -*- 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 : 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;
+};
+
+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 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/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp
new file mode 100644
index 000000000..566c41d4a
--- /dev/null
+++ b/widget/windows/JumpListBuilder.cpp
@@ -0,0 +1,542 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIMutableArray.h"
+#include "nsWidgetsCID.h"
+#include "WinTaskbar.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/Preferences.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsIObserverService.h"
+
+#include "WinUtils.h"
+
+// 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 {
+
+static NS_DEFINE_CID(kJumpListItemCID, NS_WIN_JUMPLISTITEM_CID);
+static NS_DEFINE_CID(kJumpListLinkCID, NS_WIN_JUMPLISTLINK_CID);
+static NS_DEFINE_CID(kJumpListShortcutCID, NS_WIN_JUMPLISTSHORTCUT_CID);
+
+// defined in WinTaskbar.cpp
+extern const wchar_t *gMozillaJumpListIDGeneric;
+
+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"
+
+JumpListBuilder::JumpListBuilder() :
+ mMaxItems(0),
+ mHasCommit(false)
+{
+ ::CoInitialize(nullptr);
+
+ CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr));
+
+ // Make a lazy thread for any IO
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+ NS_LITERAL_CSTRING("Jump List"),
+ 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);
+ }
+}
+
+JumpListBuilder::~JumpListBuilder()
+{
+ Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
+ mJumpListMgr = nullptr;
+ ::CoUninitialize();
+}
+
+NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable)
+{
+ *aAvailable = false;
+
+ 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)
+{
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *aMaxItems = 0;
+
+ if (sBuildingList) {
+ *aMaxItems = mMaxItems;
+ return NS_OK;
+ }
+
+ IObjectArray *objArray;
+ if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
+ *aMaxItems = mMaxItems;
+
+ if (objArray)
+ objArray->Release();
+
+ mJumpListMgr->AbortList();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(removedItems);
+
+ *_retval = false;
+
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if(sBuildingList)
+ AbortListBuild();
+
+ IObjectArray *objArray;
+
+ // 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.
+ if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
+ if (objArray) {
+ TransferIObjectArrayToIMutableArray(objArray, removedItems);
+ objArray->Release();
+ }
+
+ RemoveIconCacheForItems(removedItems);
+
+ sBuildingList = true;
+ *_retval = true;
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+// Ensures that we don't have old ICO files that aren't in our jump lists
+// anymore left over in the cache.
+nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items)
+{
+ NS_ENSURE_ARG_POINTER(items);
+
+ nsresult rv;
+ uint32_t length;
+ items->GetLength(&length);
+ for (uint32_t i = 0; i < length; ++i) {
+
+ //Obtain an IJumpListItem and get the type
+ nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item) {
+ continue;
+ }
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type))) {
+ continue;
+ }
+
+ // If the item is a shortcut, remove its associated icon if any
+ if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) {
+ nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item);
+ if (shortcut) {
+ nsCOMPtr<nsIURI> uri;
+ rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv) && uri) {
+
+ // The local file path is stored inside the nsIURI
+ // Get the nsIURI spec which stores the local path for the icon to remove
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> event
+ = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec));
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ // The shortcut was generated from an IShellLinkW so IShellLinkW can
+ // only tell us what the original icon is and not the URI.
+ // So this field was used only temporarily as the actual icon file
+ // path. It should be cleared.
+ shortcut->SetFaviconPageUri(nullptr);
+ }
+ }
+ }
+
+ } // end for
+
+ return NS_OK;
+}
+
+// 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<nsISimpleEnumerator> entries;
+ rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through each directory entry and remove all ICO files found
+ do {
+ bool hasMore = false;
+ if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> supp;
+ if (NS_FAILED(entries->GetNext(getter_AddRefs(supp))))
+ break;
+
+ nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp));
+ 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;
+
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ 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 = mJumpListMgr->AddUserTasks(pArray);
+ if (SUCCEEDED(hr))
+ *_retval = true;
+ return NS_OK;
+ }
+ break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT:
+ {
+ if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT)))
+ *_retval = true;
+ return NS_OK;
+ }
+ break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT:
+ {
+ if (SUCCEEDED(mJumpListMgr->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 = mJumpListMgr->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()
+{
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mJumpListMgr->AbortList();
+ sBuildingList = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval)
+{
+ *_retval = false;
+
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ HRESULT hr = mJumpListMgr->CommitList();
+ sBuildingList = false;
+
+ // XXX We might want some specific error data here.
+ if (SUCCEEDED(hr)) {
+ *_retval = true;
+ mHasCommit = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval)
+{
+ *_retval = false;
+
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if(sBuildingList)
+ AbortListBuild();
+
+ nsAutoString uid;
+ if (!WinTaskbar::GetAppUserModelID(uid))
+ return NS_OK;
+
+ if (SUCCEEDED(mJumpListMgr->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;
+}
+
+// TransferIObjectArrayToIMutableArray - used in converting removed items
+// to our objects.
+nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems)
+{
+ NS_ENSURE_ARG_POINTER(objArray);
+ NS_ENSURE_ARG_POINTER(removedItems);
+
+ nsresult rv;
+
+ uint32_t count = 0;
+ objArray->GetCount(&count);
+
+ nsCOMPtr<nsIJumpListItem> item;
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ IShellLinkW * pLink = nullptr;
+ IShellItem * pItem = nullptr;
+
+ if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) {
+ nsCOMPtr<nsIJumpListShortcut> shortcut =
+ do_CreateInstance(kJumpListShortcutCID, &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNEXPECTED;
+ rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut);
+ item = do_QueryInterface(shortcut);
+ }
+ else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) {
+ nsCOMPtr<nsIJumpListLink> link =
+ do_CreateInstance(kJumpListLinkCID, &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNEXPECTED;
+ rv = JumpListLink::GetJumpListLink(pItem, link);
+ item = do_QueryInterface(link);
+ }
+
+ if (pLink)
+ pLink->Release();
+ if (pItem)
+ pItem->Release();
+
+ if (NS_SUCCEEDED(rv)) {
+ removedItems->AppendElement(item, false);
+ }
+ }
+ return NS_OK;
+}
+
+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();
+ } 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 000000000..553ff765d
--- /dev/null
+++ b/widget/windows/JumpListBuilder.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 __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 "nsIMutableArray.h"
+
+#include "nsIJumpListBuilder.h"
+#include "nsIJumpListItem.h"
+#include "JumpListItem.h"
+#include "nsIObserver.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace widget {
+
+class JumpListBuilder : public nsIJumpListBuilder,
+ public nsIObserver
+{
+ virtual ~JumpListBuilder();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIJUMPLISTBUILDER
+ NS_DECL_NSIOBSERVER
+
+ JumpListBuilder();
+
+protected:
+ static bool sBuildingList;
+
+private:
+ RefPtr<ICustomDestinationList> mJumpListMgr;
+ uint32_t mMaxItems;
+ bool mHasCommit;
+ nsCOMPtr<nsIThread> mIOThread;
+
+ bool IsSeparator(nsCOMPtr<nsIJumpListItem>& item);
+ nsresult TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems);
+ nsresult RemoveIconCacheForItems(nsIMutableArray *removedItems);
+ nsresult RemoveIconCacheForAllItems();
+
+ 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 000000000..57dff6466
--- /dev/null
+++ b/widget/windows/JumpListItem.cpp
@@ -0,0 +1,630 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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_IMPL_ISUPPORTS_INHERITED(JumpListSeparator,
+ JumpListItem,
+ nsIJumpListSeparator)
+
+NS_IMPL_ISUPPORTS_INHERITED(JumpListLink,
+ JumpListItem,
+ nsIJumpListLink)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JumpListShortcut)
+ NS_INTERFACE_MAP_ENTRY(nsIJumpListShortcut)
+NS_INTERFACE_MAP_END_INHERITING(JumpListItem)
+
+NS_IMPL_CYCLE_COLLECTION(JumpListShortcut, mHandlerApp)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(JumpListShortcut)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(JumpListShortcut)
+
+NS_IMETHODIMP JumpListItem::GetType(int16_t *aType)
+{
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = mItemType;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListItem::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;
+
+ // Confirm the app is present on the system
+ if (!ExecutableExists(mHandlerApp))
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ 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(WinUtils::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;
+}
+
+// Confirm the app is on the system
+bool JumpListShortcut::ExecutableExists(nsCOMPtr<nsILocalHandlerApp>& handlerApp)
+{
+ nsresult rv;
+
+ if (!handlerApp)
+ return false;
+
+ nsCOMPtr<nsIFile> executable;
+ rv = handlerApp->GetExecutable(getter_AddRefs(executable));
+ if (NS_SUCCEEDED(rv) && executable) {
+ bool exists;
+ executable->Exists(&exists);
+ return exists;
+ }
+ return false;
+}
+
+} // namespace widget
+} // namespace mozilla
+
diff --git a/widget/windows/JumpListItem.h b/widget/windows/JumpListItem.h
new file mode 100644
index 000000000..1e3152ae0
--- /dev/null
+++ b/widget/windows/JumpListItem.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIMutableArray.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsICryptoHash.h"
+#include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIThread;
+
+namespace mozilla {
+namespace widget {
+
+class JumpListItem : public nsIJumpListItem
+{
+public:
+ JumpListItem() :
+ mItemType(nsIJumpListItem::JUMPLIST_ITEM_EMPTY)
+ {}
+
+ JumpListItem(int32_t type) :
+ mItemType(type)
+ {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIJUMPLISTITEM
+
+ static const char kJumpListCacheDir[];
+
+protected:
+ virtual ~JumpListItem()
+ {}
+
+ short Type() { return mItemType; }
+ short mItemType;
+
+};
+
+class JumpListSeparator : public JumpListItem, public nsIJumpListSeparator
+{
+ ~JumpListSeparator() {}
+
+public:
+ JumpListSeparator() :
+ JumpListItem(nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR)
+ {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHOD GetType(int16_t *aType) override { return JumpListItem::GetType(aType); }
+ NS_IMETHOD Equals(nsIJumpListItem *item, bool *_retval) override { return JumpListItem::Equals(item, _retval); }
+
+ static nsresult GetSeparator(RefPtr<IShellLinkW>& aShellLink);
+};
+
+class JumpListLink : public JumpListItem, public nsIJumpListLink
+{
+ ~JumpListLink() {}
+
+public:
+ JumpListLink() :
+ JumpListItem(nsIJumpListItem::JUMPLIST_ITEM_LINK)
+ {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHOD GetType(int16_t *aType) override { return JumpListItem::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 JumpListItem, public nsIJumpListShortcut
+{
+ ~JumpListShortcut() {}
+
+public:
+ JumpListShortcut() :
+ JumpListItem(nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT)
+ {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(JumpListShortcut, JumpListItem)
+ NS_IMETHOD GetType(int16_t *aType) override { return JumpListItem::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 000000000..11f657874
--- /dev/null
+++ b/widget/windows/KeyboardLayout.cpp
@@ -0,0 +1,5232 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/DebugOnly.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/WindowsVersion.h"
+
+#include "nsAlgorithm.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+#include "nsGkAtoms.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIIdleServiceInternal.h"
+#include "nsIWindowsRegKey.h"
+#include "nsMemory.h"
+#include "nsPrintfCString.h"
+#include "nsQuickSort.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* 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 NS_LITERAL_CSTRING("NULL (0x0000)");
+ case 0x0008:
+ return NS_LITERAL_CSTRING("BACKSPACE (0x0008)");
+ case 0x0009:
+ return NS_LITERAL_CSTRING("CHARACTER TABULATION (0x0009)");
+ case 0x000A:
+ return NS_LITERAL_CSTRING("LINE FEED (0x000A)");
+ case 0x000B:
+ return NS_LITERAL_CSTRING("LINE TABULATION (0x000B)");
+ case 0x000C:
+ return NS_LITERAL_CSTRING("FORM FEED (0x000C)");
+ case 0x000D:
+ return NS_LITERAL_CSTRING("CARRIAGE RETURN (0x000D)");
+ case 0x0018:
+ return NS_LITERAL_CSTRING("CANCEL (0x0018)");
+ case 0x001B:
+ return NS_LITERAL_CSTRING("ESCAPE (0x001B)");
+ case 0x0020:
+ return NS_LITERAL_CSTRING("SPACE (0x0020)");
+ case 0x007F:
+ return NS_LITERAL_CSTRING("DELETE (0x007F)");
+ case 0x00A0:
+ return NS_LITERAL_CSTRING("NO-BREAK SPACE (0x00A0)");
+ case 0x00AD:
+ return NS_LITERAL_CSTRING("SOFT HYPHEN (0x00AD)");
+ case 0x2000:
+ return NS_LITERAL_CSTRING("EN QUAD (0x2000)");
+ case 0x2001:
+ return NS_LITERAL_CSTRING("EM QUAD (0x2001)");
+ case 0x2002:
+ return NS_LITERAL_CSTRING("EN SPACE (0x2002)");
+ case 0x2003:
+ return NS_LITERAL_CSTRING("EM SPACE (0x2003)");
+ case 0x2004:
+ return NS_LITERAL_CSTRING("THREE-PER-EM SPACE (0x2004)");
+ case 0x2005:
+ return NS_LITERAL_CSTRING("FOUR-PER-EM SPACE (0x2005)");
+ case 0x2006:
+ return NS_LITERAL_CSTRING("SIX-PER-EM SPACE (0x2006)");
+ case 0x2007:
+ return NS_LITERAL_CSTRING("FIGURE SPACE (0x2007)");
+ case 0x2008:
+ return NS_LITERAL_CSTRING("PUNCTUATION SPACE (0x2008)");
+ case 0x2009:
+ return NS_LITERAL_CSTRING("THIN SPACE (0x2009)");
+ case 0x200A:
+ return NS_LITERAL_CSTRING("HAIR SPACE (0x200A)");
+ case 0x200B:
+ return NS_LITERAL_CSTRING("ZERO WIDTH SPACE (0x200B)");
+ case 0x200C:
+ return NS_LITERAL_CSTRING("ZERO WIDTH NON-JOINER (0x200C)");
+ case 0x200D:
+ return NS_LITERAL_CSTRING("ZERO WIDTH JOINER (0x200D)");
+ case 0x200E:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT MARK (0x200E)");
+ case 0x200F:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT MARK (0x200F)");
+ case 0x2029:
+ return NS_LITERAL_CSTRING("PARAGRAPH SEPARATOR (0x2029)");
+ case 0x202A:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT EMBEDDING (0x202A)");
+ case 0x202B:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT EMBEDDING (0x202B)");
+ case 0x202D:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT OVERRIDE (0x202D)");
+ case 0x202E:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT OVERRIDE (0x202E)");
+ case 0x202F:
+ return NS_LITERAL_CSTRING("NARROW NO-BREAK SPACE (0x202F)");
+ case 0x205F:
+ return NS_LITERAL_CSTRING("MEDIUM MATHEMATICAL SPACE (0x205F)");
+ case 0x2060:
+ return NS_LITERAL_CSTRING("WORD JOINER (0x2060)");
+ case 0x2066:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT ISOLATE (0x2066)");
+ case 0x2067:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT ISOLATE (0x2067)");
+ case 0x3000:
+ return NS_LITERAL_CSTRING("IDEOGRAPHIC SPACE (0x3000)");
+ case 0xFEFF:
+ return NS_LITERAL_CSTRING("ZERO WIDTH NO-BREAK SPACE (0xFEFF)");
+ 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 nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_LEFT");
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_RIGHT");
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_STANDARD");
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_NUMPAD");
+ default:
+ return nsPrintfCString("Unknown (0x%04X)", aLocation);
+ }
+}
+
+static const nsCString
+GetCharacterCodeName(char16_t* aChars, uint32_t aLength)
+{
+ if (!aLength) {
+ return NS_LITERAL_CSTRING("");
+ }
+ nsAutoCString result;
+ for (uint32_t i = 0; i < aLength; ++i) {
+ if (!result.IsEmpty()) {
+ result.AppendLiteral(", ");
+ } else {
+ result.AssignLiteral("\"");
+ }
+ result.Append(GetCharacterCodeName(aChars[i]));
+ }
+ 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 NS_LITERAL_CSTRING("WM_NULL");
+ case WM_KEYDOWN:
+ return NS_LITERAL_CSTRING("WM_KEYDOWN");
+ case WM_KEYUP:
+ return NS_LITERAL_CSTRING("WM_KEYUP");
+ case WM_SYSKEYDOWN:
+ return NS_LITERAL_CSTRING("WM_SYSKEYDOWN");
+ case WM_SYSKEYUP:
+ return NS_LITERAL_CSTRING("WM_SYSKEYUP");
+ case WM_CHAR:
+ return NS_LITERAL_CSTRING("WM_CHAR");
+ case WM_UNICHAR:
+ return NS_LITERAL_CSTRING("WM_UNICHAR");
+ case WM_SYSCHAR:
+ return NS_LITERAL_CSTRING("WM_SYSCHAR");
+ case WM_DEADCHAR:
+ return NS_LITERAL_CSTRING("WM_DEADCHAR");
+ case WM_SYSDEADCHAR:
+ return NS_LITERAL_CSTRING("WM_SYSDEADCHAR");
+ case MOZ_WM_KEYDOWN:
+ return NS_LITERAL_CSTRING("MOZ_WM_KEYDOWN");
+ case MOZ_WM_KEYUP:
+ return NS_LITERAL_CSTRING("MOZ_WM_KEYUP");
+ case WM_APPCOMMAND:
+ return NS_LITERAL_CSTRING("WM_APPCOMMAND");
+ case WM_QUIT:
+ return NS_LITERAL_CSTRING("WM_QUIT");
+ 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 NS_LITERAL_CSTRING("APPCOMMAND_BASS_BOOST");
+ case APPCOMMAND_BASS_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BASS_DOWN");
+ case APPCOMMAND_BASS_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BASS_UP");
+ case APPCOMMAND_BROWSER_BACKWARD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_BACKWARD");
+ case APPCOMMAND_BROWSER_FAVORITES:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_FAVORITES");
+ case APPCOMMAND_BROWSER_FORWARD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_FORWARD");
+ case APPCOMMAND_BROWSER_HOME:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_HOME");
+ case APPCOMMAND_BROWSER_REFRESH:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_REFRESH");
+ case APPCOMMAND_BROWSER_SEARCH:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_SEARCH");
+ case APPCOMMAND_BROWSER_STOP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_STOP");
+ case APPCOMMAND_CLOSE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_CLOSE");
+ case APPCOMMAND_COPY:
+ return NS_LITERAL_CSTRING("APPCOMMAND_COPY");
+ case APPCOMMAND_CORRECTION_LIST:
+ return NS_LITERAL_CSTRING("APPCOMMAND_CORRECTION_LIST");
+ case APPCOMMAND_CUT:
+ return NS_LITERAL_CSTRING("APPCOMMAND_CUT");
+ case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE");
+ case APPCOMMAND_FIND:
+ return NS_LITERAL_CSTRING("APPCOMMAND_FIND");
+ case APPCOMMAND_FORWARD_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_FORWARD_MAIL");
+ case APPCOMMAND_HELP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_HELP");
+ case APPCOMMAND_LAUNCH_APP1:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_APP1");
+ case APPCOMMAND_LAUNCH_APP2:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_APP2");
+ case APPCOMMAND_LAUNCH_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_MAIL");
+ case APPCOMMAND_LAUNCH_MEDIA_SELECT:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_MEDIA_SELECT");
+ case APPCOMMAND_MEDIA_CHANNEL_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_CHANNEL_DOWN");
+ case APPCOMMAND_MEDIA_CHANNEL_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_CHANNEL_UP");
+ case APPCOMMAND_MEDIA_FAST_FORWARD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_FAST_FORWARD");
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_NEXTTRACK");
+ case APPCOMMAND_MEDIA_PAUSE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PAUSE");
+ case APPCOMMAND_MEDIA_PLAY:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PLAY");
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PLAY_PAUSE");
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PREVIOUSTRACK");
+ case APPCOMMAND_MEDIA_RECORD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_RECORD");
+ case APPCOMMAND_MEDIA_REWIND:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_REWIND");
+ case APPCOMMAND_MEDIA_STOP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_STOP");
+ case APPCOMMAND_MIC_ON_OFF_TOGGLE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MIC_ON_OFF_TOGGLE");
+ case APPCOMMAND_MICROPHONE_VOLUME_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_DOWN");
+ case APPCOMMAND_MICROPHONE_VOLUME_MUTE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_MUTE");
+ case APPCOMMAND_MICROPHONE_VOLUME_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_UP");
+ case APPCOMMAND_NEW:
+ return NS_LITERAL_CSTRING("APPCOMMAND_NEW");
+ case APPCOMMAND_OPEN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_OPEN");
+ case APPCOMMAND_PASTE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_PASTE");
+ case APPCOMMAND_PRINT:
+ return NS_LITERAL_CSTRING("APPCOMMAND_PRINT");
+ case APPCOMMAND_REDO:
+ return NS_LITERAL_CSTRING("APPCOMMAND_REDO");
+ case APPCOMMAND_REPLY_TO_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_REPLY_TO_MAIL");
+ case APPCOMMAND_SAVE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_SAVE");
+ case APPCOMMAND_SEND_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_SEND_MAIL");
+ case APPCOMMAND_SPELL_CHECK:
+ return NS_LITERAL_CSTRING("APPCOMMAND_SPELL_CHECK");
+ case APPCOMMAND_TREBLE_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_TREBLE_DOWN");
+ case APPCOMMAND_TREBLE_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_TREBLE_UP");
+ case APPCOMMAND_UNDO:
+ return NS_LITERAL_CSTRING("APPCOMMAND_UNDO");
+ case APPCOMMAND_VOLUME_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_DOWN");
+ case APPCOMMAND_VOLUME_MUTE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_MUTE");
+ case APPCOMMAND_VOLUME_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_UP");
+ default:
+ return nsPrintfCString("Unknown app command (0x%08X)", aCommand);
+ }
+}
+
+static const nsCString
+GetAppCommandDeviceName(LPARAM aDevice)
+{
+ switch (aDevice) {
+ case FAPPCOMMAND_KEY:
+ return NS_LITERAL_CSTRING("FAPPCOMMAND_KEY");
+ case FAPPCOMMAND_MOUSE:
+ return NS_LITERAL_CSTRING("FAPPCOMMAND_MOUSE");
+ case FAPPCOMMAND_OEM:
+ return NS_LITERAL_CSTRING("FAPPCOMMAND_OEM");
+ default:
+ return nsPrintfCString("Unknown app command device (0x%04X)", aDevice);
+ }
+};
+
+class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString
+{
+public:
+ 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)
+{
+ nsAutoCString 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_LITERAL_CSTRING("{}");
+ }
+ nsAutoCString 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)
+{
+ nsAutoCString 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;
+
+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;
+};
+
+
+/*****************************************************************************
+ * mozilla::widget::ModifierKeyState
+ *****************************************************************************/
+
+ModifierKeyState::ModifierKeyState()
+{
+ Update();
+}
+
+ModifierKeyState::ModifierKeyState(bool aIsShiftDown,
+ bool aIsControlDown,
+ bool aIsAltDown)
+{
+ Update();
+ Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_ALTGRAPH);
+ Modifiers modifiers = 0;
+ if (aIsShiftDown) {
+ modifiers |= MODIFIER_SHIFT;
+ }
+ if (aIsControlDown) {
+ modifiers |= MODIFIER_CONTROL;
+ }
+ if (aIsAltDown) {
+ modifiers |= MODIFIER_ALT;
+ }
+ if (modifiers) {
+ Set(modifiers);
+ }
+}
+
+ModifierKeyState::ModifierKeyState(Modifiers aModifiers) :
+ mModifiers(aModifiers)
+{
+ EnsureAltGr();
+}
+
+void
+ModifierKeyState::Update()
+{
+ mModifiers = 0;
+ if (IS_VK_DOWN(VK_SHIFT)) {
+ mModifiers |= MODIFIER_SHIFT;
+ }
+ 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;
+ }
+
+ EnsureAltGr();
+}
+
+void
+ModifierKeyState::Unset(Modifiers aRemovingModifiers)
+{
+ mModifiers &= ~aRemovingModifiers;
+ // Note that we don't need to unset AltGr flag here automatically.
+ // For EditorBase, we need to remove Alt and Control flags but AltGr isn't
+ // checked in EditorBase, so, it can be kept.
+}
+
+void
+ModifierKeyState::Set(Modifiers aAddingModifiers)
+{
+ mModifiers |= aAddingModifiers;
+ EnsureAltGr();
+}
+
+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.buttons = 0;
+ if (::GetKeyState(VK_LBUTTON) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::eLeftButtonFlag;
+ }
+ if (::GetKeyState(VK_RBUTTON) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::eRightButtonFlag;
+ }
+ if (::GetKeyState(VK_MBUTTON) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::eMiddleButtonFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON1) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::e4thButtonFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON2) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::e5thButtonFlag;
+ }
+}
+
+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::IsAltGr() const
+{
+ return IsControl() && IsAlt();
+}
+
+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;
+}
+
+void
+ModifierKeyState::EnsureAltGr()
+{
+ // If both Control key and Alt key are pressed, it means AltGr is pressed.
+ // Ideally, we should check whether the current keyboard layout has AltGr
+ // or not. However, setting AltGr flags for keyboard which doesn't have
+ // AltGr must not be serious bug. So, it should be OK for now.
+ if (IsAltGr()) {
+ mModifiers |= MODIFIER_ALTGRAPH;
+ }
+}
+
+/*****************************************************************************
+ * 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
+{
+ nsCaseInsensitiveStringComparator comp;
+ return mChars.Equals(aOther.mChars, comp);
+}
+
+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_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_CONTROL) {
+ modifiers |= MODIFIER_CONTROL;
+ }
+ if (aShiftState & STATE_ALT) {
+ modifiers |= MODIFIER_ALT;
+ }
+ if (aShiftState & STATE_CAPSLOCK) {
+ modifiers |= MODIFIER_CAPSLOCK;
+ }
+ if ((modifiers & (MODIFIER_ALT | MODIFIER_CONTROL)) ==
+ (MODIFIER_ALT | MODIFIER_CONTROL)) {
+ modifiers |= MODIFIER_ALTGRAPH;
+ }
+ return modifiers;
+}
+
+inline char16_t
+VirtualKey::GetCompositeChar(ShiftState aShiftState, char16_t aBaseChar) const
+{
+ return mShiftStates[aShiftState].DeadKey.Table->GetCompositeChar(aBaseChar);
+}
+
+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)
+{
+ NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index");
+
+ 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)
+{
+ NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index");
+
+ SetDeadKey(aShiftState, true);
+
+ mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar;
+ mShiftStates[aShiftState].DeadKey.Table = nullptr;
+}
+
+UniCharsAndModifiers
+VirtualKey::GetUniChars(ShiftState aShiftState) const
+{
+ UniCharsAndModifiers result = GetNativeUniChars(aShiftState);
+
+ const ShiftState STATE_ALT_CONTROL = (STATE_ALT | STATE_CONTROL);
+ if (!(aShiftState & STATE_ALT_CONTROL)) {
+ return result;
+ }
+
+ if (result.IsEmpty()) {
+ result = GetNativeUniChars(aShiftState & ~STATE_ALT_CONTROL);
+ result.FillModifiers(ShiftStateToModifiers(aShiftState));
+ return result;
+ }
+
+ if ((aShiftState & STATE_ALT_CONTROL) == STATE_ALT_CONTROL) {
+ // Even if the shifted chars and the unshifted chars are same, we
+ // should consume the Alt key state and the Ctrl key state when
+ // AltGr key is pressed. Because if we don't consume them, the input
+ // events are ignored on EditorBase. (I.e., Users cannot input the
+ // characters with this key combination.)
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ result.FillModifiers(finalModifiers);
+ return result;
+ }
+
+ UniCharsAndModifiers unmodifiedReslt =
+ GetNativeUniChars(aShiftState & ~STATE_ALT_CONTROL);
+ if (!result.UniCharsEqual(unmodifiedReslt)) {
+ // Otherwise, we should consume the Alt key state and the Ctrl key state
+ // only when the shifted chars and unshifted chars are different.
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ result.FillModifiers(finalModifiers);
+ }
+ return result;
+}
+
+
+UniCharsAndModifiers
+VirtualKey::GetNativeUniChars(ShiftState aShiftState) const
+{
+#ifdef DEBUG
+ if (aShiftState >= ArrayLength(mShiftStates)) {
+ nsPrintfCString warning("Shift state is out of range: "
+ "aShiftState=%d, ArrayLength(mShiftState)=%d",
+ aShiftState, ArrayLength(mShiftStates));
+ NS_WARNING(warning.get());
+ }
+#endif
+
+ UniCharsAndModifiers result;
+ Modifiers modifiers = ShiftStateToModifiers(aShiftState);
+ if (IsDeadKey(aShiftState)) {
+ result.Append(mShiftStates[aShiftState].DeadKey.DeadChar, modifiers);
+ return result;
+ }
+
+ uint32_t index;
+ uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars);
+ for (index = 0;
+ index < len && mShiftStates[aShiftState].Normal.Chars[index]; index++) {
+ result.Append(mShiftStates[aShiftState].Normal.Chars[index], modifiers);
+ }
+ return result;
+}
+
+// static
+void
+VirtualKey::FillKbdState(PBYTE aKbdState,
+ const ShiftState aShiftState)
+{
+ NS_ASSERTION(aShiftState < 16, "aShiftState out of range");
+
+ if (aShiftState & STATE_SHIFT) {
+ aKbdState[VK_SHIFT] |= 0x80;
+ } else {
+ aKbdState[VK_SHIFT] &= ~0x80;
+ aKbdState[VK_LSHIFT] &= ~0x80;
+ aKbdState[VK_RSHIFT] &= ~0x80;
+ }
+
+ 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 = {};
+
+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)
+ , mIsDeadKey(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;
+ }
+
+ if (mMsg.message == WM_APPCOMMAND) {
+ InitWithAppCommand();
+ } else {
+ InitWithKeyChar();
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::NativeKey(), mKeyboardLayout=0x%08X, "
+ "mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=0x%04X, "
+ "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, "
+ "mIsDeadKey=%s, mIsPrintableKey=%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(mIsDeadKey),
+ GetBoolName(mIsPrintableKey), GetBoolName(mCharMessageHasGone),
+ GetBoolName(mIsOverridingKeyboardLayout)));
+}
+
+void
+NativeKey::InitWithKeyChar()
+{
+ 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: {
+ // First, resolve the IME converted virtual keycode to its original
+ // keycode.
+ if (mMsg.wParam == VK_PROCESSKEY) {
+ mOriginalVirtualKeyCode =
+ static_cast<uint8_t>(::ImmGetVirtualKey(mMsg.hwnd));
+ } else {
+ 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;
+ }
+
+ if (!CanComputeVirtualKeyCodeFromScanCode()) {
+ // The right control key and the right alt key are extended keys.
+ // Therefore, we never get VK_RCONTRL and VK_RMENU for the result of
+ // MapVirtualKeyEx() on WinXP or WinServer2003.
+ //
+ // If VK_SHIFT, VK_CONTROL or VK_MENU key message is caused by well
+ // known scan code, we should decide it as Right key. Otherwise,
+ // decide it as Left key.
+ switch (mOriginalVirtualKeyCode) {
+ case VK_CONTROL:
+ mVirtualKeyCode =
+ mIsExtended && mScanCode == 0x1D ? VK_RCONTROL : VK_LCONTROL;
+ break;
+ case VK_MENU:
+ mVirtualKeyCode =
+ mIsExtended && mScanCode == 0x38 ? VK_RMENU : VK_LMENU;
+ break;
+ case VK_SHIFT:
+ // 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;
+ }
+
+ 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::InitWithKeyChar(), 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.
+
+ // We cannot compute the virtual key code from WM_CHAR message on WinXP
+ // if it's caused by an extended key.
+ if (!CanComputeVirtualKeyCodeFromScanCode()) {
+ break;
+ }
+ mVirtualKeyCode = mOriginalVirtualKeyCode =
+ ComputeVirtualKeyCodeFromScanCodeEx();
+ NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode");
+ break;
+ default:
+ MOZ_CRASH("Unsupported message");
+ }
+
+ if (!mVirtualKeyCode) {
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ }
+
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ mDOMKeyCode =
+ keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode);
+ // 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(mOriginalVirtualKeyCode);
+ 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::InitWithKeyChar(), removed char message, %s",
+ this, ToString(charMsg).get()));
+ NS_WARN_IF(charMsg.hwnd != mMsg.hwnd);
+ mFollowingCharMsgs.AppendElement(charMsg);
+ }
+ }
+
+ keyboardLayout->InitNativeKey(*this, mModKeyState);
+
+ mIsDeadKey =
+ (IsFollowedByDeadCharMessage() ||
+ keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState));
+ mIsPrintableKey =
+ mKeyNameIndex == KEY_NAME_INDEX_USE_STRING ||
+ KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode);
+
+ 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(
+ const ModifierKeyState& aModKeyState)
+{
+ 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 = aModKeyState.GetModifiers();
+ if (IsFollowedByPrintableCharMessage()) {
+ modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ // 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());
+}
+
+// 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 nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+
+ case VK_RSHIFT:
+ case VK_RCONTROL:
+ case VK_RMENU:
+ case VK_RWIN:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+
+ case VK_RETURN:
+ // XXX This code assumes that all keyboard drivers use same mapping.
+ return !mIsExtended ? nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD :
+ nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+
+ 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 ? nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD :
+ nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+
+ // 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 nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU:
+ NS_WARNING("Failed to decide the key location?");
+
+ default:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ }
+}
+
+bool
+NativeKey::CanComputeVirtualKeyCodeFromScanCode() const
+{
+ // Vista or later supports ScanCodeEx.
+ if (IsVistaOrLater()) {
+ return true;
+ }
+ // Otherwise, MapVirtualKeyEx() can compute virtual keycode only with
+ // non-extended key.
+ return !mIsExtended;
+}
+
+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.
+ // On the other hand, neither WinXP nor WinServer2003 doesn't support 0xE000.
+ // Therefore, we have no way to get virtual keycode from scan code of
+ // extended keys.
+ if (NS_WARN_IF(!CanComputeVirtualKeyCodeFromScanCode())) {
+ return 0;
+ }
+ 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,
+ IsVistaOrLater() ? MAPVK_VK_TO_VSC_EX :
+ MAPVK_VK_TO_VSC,
+ mKeyboardLayout));
+}
+
+char16_t
+NativeKey::ComputeUnicharFromScanCode() const
+{
+ return static_cast<char16_t>(
+ ::MapVirtualKeyEx(ComputeVirtualKeyCodeFromScanCode(),
+ MAPVK_VK_TO_CHAR, mKeyboardLayout));
+}
+
+nsEventStatus
+NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG* aMsgSentToPlugin) const
+{
+ return InitKeyEvent(aKeyEvent, mModKeyState, aMsgSentToPlugin);
+}
+
+nsEventStatus
+NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const ModifierKeyState& aModKeyState,
+ const MSG* aMsgSentToPlugin) 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.
+ if (mCharMessageHasGone) {
+ aKeyEvent.PreventDefaultBeforeDispatch();
+ }
+ MOZ_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.
+ if (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP) {
+ aKeyEvent.PreventDefaultBeforeDispatch();
+ }
+ 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 = IsRepeat();
+ 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);
+
+ if (aMsgSentToPlugin) {
+ MaybeInitPluginEventOfKeyEvent(aKeyEvent, *aMsgSentToPlugin);
+ }
+
+ 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;
+}
+
+void
+NativeKey::MaybeInitPluginEventOfKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG& aMsgSentToPlugin) const
+{
+ if (mWidget->GetInputContext().mIMEState.mEnabled != IMEState::PLUGIN) {
+ return;
+ }
+ NPEvent pluginEvent;
+ pluginEvent.event = aMsgSentToPlugin.message;
+ pluginEvent.wParam = aMsgSentToPlugin.wParam;
+ pluginEvent.lParam = aMsgSentToPlugin.lParam;
+ aKeyEvent.mPluginEvent.Copy(pluginEvent);
+}
+
+bool
+NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const
+{
+ nsCOMPtr<nsIAtom> 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 commandEvent(true, nsGkAtoms::onAppCommand,
+ command, mWidget);
+
+ mWidget->InitEvent(commandEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatching %s command event...",
+ this, nsAtomCString(command).get()));
+ bool ok = mWidget->DispatchWindowEvent(&commandEvent) || mWidget->Destroyed();
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatched 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, &mMsg);
+ 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, &mMsg);
+ 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, &mMsg);
+ 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 && !mWidget->PluginHasFocus() && !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));
+ MaybeDispatchPluginEventsForRemovedCharMessages();
+ 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 (MaybeDispatchPluginEventsForRemovedCharMessages() ||
+ 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) && 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.
+ if (IsControlCharMessage(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);
+ 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, EditorBase 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.IsAltGr() && IsPrintableCharMessage(aCharMsg)) {
+ modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState, &aCharMsg);
+ 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, &mMsg);
+ 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.
+ 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;
+}
+
+#ifdef MOZ_CRASHREPORTER
+
+static nsCString
+GetResultOfInSendMessageEx()
+{
+ DWORD ret = ::InSendMessageEx(nullptr);
+ if (!ret) {
+ return NS_LITERAL_CSTRING("ISMEX_NOSEND");
+ }
+ nsAutoCString 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;
+}
+
+#endif // #ifdef MOZ_CRASHREPORTER
+
+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) {
+#ifdef MOZ_CRASHREPORTER
+ 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(
+ NS_LITERAL_CSTRING("\nThere is no message in any window"));
+ }
+#endif // #ifdef MOZ_CRASHREPORTER
+ 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()));
+#ifdef MOZ_CRASHREPORTER
+ 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(
+ NS_LITERAL_CSTRING("\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(
+ NS_LITERAL_CSTRING("\nThere is no key message in any windows."));
+ }
+#endif // #ifdef MOZ_CRASHREPORTER
+ 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()));
+#ifdef MOZ_CRASHREPORTER
+ 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);
+#endif // #ifdef MOZ_CRASHREPORTER
+ MOZ_CRASH("We lost the following char message");
+ return false;
+}
+
+bool
+NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages() const
+{
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(!IsKeyMessageOnPlugin());
+
+ for (size_t i = 0;
+ i < mFollowingCharMsgs.Length() && mWidget->ShouldDispatchPluginEvent();
+ ++i) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatching %uth plugin event for %s...",
+ this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
+ MOZ_RELEASE_ASSERT(!mWidget->Destroyed(),
+ "NativeKey tries to dispatch a plugin event on destroyed widget");
+ mWidget->DispatchPluginEvent(mFollowingCharMsgs[i]);
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "%uth plugin event caused %s",
+ this, i + 1, mWidget->Destroyed() ? "destroying the widget" :
+ "focus change"));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatched %uth plugin event",
+ this, i + 1));
+ }
+
+ // Dispatch odd char messages which are caused by ATOK or WXG (both of them
+ // are Japanese IME) and removed by RemoveFollowingOddCharMessages().
+ for (size_t i = 0;
+ i < mRemovedOddCharMsgs.Length() && mWidget->ShouldDispatchPluginEvent();
+ ++i) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatching %uth plugin event for odd char message, %s...",
+ this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
+ MOZ_RELEASE_ASSERT(!mWidget->Destroyed(),
+ "NativeKey tries to dispatch a plugin event on destroyed widget");
+ mWidget->DispatchPluginEvent(mRemovedOddCharMsgs[i]);
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "%uth plugin event for odd char message caused %s",
+ this, i + 1, mWidget->Destroyed() ? "destroying the widget" :
+ "focus change"));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatched %uth plugin event for odd char message",
+ this, i + 1));
+ }
+
+ 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;
+ }
+
+ 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,
+ !needsCallback ? &mFollowingCharMsgs[0] : nullptr);
+ 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.
+ MaybeInitPluginEventOfKeyEvent(aKeyboardEvent, mFollowingCharMsgs[i]);
+ 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;
+nsIIdleServiceInternal* 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<nsIIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/idleservice;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)
+{
+ 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,
+ const ModifierKeyState& aModKeyState)
+{
+ 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 =
+ aModKeyState.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers);
+ 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(aModKeyState);
+ 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) &&
+ !aModKeyState.IsAltGr() &&
+ (aModKeyState.IsControl() || aModKeyState.IsAlt())) {
+ ModifierKeyState state = aModKeyState;
+ state.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ UniCharsAndModifiers charsWithoutModifier =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, 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, aModKeyState)) {
+ 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, aModKeyState)) {
+ return;
+ }
+
+ UniCharsAndModifiers baseChars =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+
+ // 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,
+ const ModifierKeyState& aModKeyState)
+{
+ // Only when it's not in dead key sequence, we can trust IsDeadKey() result.
+ if (!IsInDeadKeySequence() &&
+ !IsDeadKey(aNativeKey.mOriginalVirtualKeyCode, aModKeyState)) {
+ 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.mOriginalVirtualKeyCode);
+
+ if (isDeadKeyDownEvent || isDeadKeyUpEvent) {
+ ActivateDeadKeyState(aNativeKey, aModKeyState);
+ // 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.mOriginalVirtualKeyCode, aModKeyState);
+ 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.mOriginalVirtualKeyCode, aModKeyState)) {
+ 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, aModKeyState)) {
+ return true;
+ }
+
+ // Otherwise, dead key followed by another dead key causes inputting both
+ // character.
+ UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers();
+ UniCharsAndModifiers newChars =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+ // 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,
+ const ModifierKeyState& aModKeyState)
+{
+ if (!IsInDeadKeySequence()) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) {
+ return false;
+ }
+
+ UniCharsAndModifiers baseChars =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+ 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::GetNativeUniCharsAndModifiers(
+ uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const
+{
+ int32_t key = GetKeyIndex(aVirtualKey);
+ if (key < 0) {
+ return UniCharsAndModifiers();
+ }
+ VirtualKey::ShiftState shiftState =
+ VirtualKey::ModifierKeyStateToShiftState(aModKeyState);
+ return mVirtualKeys[key].GetNativeUniChars(shiftState);
+}
+
+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 NS_LITERAL_CSTRING("No name or too long name");
+ }
+ return NS_ConvertUTF16toUTF8(buf);
+ }
+
+ if (NS_WARN_IF((layout & 0xF000) != 0xF000)) {
+ nsAutoCString 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 EmptyCString();
+ }
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+ nsString(kKeyboardLayouts),
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return EmptyCString();
+ }
+ uint32_t childCount = 0;
+ if (NS_WARN_IF(NS_FAILED(regKey->GetChildCount(&childCount))) ||
+ NS_WARN_IF(!childCount)) {
+ return EmptyCString();
+ }
+ 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 EmptyCString();
+}
+
+void
+KeyboardLayout::LoadLayout(HKL aLayout)
+{
+ mIsPendingToRestoreKeyboardLayout = false;
+
+ if (mKeyboardLayout == aLayout) {
+ return;
+ }
+
+ mKeyboardLayout = aLayout;
+
+ 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);
+ 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(),
+ GetCharacterCodeName(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(),
+ GetCharacterCodeName(uniChars, ret).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 =
+ IsVistaOrLater() ? MAPVK_VSC_TO_VK_EX : MAPVK_VSC_TO_VK;
+ 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]));
+ }
+ // XP and Server 2003 don't support 0xE0 prefix of the scancode.
+ // Therefore, we don't need to continue on them.
+ if (!IsVistaOrLater()) {
+ break;
+ }
+ }
+ }
+}
+
+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,
+ const ModifierKeyState& aModKeyState)
+{
+ // Dead-key state should be activated at keydown.
+ if (!aNativeKey.IsKeyDownMessage()) {
+ return;
+ }
+
+ mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode);
+ mDeadKeyShiftStates.AppendElement(
+ VirtualKey::ModifierKeyStateToShiftState(aModKeyState));
+}
+
+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_WARN_IF("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,
+ GetCharacterCodeName(compositeChars, 1).get(),
+ ret <= 0 ? "''" :
+ GetCharacterCodeName(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 ? "''" :
+ GetCharacterCodeName(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;
+
+ // 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) {
+ return 0;
+ }
+ }
+ 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 0;
+ // 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;
+ }
+
+ 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);
+
+ 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;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
+ 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);
+ 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;
+ }
+ bool makeSysKeyMsg = 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();
+ }
+ }
+ for (uint32_t i = keySequence.Length(); i > 0; --i) {
+ uint8_t key = keySequence[i - 1].mGeneral;
+ uint8_t keySpecific = keySequence[i - 1].mSpecific;
+ uint16_t scanCode = keySequence[i - 1].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.
+ 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 000000000..dd2ac0bfc
--- /dev/null
+++ b/widget/windows/KeyboardLayout.h
@@ -0,0 +1,1012 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 nsIIdleServiceInternal;
+
+namespace mozilla {
+namespace widget {
+
+static const uint32_t sModifierKeyMap[][3] = {
+ { nsIWidget::CAPS_LOCK, VK_CAPITAL, 0 },
+ { nsIWidget::NUM_LOCK, VK_NUMLOCK, 0 },
+ { nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT },
+ { nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT },
+ { nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL },
+ { nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL },
+ { nsIWidget::ALT_L, VK_MENU, VK_LMENU },
+ { nsIWidget::ALT_R, VK_MENU, VK_RMENU }
+};
+
+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();
+ }
+
+ 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.
+ AutoTArray<Modifiers, 5> mModifiers;
+};
+
+struct DeadKeyEntry;
+class DeadKeyTable;
+
+
+class VirtualKey
+{
+public:
+ // 0 - Normal
+ // 1 - Shift
+ // 2 - Control
+ // 3 - Control + Shift
+ // 4 - Alt
+ // 5 - Alt + Shift
+ // 6 - Alt + Control (AltGr)
+ // 7 - Alt + Control + Shift (AltGr + Shift)
+ // 8 - CapsLock
+ // 9 - CapsLock + Shift
+ // 10 - CapsLock + Control
+ // 11 - CapsLock + Control + Shift
+ // 12 - CapsLock + Alt
+ // 13 - CapsLock + Alt + Shift
+ // 14 - CapsLock + Alt + Control (CapsLock + AltGr)
+ // 15 - CapsLock + Alt + Control + Shift (CapsLock + AltGr + Shift)
+
+ enum ShiftStateFlag
+ {
+ STATE_SHIFT = 0x01,
+ STATE_CONTROL = 0x02,
+ STATE_ALT = 0x04,
+ STATE_CAPSLOCK = 0x08
+ };
+
+ typedef uint8_t ShiftState;
+
+ static ShiftState ModifiersToShiftState(Modifiers aModifiers);
+ static ShiftState ModifierKeyStateToShiftState(
+ const ModifierKeyState& aModKeyState)
+ {
+ return ModifiersToShiftState(aModKeyState.GetModifiers());
+ }
+ static Modifiers ShiftStateToModifiers(ShiftState aShiftState);
+
+private:
+ union KeyShiftState
+ {
+ struct
+ {
+ char16_t Chars[4];
+ } Normal;
+ struct
+ {
+ const DeadKeyTable* Table;
+ char16_t DeadChar;
+ } DeadKey;
+ };
+
+ KeyShiftState mShiftStates[16];
+ uint16_t mIsDeadKey;
+
+ void SetDeadKey(ShiftState aShiftState, bool aIsDeadKey)
+ {
+ if (aIsDeadKey) {
+ mIsDeadKey |= 1 << aShiftState;
+ } else {
+ mIsDeadKey &= ~(1 << aShiftState);
+ }
+ }
+
+public:
+ static void FillKbdState(PBYTE aKbdState, const ShiftState aShiftState);
+
+ bool IsDeadKey(ShiftState aShiftState) const
+ {
+ return (mIsDeadKey & (1 << aShiftState)) != 0;
+ }
+
+ void AttachDeadKeyTable(ShiftState aShiftState,
+ const DeadKeyTable* aDeadKeyTable)
+ {
+ 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;
+ char16_t GetCompositeChar(const ModifierKeyState& aModKeyState,
+ char16_t aBaseChar) const
+ {
+ return GetCompositeChar(ModifierKeyStateToShiftState(aModKeyState),
+ aBaseChar);
+ }
+ UniCharsAndModifiers GetNativeUniChars(ShiftState aShiftState) const;
+ UniCharsAndModifiers GetNativeUniChars(
+ const ModifierKeyState& aModKeyState) const
+ {
+ return GetNativeUniChars(ModifierKeyStateToShiftState(aModKeyState));
+ }
+ 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);
+
+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;
+ 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;
+ // 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 InitWithKeyChar();
+
+ /**
+ * InitCommittedCharsAndModifiersWithFollowingCharMessages() initializes
+ * mCommittedCharsAndModifiers with mFollowingCharMsgs and aModKeyState.
+ * If mFollowingCharMsgs includes non-printable char messages, they are
+ * ignored (skipped).
+ */
+ void InitCommittedCharsAndModifiersWithFollowingCharMessages(
+ const ModifierKeyState& aModKeyState);
+
+ /**
+ * Returns true if the key event is caused by auto repeat.
+ */
+ bool IsRepeat() const
+ {
+ switch (mMsg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ case WM_DEADCHAR:
+ case WM_SYSDEADCHAR:
+ case MOZ_WM_KEYDOWN:
+ return ((mMsg.lParam & (1 << 30)) != 0);
+ case WM_APPCOMMAND:
+ if (mVirtualKeyCode) {
+ // If we can map the WM_APPCOMMAND to a virtual keycode, we can trust
+ // the result of GetKeyboardState().
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+ ::GetKeyboardState(kbdState);
+ return !!kbdState[mVirtualKeyCode];
+ }
+ // If there is no virtual keycode for the command, 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.
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ UINT GetScanCodeWithExtendedFlag() const;
+
+ // The result is one of nsIDOMKeyEvent::DOM_KEY_LOCATION_*.
+ 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;
+
+ 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 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 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);
+
+ /**
+ * Whether the key event can compute virtual keycode from the scancode value.
+ */
+ bool CanComputeVirtualKeyCodeFromScanCode() const;
+
+ /**
+ * 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 MSG* aMsgSentToPlugin = nullptr) const;
+ nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG* aMsgSentToPlugin = nullptr) const;
+
+ /**
+ * MaybeInitPluginEventOfKeyEvent() may initialize aKeyEvent::mPluginEvent
+ * with aMsgSentToPlugin if it's necessary.
+ */
+ void MaybeInitPluginEventOfKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG& aMsgSentToPlugin) 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;
+
+ /**
+ * MaybeDispatchPluginEventsForRemovedCharMessages() dispatches plugin events
+ * for removed char messages when a windowless plugin has focus.
+ * Returns true if the widget is destroyed or blurred during dispatching a
+ * plugin event.
+ */
+ bool MaybeDispatchPluginEventsForRemovedCharMessages() 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 bool IsEmptyMSG(const MSG& aMSG)
+ {
+ return !memcmp(&aMSG, &sEmptyMSG, sizeof(MSG));
+ }
+
+ bool IsAnotherInstanceRemovingCharMessage() const
+ {
+ return mLastInstance && !IsEmptyMSG(mLastInstance->mRemovingMsg);
+ }
+};
+
+class KeyboardLayout
+{
+public:
+ static KeyboardLayout* GetInstance();
+ static void Shutdown();
+ static HKL GetActiveLayout();
+ static nsCString GetActiveLayoutName();
+ static void NotifyIdleServiceOfUserActivity();
+
+ static bool IsPrintableCharKey(uint8_t aVirtualKey);
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ /**
+ * 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);
+ }
+
+ /**
+ * GetNativeUniCharsAndModifiers() returns characters which are inputted by
+ * aVirtualKey with aModKeyState. The method isn't stateful.
+ * Note that different from GetUniCharsAndModifiers(), this returns
+ * actual modifier state of Ctrl and Alt.
+ */
+ UniCharsAndModifiers GetNativeUniCharsAndModifiers(
+ uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const;
+
+ /**
+ * 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 nsIIdleServiceInternal* 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;
+
+ 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,
+ const ModifierKeyState& aModKeyState);
+ 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,
+ const ModifierKeyState& aModKeyState);
+
+ /**
+ * 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,
+ const ModifierKeyState& aModKeyState);
+
+ /**
+ * MaybeInitNativeKeyWithCompositeChar() may initialize aNativeKey with
+ * proper composite character when dead key produces a composite character.
+ * Otherwise, just returns false.
+ */
+ bool MaybeInitNativeKeyWithCompositeChar(
+ NativeKey& aNativeKey,
+ const ModifierKeyState& aModKeyState);
+
+ /**
+ * 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 000000000..de4a40d2a
--- /dev/null
+++ b/widget/windows/LSPAnnotator.cpp
@@ -0,0 +1,161 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.
+ */
+
+#if _WIN32_WINNT < 0x0600
+// Redefining _WIN32_WINNT for some Vista APIs that we call
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600
+#endif
+#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:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ void Annotate();
+ nsCString mString;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+NS_IMPL_ISUPPORTS(LSPAnnotationGatherer, nsIRunnable)
+
+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(NS_LITERAL_CSTRING("Winsock_LSP"), mString);
+ }
+ mThread->AsyncShutdown();
+}
+
+NS_IMETHODIMP
+LSPAnnotationGatherer::Run()
+{
+ PR_SetCurrentThreadName("LSP Annotator");
+
+ mThread = NS_GetCurrentThread();
+
+ 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?
+ NS_NOTREACHED("WSCEnumProtocols suceeded 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(" : ");
+ // If WSCGetProviderInfo is available, we should call it to obtain the
+ // category flags for this provider. When present, these flags inform
+ // Windows as to which order to chain the providers.
+ nsModuleHandle ws2_32(LoadLibraryW(L"ws2_32.dll"));
+ if (ws2_32) {
+ decltype(WSCGetProviderInfo)* pWSCGetProviderInfo =
+ reinterpret_cast<decltype(WSCGetProviderInfo)*>(
+ GetProcAddress(ws2_32, "WSCGetProviderInfo"));
+ if (pWSCGetProviderInfo) {
+ DWORD categoryInfo;
+ size_t categoryInfoSize = sizeof(categoryInfo);
+ if (!pWSCGetProviderInfo(&providers[i].ProviderId,
+ ProviderInfoLspCategories,
+ (PBYTE)&categoryInfo, &categoryInfoSize, 0,
+ &err)) {
+ str.AppendPrintf("0x%x", 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(this, &LSPAnnotationGatherer::Annotate));
+ return NS_OK;
+}
+
+void LSPAnnotate()
+{
+ nsCOMPtr<nsIThread> thread;
+ nsCOMPtr<nsIRunnable> runnable =
+ do_QueryObject(new LSPAnnotationGatherer());
+ NS_NewThread(getter_AddRefs(thread), runnable);
+}
+
+} // namespace crashreporter
+} // namespace mozilla
diff --git a/widget/windows/PCompositorWidget.ipdl b/widget/windows/PCompositorWidget.ipdl
new file mode 100644
index 000000000..4805c95a4
--- /dev/null
+++ b/widget/windows/PCompositorWidget.ipdl
@@ -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 protocol PCompositorBridge;
+
+using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
+
+namespace mozilla {
+namespace widget {
+
+sync protocol PCompositorWidget
+{
+ manager PCompositorBridge;
+
+parent:
+ sync EnterPresentLock();
+ sync LeavePresentLock();
+ async UpdateTransparency(int32_t aMode);
+ sync ClearTransparentWindow();
+ async __delete__();
+
+child:
+ async ObserveVsync();
+ async UnobserveVsync();
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/PlatformWidgetTypes.ipdlh b/widget/windows/PlatformWidgetTypes.ipdlh
new file mode 100644
index 000000000..aad9cf395
--- /dev/null
+++ b/widget/windows/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,23 @@
+/* -*- 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.
+
+using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace widget {
+
+struct CompositorWidgetInitData
+{
+ WindowsHandle hWnd;
+ uintptr_t widgetKey;
+ int32_t transparencyMode;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TSFTextStore.cpp b/widget/windows/TSFTextStore.cpp
new file mode 100644
index 000000000..fb0505aa3
--- /dev/null
+++ b/widget/windows/TSFTextStore.cpp
@@ -0,0 +1,6423 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsWindow.h"
+#include "nsPrintfCString.h"
+#include "WinIMEHandler.h"
+#include "WinUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsIXULRuntime.h"
+
+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");
+
+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)
+{
+ nsAutoCString 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:
+ 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*
+GetIMEEnabledName(IMEState::Enabled aIMEEnabled)
+{
+ switch (aIMEEnabled) {
+ case IMEState::DISABLED:
+ return "DISABLED";
+ case IMEState::ENABLED:
+ return "ENABLED";
+ case IMEState::PASSWORD:
+ return "PASSWORD";
+ case IMEState::PLUGIN:
+ return "PLUGIN";
+ default:
+ return "Invalid";
+ }
+}
+
+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";
+ default:
+ return "Unknown";
+ }
+}
+
+static nsCString
+GetCLSIDNameStr(REFCLSID aCLSID)
+{
+ LPOLESTR str = nullptr;
+ HRESULT hr = ::StringFromCLSID(aCLSID, &str);
+ if (FAILED(hr) || !str || !str[0]) {
+ return EmptyCString();
+ }
+
+ nsAutoCString 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 EmptyCString();
+ }
+
+ return NS_ConvertUTF16toUTF8(str);
+}
+
+static nsCString
+GetGUIDNameStrWithTable(REFGUID aGUID)
+{
+#define RETURN_GUID_NAME(aNamedGUID) \
+ if (IsEqualGUID(aGUID, aNamedGUID)) { \
+ return NS_LITERAL_CSTRING(#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 EmptyCString();
+ }
+
+ nsAutoString key(L"Interface\\");
+ key += str;
+
+ nsAutoCString 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)
+{
+ nsAutoCString 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 char*
+GetActiveSelEndName(TsActiveSelEnd aSelEnd)
+{
+ return aSelEnd == TS_AE_NONE ? "TS_AE_NONE" :
+ aSelEnd == TS_AE_START ? "TS_AE_START" :
+ aSelEnd == TS_AE_END ? "TS_AE_END" : "Unknown";
+}
+
+static const nsCString
+GetLockFlagNameStr(DWORD aLockFlags)
+{
+ nsAutoCString 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 NS_LITERAL_CSTRING("TF_CT_NONE");
+ 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 NS_LITERAL_CSTRING("TF_LS_NONE");
+ case TF_LS_SOLID:
+ return NS_LITERAL_CSTRING("TF_LS_SOLID");
+ case TF_LS_DOT:
+ return NS_LITERAL_CSTRING("TF_LS_DOT");
+ case TF_LS_DASH:
+ return NS_LITERAL_CSTRING("TF_LS_DASH");
+ case TF_LS_SQUIGGLE:
+ return NS_LITERAL_CSTRING("TF_LS_SQUIGGLE");
+ 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 NS_LITERAL_CSTRING("TF_ATTR_INPUT");
+ case TF_ATTR_TARGET_CONVERTED:
+ return NS_LITERAL_CSTRING("TF_ATTR_TARGET_CONVERTED");
+ case TF_ATTR_CONVERTED:
+ return NS_LITERAL_CSTRING("TF_ATTR_CONVERTED");
+ case TF_ATTR_TARGET_NOTCONVERTED:
+ return NS_LITERAL_CSTRING("TF_ATTR_TARGET_NOTCONVERTED");
+ case TF_ATTR_INPUT_ERROR:
+ return NS_LITERAL_CSTRING("TF_ATTR_INPUT_ERROR");
+ case TF_ATTR_FIXEDCONVERTED:
+ return NS_LITERAL_CSTRING("TF_ATTR_FIXEDCONVERTED");
+ case TF_ATTR_OTHER:
+ return NS_LITERAL_CSTRING("TF_ATTR_OTHER");
+ default: {
+ return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
+ }
+ }
+}
+
+static nsCString
+GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr)
+{
+ nsAutoCString 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 WidgetMouseEventBase::eLeftButton:
+ return "LeftButton";
+ case WidgetMouseEventBase::eMiddleButton:
+ return "MiddleButton";
+ case WidgetMouseEventBase::eRightButton:
+ 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 NS_LITERAL_CSTRING("no buttons");
+ }
+ nsAutoCString names;
+ if (aButtons & WidgetMouseEventBase::eLeftButtonFlag) {
+ names = "LeftButton";
+ }
+ if (aButtons & WidgetMouseEventBase::eRightButtonFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "RightButton";
+ }
+ if (aButtons & WidgetMouseEventBase::eMiddleButtonFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "MiddleButton";
+ }
+ if (aButtons & WidgetMouseEventBase::e4thButtonFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "4thButton";
+ }
+ if (aButtons & WidgetMouseEventBase::e5thButtonFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "5thButton";
+ }
+ return names;
+}
+
+static nsCString
+GetModifiersName(Modifiers aModifiers)
+{
+ if (aModifiers == MODIFIER_NONE) {
+ return NS_LITERAL_CSTRING("no modifiers");
+ }
+ nsAutoCString 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:
+ 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");
+ }
+};
+
+/******************************************************************/
+/* InputScopeImpl */
+/******************************************************************/
+
+class InputScopeImpl final : public ITfInputScope
+{
+ ~InputScopeImpl() {}
+
+public:
+ InputScopeImpl(const nsTArray<InputScope>& aList)
+ : mInputScopes(aList)
+ {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p InputScopeImpl()", this));
+ }
+
+ 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) {
+ sInstance = new TSFStaticSink();
+ }
+ 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()
+ {
+ 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);
+ }
+
+ bool EnsureInitActiveTIPKeyboard();
+
+ /****************************************************************************
+ * Japanese TIP
+ ****************************************************************************/
+
+ // Note that TIP name may depend on the language of the environment.
+ // For example, some TIP may use localized name for its target language
+ // environment but English name for the others.
+
+ bool IsMSJapaneseIMEActive() const
+ {
+ // FYI: Name of MS-IME for Japanese is same as MS-IME for Korean.
+ // Therefore, we need to check the langid too.
+ return mLangID == 0x411 &&
+ (mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft IME") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"Microsoft \xC785\xB825\xAE30")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x8F93\x5165\x6CD5")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x8F38\x5165\x6CD5")));
+ }
+
+ bool IsMSOfficeJapaneseIME2010Active() const
+ {
+ // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
+ static const GUID kGUID = {
+ 0x54EDCC94, 0x1524, 0x4BB1,
+ { 0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64 }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOKActive() const
+ {
+ // FYI: Name of ATOK includes the release year like "ATOK 2015".
+ return StringBeginsWith(mActiveTIPKeyboardDescription,
+ NS_LITERAL_STRING("ATOK "));
+ }
+
+ bool IsATOK2011Active() const
+ {
+ // {F9C24A5C-8A53-499D-9572-93B2FF582115}
+ static const GUID kGUID = {
+ 0xF9C24A5C, 0x8A53, 0x499D,
+ { 0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15 }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2012Active() const
+ {
+ // {1DE01562-F445-401B-B6C3-E5B18DB79461}
+ static const GUID kGUID = {
+ 0x1DE01562, 0xF445, 0x401B,
+ { 0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61 }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2013Active() const
+ {
+ // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
+ static const GUID kGUID = {
+ 0x3C4DB511, 0x189A, 0x4168,
+ { 0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15 }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2014Active() const
+ {
+ // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
+ static const GUID kGUID = {
+ 0x4EF33B79, 0x6AA9, 0x4271,
+ { 0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2015Active() const
+ {
+ // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
+ static const GUID kGUID = {
+ 0xEAB4DC00, 0xCE2E, 0x483D,
+ { 0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2016Active() const
+ {
+ // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
+ static const GUID kGUID = {
+ 0x0B557B4C, 0x5740, 0x4110,
+ { 0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ // Note that ATOK 2011 - 2016 refers native caret position for deciding its
+ // popup window position.
+ bool IsATOKReferringNativeCaretActive() const
+ {
+ return IsATOKActive() &&
+ (IsATOK2011Active() || IsATOK2012Active() || IsATOK2013Active() ||
+ IsATOK2014Active() || IsATOK2015Active() || IsATOK2016Active());
+ }
+
+ /****************************************************************************
+ * Traditional Chinese TIP
+ ****************************************************************************/
+
+ bool IsMSChangJieActive() const
+ {
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft ChangJie") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x4ED3\x9889")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x5009\x9821"));
+ }
+
+ bool IsMSQuickQuickActive() const
+ {
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft Quick") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x901F\x6210")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x901F\x6210"));
+ }
+
+ bool IsFreeChangJieActive() const
+ {
+ // FYI: The TIP name is misspelled...
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Free CangJie IME 10");
+ }
+
+ bool IsEasyChangjeiActive() const
+ {
+ return
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(
+ u"\x4E2D\x6587 (\x7E41\x9AD4) - \x6613\x9821\x8F38\x5165\x6CD5"));
+ }
+
+ /****************************************************************************
+ * Simplified Chinese TIP
+ ****************************************************************************/
+
+ bool IsMSPinyinActive() const
+ {
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft Pinyin") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x62FC\x97F3")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x62FC\x97F3"));
+ }
+
+ bool IsMSWubiActive() const
+ {
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft Wubi") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x4E94\x7B14")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x4E94\x7B46"));
+ }
+
+public: // ITfInputProcessorProfileActivationSink
+ STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID,
+ HKL, DWORD);
+
+private:
+ TSFStaticSink();
+ virtual ~TSFStaticSink() {}
+
+ void Destroy();
+
+ void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile, nsAString& aDescription);
+ bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile);
+
+ // 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
+ GUID mActiveTIPGUID;
+
+ static StaticRefPtr<TSFStaticSink> sInstance;
+};
+
+StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
+
+TSFStaticSink::TSFStaticSink()
+ : 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;
+ mActiveTIPGUID = guidProfile;
+ mLangID = langid;
+ mIsIMM_IME = IsIMM_IME(hkl);
+ GetTIPDescription(rclsid, mLangID, guidProfile,
+ mActiveTIPKeyboardDescription);
+ // 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;
+}
+
+/******************************************************************/
+/* TSFTextStore */
+/******************************************************************/
+
+StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr;
+StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump;
+StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
+StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
+StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
+StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
+StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
+StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
+StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
+DWORD TSFTextStore::sClientId = 0;
+
+bool TSFTextStore::sCreateNativeCaretForLegacyATOK = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToATOKOfCompositionString = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSSimplifiedTIP = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSTraditionalTIP = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToFreeChangJie = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToEasyChangjei = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret = false;
+bool TSFTextStore::sHackQueryInsertForMSSimplifiedTIP = false;
+bool TSFTextStore::sHackQueryInsertForMSTraditionalTIP = false;
+
+#define TEXTSTORE_DEFAULT_VIEW (1)
+
+TSFTextStore::TSFTextStore()
+ : mEditCookie(0)
+ , mSinkMask(0)
+ , mLock(0)
+ , mLockQueued(0)
+ , mHandlingKeyMessage(0)
+ , mContentForTSF(mComposition, mSelectionForTSF)
+ , mRequestedAttrValues(false)
+ , mIsRecordingActionsWithoutLock(false)
+ , mHasReturnedNoLayoutError(false)
+ , mWaitingQueryLayout(false)
+ , mPendingDestroy(false)
+ , mDeferClearingContentForTSF(false)
+ , mNativeCaretIsCreated(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;
+ }
+
+ TSFStaticSink::GetInstance()->EnsureInitActiveTIPKeyboard();
+
+ 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);
+
+ // 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.IsComposing()=%s, mHandlingKeyMessage=%u",
+ this, GetLockFlagNameStr(mLock).get(),
+ GetBoolName(mComposition.IsComposing()),
+ 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.
+ 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.IsComposing()) {
+ 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.IsInitialized() || mSelectionForTSF.IsDirty())) {
+ 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;
+ }
+
+ RefPtr<nsWindowBase> kungFuDeathGrip(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::COMPOSITION_START: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing COMPOSITION_START={ 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, mWidget);
+ mWidget->InitEvent(selectionSet);
+ selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = false;
+ 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 = mWidget->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, "
+ "IsComposingInContent()=%s",
+ this, GetBoolName(!IsComposingInContent())));
+ mDeferClearingContentForTSF = !IsComposingInContent();
+ }
+ if (!mWidget || mWidget->Destroyed()) {
+ break;
+ }
+ break;
+ }
+ case PendingAction::COMPOSITION_UPDATE: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing COMPOSITION_UPDATE={ 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... "
+ "IsComposingInContent()=%s",
+ this, GetBoolName(IsComposingInContent())));
+ mDeferClearingContentForTSF = !IsComposingInContent();
+ } else {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "dispatching compositionchange event...", this));
+ WidgetEventTime eventTime = mWidget->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, "
+ "IsComposingInContent()=%s",
+ this, GetBoolName(IsComposingInContent())));
+ mDeferClearingContentForTSF = !IsComposingInContent();
+ }
+ // Be aware, the mWidget might already have been destroyed.
+ }
+ break;
+ }
+ case PendingAction::COMPOSITION_END: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing COMPOSITION_END={ 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.
+ // In this case, we should not clear mContentForTSFuntil we notify
+ // the IME of the composition update.
+ mDeferClearingContentForTSF = true;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions(), "
+ "dispatching compositioncommit event...", this));
+ WidgetEventTime eventTime = mWidget->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, "
+ "IsComposingInContent()=%s",
+ this, GetBoolName(IsComposingInContent())));
+ mDeferClearingContentForTSF = !IsComposingInContent();
+ }
+ break;
+ }
+ case PendingAction::SET_SELECTION: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing SET_SELECTION={ 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, mWidget);
+ selectionSet.mOffset =
+ static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength =
+ static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = action.mSelectionReversed;
+ break;
+ }
+ default:
+ MOZ_CRASH("unexpected action type");
+ }
+
+ if (mWidget && !mWidget->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.IsInitialized()) {
+ mContentForTSF.Clear();
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "mContentForTSF is cleared", 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.IsInitialized()) {
+ 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();
+ }
+}
+
+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;
+ }
+ pdcs->dwDynamicFlags = 0;
+ // 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
+ const TSFStaticSink* kSink = TSFStaticSink::GetInstance();
+ if (IsWin8OrLater() && !mComposition.IsComposing() &&
+ ((sHackQueryInsertForMSTraditionalTIP &&
+ (kSink->IsMSChangJieActive() || kSink->IsMSQuickQuickActive())) ||
+ (sHackQueryInsertForMSSimplifiedTIP &&
+ (kSink->IsMSPinyinActive() || kSink->IsMSWubiActive())))) {
+ 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;
+ }
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "SelectionForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ *pSelection = selectionForTSF.ACP();
+ *pcFetched = 1;
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection() succeeded", this));
+ return S_OK;
+}
+
+bool
+TSFTextStore::IsComposingInContent() const
+{
+ if (!mDispatcher) {
+ return false;
+ }
+ if (!mDispatcher->IsInNativeInputTransaction()) {
+ return false;
+ }
+ return mDispatcher->IsComposing();
+}
+
+TSFTextStore::Content&
+TSFTextStore::ContentForTSFRef()
+{
+ // This should be called when the document is locked or the content hasn't
+ // been abandoned yet.
+ if (NS_WARN_IF(!IsReadLocked() && !mContentForTSF.IsInitialized())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
+ "called wrong timing, IsReadLocked()=%s, "
+ "mContentForTSF.IsInitialized()=%s",
+ this, GetBoolName(IsReadLocked()),
+ GetBoolName(mContentForTSF.IsInitialized())));
+ mContentForTSF.Clear();
+ return mContentForTSF;
+ }
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
+ "SelectionForTSFRef() failure", this));
+ mContentForTSF.Clear();
+ return mContentForTSF;
+ }
+
+ if (!mContentForTSF.IsInitialized()) {
+ nsAutoString text;
+ if (NS_WARN_IF(!GetCurrentText(text))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
+ "GetCurrentText() failure", this));
+ mContentForTSF.Clear();
+ return mContentForTSF;
+ }
+
+ mContentForTSF.Init(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::ContentForTSFRef(): "
+ "mContentForTSF={ mText=\"%s\" (Length()=%u), "
+ "mLastCompositionString=\"%s\" (Length()=%u), "
+ "mMinTextModifiedOffset=%u }",
+ this, mContentForTSF.Text().Length() <= 40 ?
+ GetEscapedUTF8String(mContentForTSF.Text()).get() : "<omitted>",
+ mContentForTSF.Text().Length(),
+ GetEscapedUTF8String(mContentForTSF.LastCompositionString()).get(),
+ mContentForTSF.LastCompositionString().Length(),
+ mContentForTSF.MinTextModifiedOffset()));
+
+ return mContentForTSF;
+}
+
+bool
+TSFTextStore::CanAccessActualContentDirectly() const
+{
+ if (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty()) {
+ 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.IsInitialized()) {
+ aTextContent = mContentForTSF.Text();
+ 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 queryText(true, eQueryTextContent, mWidget);
+ queryText.InitForQueryTextContent(0, UINT32_MAX);
+ mWidget->InitEvent(queryText);
+ DispatchEvent(queryText);
+ if (NS_WARN_IF(!queryText.mSucceeded)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
+ "eQueryTextContent failure", this));
+ aTextContent.Truncate();
+ return false;
+ }
+
+ aTextContent = queryText.mReply.mString;
+ return true;
+}
+
+TSFTextStore::Selection&
+TSFTextStore::SelectionForTSFRef()
+{
+ if (mSelectionForTSF.IsDirty()) {
+ 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_CRASH();
+ }
+
+ WidgetQueryContentEvent querySelection(true, eQuerySelectedText, mWidget);
+ mWidget->InitEvent(querySelection);
+ DispatchEvent(querySelection);
+ if (NS_WARN_IF(!querySelection.mSucceeded)) {
+ return mSelectionForTSF;
+ }
+
+ mSelectionForTSF.SetSelection(querySelection.mReply.mOffset,
+ querySelection.mReply.mString.Length(),
+ querySelection.mReply.mReversed,
+ querySelection.GetWritingMode());
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::SelectionForTSFRef(): "
+ "acpStart=%d, acpEnd=%d (length=%d), reverted=%s",
+ this, mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
+ mSelectionForTSF.Length(),
+ GetBoolName(mSelectionForTSF.IsReversed())));
+
+ 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.mStart,
+ start - mComposition.mStart + 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;
+ }
+
+ NS_ENSURE_TRUE(sCategoryMgr, E_FAIL);
+ GUID guid;
+ hr = sCategoryMgr->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;
+ }
+
+ NS_ENSURE_TRUE(sDisplayAttrMgr, E_FAIL);
+ RefPtr<ITfDisplayAttributeInfo> info;
+ hr = sDisplayAttrMgr->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.mView=0x%p",
+ this, aRangeNew, mComposition.mView.get()));
+
+ if (!mComposition.IsComposing()) {
+ 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.mView);
+ 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;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
+ "range=%ld-%ld, mComposition={ mStart=%ld, mString.Length()=%lu }",
+ this, compStart, compStart + compLength, mComposition.mStart,
+ mComposition.mString.Length()));
+
+ if (mComposition.mStart != compStart ||
+ mComposition.mString.Length() != (ULONG)compLength) {
+ // 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(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(ITfCompositionView* aCompositionView,
+ ITfRange* aNewRange)
+{
+ Selection& selectionForTSF = SelectionForTSFRef();
+
+ LONG newStart, newLength;
+ HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
+ LONG newEnd = newStart + newLength;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
+ "aNewRange=0x%p { newStart=%d, newLength=%d }), "
+ "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
+ "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
+ this, aCompositionView, aNewRange, newStart, newLength,
+ mComposition.mStart, mComposition.mString.Length(),
+ GetBoolName(selectionForTSF.IsDirty()),
+ selectionForTSF.StartOffset(), selectionForTSF.Length()));
+
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to SelectionForTSFRef() 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 >= mComposition.EndOffset() || newEnd <= mComposition.mStart) {
+ RecordCompositionEndAction();
+ RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
+ return S_OK;
+ }
+
+ // 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 = mComposition;
+ Selection oldSelection = selectionForTSF;
+
+ // Commit only the part of composition.
+ LONG keepComposingStartOffset = std::max(mComposition.mStart, newStart);
+ LONG keepComposingEndOffset = std::min(mComposition.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(mComposition.mString);
+ commitString.Cut(keepComposingStartOffset - mComposition.mStart,
+ keepComposingLength);
+ // Update the composition string.
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ contentForTSF.ReplaceTextWith(mComposition.mStart,
+ mComposition.mString.Length(),
+ commitString);
+ // Record a compositionupdate action for commit the part of composing string.
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mData = mComposition.mString;
+ 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 =
+ uint32_t(oldComposition.mStart + 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.mString,
+ keepComposingStartOffset - oldComposition.mStart,
+ keepComposingLength));
+ selectionForTSF = oldSelection;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition() succeeded, "
+ "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
+ "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
+ this, mComposition.mStart, mComposition.mString.Length(),
+ GetBoolName(selectionForTSF.IsDirty()),
+ selectionForTSF.StartOffset(), selectionForTSF.Length()));
+
+ 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, uint8_t& 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={ mView=0x%p, mStart=%d, mString=\"%s\" "
+ "(Length()=%d) }",
+ this, mComposition.mView.get(), mComposition.mStart,
+ GetEscapedUTF8String(mComposition.mString).get(),
+ mComposition.mString.Length()));
+
+ if (!mComposition.IsComposing()) {
+ 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.mView->GetRange(getter_AddRefs(composingRange));
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "FAILED due to mComposition.mView->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.
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to SelectionForTSFRef() failure", this));
+ return E_FAIL;
+ }
+
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mData = mComposition.mString;
+ // 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.mStart),
+ mComposition.EndOffset());
+ LONG end = std::max(std::min(rangeStart + rangeLength,
+ mComposition.EndOffset()),
+ mComposition.mStart);
+ LONG length = end - start;
+ if (length < 0) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "ignores invalid range (%d-%d)",
+ this, rangeStart - mComposition.mStart,
+ rangeStart - mComposition.mStart + 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.mStart,
+ rangeStart - mComposition.mStart + rangeLength));
+ continue;
+ }
+
+ TextRange newRange;
+ newRange.mStartOffset = uint32_t(start - mComposition.mStart);
+ // The end of the last range in the array is
+ // always kept at the end of composition
+ newRange.mEndOffset = mComposition.mString.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.IsCollapsed() && action->mRanges->Length() == 1) {
+ TextRange& range = action->mRanges->ElementAt(0);
+ LONG start = selectionForTSF.MinOffset();
+ LONG end = selectionForTSF.MaxOffset();
+ if ((LONG)range.mStartOffset == start - mComposition.mStart &&
+ (LONG)range.mEndOffset == end - mComposition.mStart &&
+ 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.mStart);
+
+ // 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={ "
+ "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s} }, "
+ "aDispatchCompositionChangeEvent=%s), mComposition.IsComposing()=%s",
+ this, pSelection->acpStart, pSelection->acpEnd,
+ GetActiveSelEndName(pSelection->style.ase),
+ GetBoolName(pSelection->style.fInterimChar),
+ GetBoolName(aDispatchCompositionChangeEvent),
+ GetBoolName(mComposition.IsComposing())));
+
+ MOZ_ASSERT(IsReadWriteLocked());
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "SelectionForTSFRef() failure", 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::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
+ "did nothing because the selection range isn't changing", this));
+ selectionForTSF.SetSelection(*pSelection);
+ return S_OK;
+ }
+
+ if (mComposition.IsComposing()) {
+ 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.mStart ||
+ 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.IsInitialized() &&
+ 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::SET_SELECTION;
+ 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=%p { "
+ "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s } }), "
+ "mComposition.IsComposing()=%s",
+ this, ulCount, pSelection,
+ pSelection ? pSelection->acpStart : 0,
+ pSelection ? pSelection->acpEnd : 0,
+ pSelection ? GetActiveSelEndName(pSelection->style.ase) : "",
+ pSelection ? GetBoolName(pSelection->style.fInterimChar) : "",
+ GetBoolName(mComposition.IsComposing())));
+
+ 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={ mStart=%ld, "
+ "mString.Length()=%lu, IsComposing()=%s }",
+ this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut,
+ prgRunInfo, ulRunInfoReq, pulRunInfoOut, pacpNext,
+ mComposition.mStart, mComposition.mString.Length(),
+ GetBoolName(mComposition.IsComposing())));
+
+ 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;
+ }
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ if (contentForTSF.Text().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.Text().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.Text().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.Text().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.IsComposing()=%s",
+ this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" :
+ "not-specified",
+ acpStart, acpEnd, pchText,
+ pchText && cch ?
+ GetEscapedUTF8String(pchText, cch).get() : "",
+ cch, pChange, GetBoolName(mComposition.IsComposing())));
+
+ // 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;
+}
+
+void
+TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
+ const nsString& aHTMLInputInputMode)
+{
+ mInputScopes.Clear();
+ if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) {
+ if (aHTMLInputInputMode.EqualsLiteral("url")) {
+ mInputScopes.AppendElement(IS_URL);
+ } else if (aHTMLInputInputMode.EqualsLiteral("email")) {
+ mInputScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
+ } else if (aHTMLInputType.EqualsLiteral("tel")) {
+ mInputScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
+ mInputScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
+ } else if (aHTMLInputType.EqualsLiteral("numeric")) {
+ mInputScopes.AppendElement(IS_NUMBER);
+ }
+ return;
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
+ if (aHTMLInputType.EqualsLiteral("url")) {
+ mInputScopes.AppendElement(IS_URL);
+ } else if (aHTMLInputType.EqualsLiteral("search")) {
+ mInputScopes.AppendElement(IS_SEARCH);
+ } else if (aHTMLInputType.EqualsLiteral("email")) {
+ mInputScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
+ } else if (aHTMLInputType.EqualsLiteral("password")) {
+ mInputScopes.AppendElement(IS_PASSWORD);
+ } else if (aHTMLInputType.EqualsLiteral("datetime") ||
+ aHTMLInputType.EqualsLiteral("datetime-local")) {
+ mInputScopes.AppendElement(IS_DATE_FULLDATE);
+ mInputScopes.AppendElement(IS_TIME_FULLTIME);
+ } else if (aHTMLInputType.EqualsLiteral("date") ||
+ aHTMLInputType.EqualsLiteral("month") ||
+ aHTMLInputType.EqualsLiteral("week")) {
+ mInputScopes.AppendElement(IS_DATE_FULLDATE);
+ } else if (aHTMLInputType.EqualsLiteral("time")) {
+ mInputScopes.AppendElement(IS_TIME_FULLTIME);
+ } else if (aHTMLInputType.EqualsLiteral("tel")) {
+ mInputScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
+ mInputScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
+ } else if (aHTMLInputType.EqualsLiteral("number")) {
+ mInputScopes.AppendElement(IS_NUMBER);
+ }
+}
+
+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: {
+ Selection& selectionForTSF = SelectionForTSFRef();
+ paAttrVals[count].varValue.vt = VT_BOOL;
+ paAttrVals[count].varValue.boolVal =
+ selectionForTSF.GetWritingMode().IsVertical() ? VARIANT_TRUE :
+ VARIANT_FALSE;
+ break;
+ }
+ case eTextOrientation: {
+ Selection& selectionForTSF = SelectionForTSFRef();
+ paAttrVals[count].varValue.vt = VT_I4;
+ paAttrVals[count].varValue.lVal =
+ 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;
+ }
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ *pacp = static_cast<LONG>(contentForTSF.Text().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.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 charAtPt(true, eQueryCharacterAtPoint, mWidget);
+ mWidget->InitEvent(charAtPt, &ourPt);
+
+ // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
+ // may cause focus change or something.
+ RefPtr<TSFTextStore> kungFuDeathGrip(this);
+ DispatchEvent(charAtPt);
+ 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(), charAtPt={ "
+ "mSucceeded=%s, mReply={ mOffset=%u, mTentativeCaretOffset=%u }}",
+ this, GetBoolName(charAtPt.mSucceeded), charAtPt.mReply.mOffset,
+ charAtPt.mReply.mTentativeCaretOffset));
+
+ if (NS_WARN_IF(!charAtPt.mSucceeded)) {
+ 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) &&
+ charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) {
+ 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.
+ if (NS_WARN_IF(charAtPt.mReply.mTentativeCaretOffset ==
+ WidgetQueryContentEvent::NOT_FOUND)) {
+ charAtPt.mReply.mTentativeCaretOffset = 0;
+ }
+
+ 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 = charAtPt.mReply.mTentativeCaretOffset;
+ } else if (charAtPt.mReply.mOffset != WidgetQueryContentEvent::NOT_FOUND) {
+ // Otherwise, we should return character offset whose bounding box contains
+ // the point.
+ offset = charAtPt.mReply.mOffset;
+ } 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 = charAtPt.mReply.mTentativeCaretOffset;
+
+ // However, if it's after the last character, we need to decrement the
+ // offset.
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ if (contentForTSF.Text().Length() <= offset) {
+ // If the tentative caret is after the last character, let's return
+ // the last character's offset.
+ offset = contentForTSF.Text().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), "
+ "mDeferNotifyingTSF=%s, mWaitingQueryLayout=%s",
+ this, vcView, acpStart, acpEnd, prc, pfClipped,
+ GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout)));
+
+ 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;
+ }
+
+ 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;
+
+ // NOTE: TSF (at least on Win 8.1) doesn't return TS_E_NOLAYOUT to the
+ // caller even if we return it. It's converted to just E_FAIL.
+ // However, this is fixed on Win 10.
+
+ bool dontReturnNoLayoutError = false;
+
+ const TSFStaticSink* kSink = TSFStaticSink::GetInstance();
+ if (mComposition.IsComposing() && mComposition.mStart < acpEnd &&
+ mContentForTSF.IsLayoutChangedAt(acpEnd)) {
+ const Selection& selectionForTSF = SelectionForTSFRef();
+ // 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.
+ const bool kIsMSOfficeJapaneseIME2010 =
+ kSink->IsMSOfficeJapaneseIME2010Active();
+ // 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.
+ if (kIsMSOfficeJapaneseIME2010 ||
+ ((sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar ||
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) &&
+ kSink->IsMSJapaneseIMEActive())) {
+ // 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 ((kIsMSOfficeJapaneseIME2010 ||
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar) &&
+ acpStart < acpEnd) {
+ acpEnd = acpStart;
+ dontReturnNoLayoutError = true;
+ }
+ // 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 ((kIsMSOfficeJapaneseIME2010 ||
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) &&
+ acpStart == acpEnd &&
+ selectionForTSF.IsCollapsed() &&
+ selectionForTSF.EndOffset() == acpEnd) {
+ if (mContentForTSF.MinOffsetOfLayoutChanged() > 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.MinOffsetOfLayoutChanged()=%u",
+ this, mContentForTSF.MinOffsetOfLayoutChanged()));
+ return E_FAIL;
+ }
+ int32_t minOffsetOfLayoutChanged =
+ static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged());
+ acpEnd = acpStart = std::max(minOffsetOfLayoutChanged - 1, 0);
+ dontReturnNoLayoutError = true;
+ }
+ }
+ // 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
+ // composition string.
+ // XXX 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.
+ else if (sDoNotReturnNoLayoutErrorToATOKOfCompositionString &&
+ kSink->IsATOKActive() &&
+ (!kSink->IsATOKReferringNativeCaretActive() ||
+ !sCreateNativeCaretForLegacyATOK) &&
+ mComposition.mStart == acpStart &&
+ mComposition.EndOffset() == acpEnd) {
+ dontReturnNoLayoutError = true;
+ }
+ // Free ChangJie 2010 and Easy Changjei 1.0.12.0 doesn't handle
+ // ITfContextView::GetTextExt() properly. Prehaps, it's due to the bug of
+ // TSF. We need to check if this is necessary on Windows 10 before
+ // disabling this on Windows 10.
+ else if ((sDoNotReturnNoLayoutErrorToFreeChangJie &&
+ kSink->IsFreeChangJieActive()) ||
+ (sDoNotReturnNoLayoutErrorToEasyChangjei &&
+ kSink->IsEasyChangjeiActive())) {
+ acpEnd = mComposition.mStart;
+ acpStart = std::min(acpStart, acpEnd);
+ dontReturnNoLayoutError = true;
+ }
+ // Some Chinese TIPs of Microsoft doesn't show candidate window in e10s
+ // mode on Win8 or later.
+ else if (IsWin8OrLater() &&
+ ((sDoNotReturnNoLayoutErrorToMSTraditionalTIP &&
+ (kSink->IsMSChangJieActive() ||
+ kSink->IsMSQuickQuickActive())) ||
+ (sDoNotReturnNoLayoutErrorToMSSimplifiedTIP &&
+ (kSink->IsMSPinyinActive() ||
+ kSink->IsMSWubiActive())))) {
+ acpEnd = mComposition.mStart;
+ acpStart = std::min(acpStart, acpEnd);
+ dontReturnNoLayoutError = true;
+ }
+
+ // 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.
+ if (dontReturnNoLayoutError) {
+ MOZ_ASSERT(mContentForTSF.IsLayoutChanged());
+ if (mContentForTSF.MinOffsetOfLayoutChanged() > 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.MinOffsetOfLayoutChanged()=%u",
+ this, mContentForTSF.MinOffsetOfLayoutChanged()));
+ return E_FAIL;
+ }
+ // 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.MinOffsetOfLayoutChanged());
+ LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
+ if (mContentForTSF.IsLayoutChangedAt(acpStart)) {
+ // If TSF queries text rect in composition string, we should return
+ // rect at start of the composition even if its layout is changed.
+ if (acpStart >= mComposition.mStart) {
+ acpStart = mComposition.mStart;
+ }
+ // Otherwise, use first character's rect. Even if there is no
+ // characters, the query event will return caret rect instead.
+ else {
+ acpStart = lastUnmodifiedOffset;
+ }
+ MOZ_ASSERT(acpStart <= acpEnd);
+ }
+ if (mContentForTSF.IsLayoutChangedAt(acpEnd)) {
+ // Use max larger offset of last unmodified offset or acpStart which
+ // may be the first character offset of the composition string.
+ acpEnd = std::max(acpStart, lastUnmodifiedOffset);
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetTextExt() hacked the queried range "
+ "for not returning TS_E_NOLAYOUT, new values are: "
+ "acpStart=%d, acpEnd=%d", this, acpStart, acpEnd));
+ }
+ }
+
+ if (!dontReturnNoLayoutError && 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 event(true, eQueryTextRect, mWidget);
+ mWidget->InitEvent(event);
+
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = acpStart;
+ if (mComposition.IsComposing()) {
+ // 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.mStart;
+ } 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);
+ event.InitForQueryTextRect(startOffset, length, options);
+
+ DispatchEvent(event);
+ if (NS_WARN_IF(!event.mSucceeded)) {
+ 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 (event.mReply.mRect.width <= 0)
+ event.mReply.mRect.width = 1;
+ if (event.mReply.mRect.height <= 0)
+ event.mReply.mRect.height = 1;
+
+ // convert to unclipped screen rect
+ nsWindow* refWindow = static_cast<nsWindow*>(
+ event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWidget);
+ // 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;
+ }
+
+ event.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, event.mReply.mRect.x, event.mReply.mRect.y,
+ event.mReply.mRect.XMost(), event.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. Therefore, we need to create native caret only when ATOK 2011 -
+ // 2016 is active.
+ if (sCreateNativeCaretForLegacyATOK &&
+ kSink->IsATOKReferringNativeCaretActive() &&
+ mComposition.IsComposing() &&
+ mComposition.mStart <= acpStart && mComposition.EndOffset() >= acpStart &&
+ mComposition.mStart <= acpEnd && mComposition.EndOffset() >= 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;
+}
+
+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->left = 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 event(true, eQueryEditorRect, mWidget);
+ mWidget->InitEvent(event);
+ DispatchEvent(event);
+ if (!event.mSucceeded) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
+ "eQueryEditorRect failure", this));
+ return false;
+ }
+
+ nsWindow* refWindow = static_cast<nsWindow*>(
+ event.mReply.mFocusedWidget ?
+ event.mReply.mFocusedWidget : mWidget);
+ // 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(event.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), IsComposing()=%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,
+ GetBoolName(mComposition.IsComposing())));
+
+ 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
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "SelectionForTSFRef() 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), IsComposing=%s",
+ this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
+ GetBoolName(mComposition.IsComposing())));
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
+ "due to ContentForTSFRef() failure()", this));
+ return false;
+ }
+
+ TS_SELECTION_ACP oldSelection = contentForTSF.Selection().ACP();
+ if (!mComposition.IsComposing()) {
+ // Use a temporary composition to contain the text
+ PendingAction* compositionStart = mPendingActions.AppendElement();
+ compositionStart->mType = PendingAction::COMPOSITION_START;
+ compositionStart->mSelectionStart = oldSelection.acpStart;
+ compositionStart->mSelectionLength =
+ oldSelection.acpEnd - oldSelection.acpStart;
+ compositionStart->mAdjustSelection = false;
+
+ PendingAction* compositionEnd = mPendingActions.AppendElement();
+ compositionEnd->mType = PendingAction::COMPOSITION_END;
+ compositionEnd->mData = aInsertStr;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
+ "appending pending compositionstart and compositionend... "
+ "PendingCompositionStart={ mSelectionStart=%d, "
+ "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" "
+ "(Length()=%u) }",
+ this, compositionStart->mSelectionStart,
+ compositionStart->mSelectionLength,
+ GetEscapedUTF8String(compositionEnd->mData).get(),
+ compositionEnd->mData.Length()));
+ }
+
+ 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* aComposition,
+ ITfRange* aRange,
+ bool aPreserveSelection)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aComposition=0x%p, aRange=0x%p, aPreserveSelection=%s), "
+ "mComposition.mView=0x%p",
+ this, aComposition, aRange, GetBoolName(aPreserveSelection),
+ mComposition.mView.get()));
+
+ 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(aComposition, start, length,
+ aPreserveSelection);
+}
+
+HRESULT
+TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition,
+ LONG aStart,
+ LONG aLength,
+ bool aPreserveSelection)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aComposition=0x%p, aStart=%d, aLength=%d, aPreserveSelection=%s), "
+ "mComposition.mView=0x%p",
+ this, aComposition, aStart, aLength, GetBoolName(aPreserveSelection),
+ mComposition.mView.get()));
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+
+ CompleteLastActionIfStillIncomplete();
+
+ // TIP may have inserted text at selection before calling
+ // OnStartComposition(). In this case, we've already created a pair of
+ // pending compositionstart and pending compositionend. If the pending
+ // compositionstart occurred same range as this composition, it was the
+ // start of this composition. In such case, we should cancel the pending
+ // compositionend and start composition normally.
+ if (!aPreserveSelection &&
+ WasTextInsertedWithoutCompositionAt(aStart, aLength)) {
+ const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
+ const PendingAction& pendingCompositionStart =
+ mPendingActions[mPendingActions.Length() - 2];
+ contentForTSF.RestoreCommittedComposition(
+ aComposition, pendingCompositionStart, pendingCompositionEnd);
+ mPendingActions.RemoveElementAt(mPendingActions.Length() - 1);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() "
+ "succeeded: restoring the committed string as composing string, "
+ "mComposition={ mStart=%ld, mString.Length()=%ld, "
+ "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
+ "style.fInterimChar=%s } }",
+ this, mComposition.mStart, mComposition.mString.Length(),
+ mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
+ GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
+ GetBoolName(mSelectionForTSF.IsInterimChar())));
+ return S_OK;
+ }
+
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::COMPOSITION_START;
+ action->mSelectionStart = aStart;
+ action->mSelectionLength = aLength;
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to SelectionForTSFRef() 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(aComposition, *action, aPreserveSelection);
+ action->mData = mComposition.mString;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
+ "mComposition={ mStart=%ld, mString.Length()=%ld, "
+ "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
+ "style.fInterimChar=%s } }",
+ this, mComposition.mStart, mComposition.mString.Length(),
+ mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
+ GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
+ GetBoolName(mSelectionForTSF.IsInterimChar())));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RecordCompositionEndAction()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), "
+ "mComposition={ mView=0x%p, mString=\"%s\" }",
+ this, mComposition.mView.get(),
+ GetEscapedUTF8String(mComposition.mString).get()));
+
+ MOZ_ASSERT(mComposition.IsComposing());
+
+ CompleteLastActionIfStillIncomplete();
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::COMPOSITION_END;
+ action->mData = mComposition.mString;
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
+ "to ContentForTSFRef() 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::COMPOSITION_START) {
+ if (pendingAction.mData != action->mData) {
+ break;
+ }
+ // When only setting selection is necessary, we should append it.
+ if (pendingAction.mAdjustSelection) {
+ PendingAction* setSelection = mPendingActions.AppendElement();
+ setSelection->mType = PendingAction::SET_SELECTION;
+ setSelection->mSelectionStart = pendingAction.mSelectionStart;
+ setSelection->mSelectionLength = pendingAction.mSelectionLength;
+ 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.mView=0x%p",
+ this, pComposition, pfOk, mComposition.mView.get()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ *pfOk = FALSE;
+
+ // Only one composition at a time
+ if (mComposition.IsComposing()) {
+ 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.mView=0x%p",
+ this, pComposition, pRangeNew, mComposition.mView.get()));
+
+ 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.IsComposing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "no active composition", this));
+ return E_UNEXPECTED;
+ }
+ if (mComposition.mView != 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) {
+ 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)) {
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "SelectionForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
+ "mComposition={ mStart=%ld, mString=\"%s\" }, "
+ "SelectionForTSFRef()={ acpStart=%ld, acpEnd=%ld, style.ase=%s }",
+ this, mComposition.mStart,
+ GetEscapedUTF8String(mComposition.mString).get(),
+ selectionForTSF.StartOffset(), selectionForTSF.EndOffset(),
+ GetActiveSelEndName(selectionForTSF.ActiveSelEnd())));
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnEndComposition(ITfCompositionView* pComposition)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
+ "mComposition={ mView=0x%p, mString=\"%s\" }",
+ this, pComposition, mComposition.mView.get(),
+ GetEscapedUTF8String(mComposition.mString).get()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ if (!mComposition.IsComposing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "no active composition", this));
+ return E_UNEXPECTED;
+ }
+
+ if (mComposition.mView != 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={ mIMEState={ mEnabled=%s }, "
+ "mHTMLInputType=\"%s\" }), "
+ "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
+ GetBoolName(aGotFocus), aFocusedWidget,
+ GetIMEEnabledName(aContext.mIMEState.mEnabled),
+ NS_ConvertUTF16toUTF8(aContext.mHTMLInputType).get(),
+ sThreadMgr.get(), sEnabledTextStore.get()));
+
+ if (NS_WARN_IF(!IsInTSFMode())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
+ RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
+
+ // If currently sEnableTextStore has focus, notifies TSF of losing focus.
+ if (ThinksHavingFocus()) {
+ 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");
+ }
+
+ // If there is sEnabledTextStore, we don't use it in the new focused editor.
+ // 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 == IMEState::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;
+ {
+ // Windows 10's softwware keyboard requires that SetSelection must be
+ // always successful into SetFocus. If returning error, it might crash
+ // into TextInputFramework.dll.
+ AutoSetTemporarySelection setSelection(textStore->SelectionForTSFRef());
+
+ 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
+nsIMEUpdatePreference
+TSFTextStore::GetIMEUpdatePreference()
+{
+ if (sThreadMgr && sEnabledTextStore && sEnabledTextStore->mDocumentMgr) {
+ RefPtr<ITfDocumentMgr> docMgr;
+ sThreadMgr->GetFocus(getter_AddRefs(docMgr));
+ if (docMgr == sEnabledTextStore->mDocumentMgr) {
+ return nsIMEUpdatePreference(
+ nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE |
+ nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE |
+ nsIMEUpdatePreference::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
+ nsIMEUpdatePreference::NOTIFY_DURING_DEACTIVE);
+ }
+ }
+ return nsIMEUpdatePreference();
+}
+
+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={ mStartOffset=%lu, "
+ "mRemovedEndOffset=%lu, mAddedEndOffset=%lu, "
+ "mCausedOnlyByComposition=%s, "
+ "mIncludingChangesDuringComposition=%s, "
+ "mIncludingChangesWithoutComposition=%s }), "
+ "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
+ "mComposition.IsComposing()=%s",
+ this, aIMENotification.mMessage,
+ textChangeData.mStartOffset,
+ textChangeData.mRemovedEndOffset,
+ textChangeData.mAddedEndOffset,
+ GetBoolName(textChangeData.mCausedOnlyByComposition),
+ GetBoolName(textChangeData.mIncludingChangesDuringComposition),
+ GetBoolName(textChangeData.mIncludingChangesWithoutComposition),
+ GetBoolName(mDestroyed),
+ mSink.get(),
+ GetSinkMaskNameStr(mSinkMask).get(),
+ GetBoolName(mComposition.IsComposing())));
+
+ 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.IsComposing());
+ 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.MarkDirty();
+
+ // 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={ mOffset=%lu, "
+ "Length()=%lu, mReversed=%s, mWritingMode=%s, "
+ "mCausedByComposition=%s, mCausedBySelectionEvent=%s, "
+ "mOccurredDuringComposition=%s } }), mDestroyed=%s, "
+ "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
+ "mComposition.IsComposing()=%s",
+ this, selectionChangeData.mOffset, selectionChangeData.Length(),
+ GetBoolName(selectionChangeData.mReversed),
+ GetWritingModeName(selectionChangeData.GetWritingMode()).get(),
+ GetBoolName(selectionChangeData.mCausedByComposition),
+ GetBoolName(selectionChangeData.mCausedBySelectionEvent),
+ GetBoolName(selectionChangeData.mOccurredDuringComposition),
+ GetBoolName(mDestroyed),
+ mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
+ GetBoolName(mIsRecordingActionsWithoutLock),
+ GetBoolName(mComposition.IsComposing())));
+
+ 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();
+
+ return NS_OK;
+}
+
+void
+TSFTextStore::NotifyTSFOfSelectionChange()
+{
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(!IsReadLocked());
+ MOZ_ASSERT(!mComposition.IsComposing());
+ MOZ_ASSERT(mPendingSelectionChangeData.IsValid());
+
+ // If selection range isn't actually changed, we don't need to notify TSF
+ // of this selection change.
+ 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.IsInitialized()) {
+ mContentForTSF.OnLayoutChanged();
+ }
+
+ // Now, the caret position is different from ours. Destroy the native caret
+ // if there is.
+ 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;
+ }
+
+ // 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 until
+ // starting next composition.
+ if (!mComposition.IsComposing() && !IsComposingInContent()) {
+ mDeferClearingContentForTSF = false;
+ }
+ mDeferNotifyingTSF = false;
+ MaybeFlushPendingNotifications();
+ 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;
+ 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 WidgetMouseEventBase::eLeftButton:
+ buttonStatus = MK_LBUTTON;
+ break;
+ case WidgetMouseEventBase::eMiddleButton:
+ buttonStatus = MK_MBUTTON;
+ break;
+ case WidgetMouseEventBase::eRightButton:
+ 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.InRange(offset)) {
+ continue;
+ }
+ if (tracker.OnMouseButtonEvent(edge - tracker.RangeStart(),
+ quadrant, buttonStatus)) {
+ return NS_SUCCESS_EVENT_CONSUMED;
+ }
+ }
+ return NS_OK;
+}
+
+void
+TSFTextStore::CreateNativeCaret()
+{
+ MaybeDestroyNativeCaret();
+
+ // Don't create native caret after destroyed.
+ if (mDestroyed) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CreateNativeCaret(), "
+ "mComposition.IsComposing()=%s",
+ this, GetBoolName(mComposition.IsComposing())));
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "SelectionForTSFRef() failure", this));
+ return;
+ }
+
+ WidgetQueryContentEvent queryCaretRect(true, eQueryCaretRect, mWidget);
+ mWidget->InitEvent(queryCaretRect);
+
+ 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.IsComposing()) {
+ // 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.mStart;
+ } 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 -= mSelectionForTSF.StartOffset();
+ }
+ queryCaretRect.InitForQueryCaretRect(caretOffset, options);
+
+ DispatchEvent(queryCaretRect);
+ if (NS_WARN_IF(!queryCaretRect.mSucceeded)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "eQueryCaretRect failure (offset=%d)", this, caretOffset));
+ return;
+ }
+
+ LayoutDeviceIntRect& caretRect = queryCaretRect.mReply.mRect;
+ mNativeCaretIsCreated = ::CreateCaret(mWidget->GetWindowHandle(), nullptr,
+ caretRect.width, caretRect.height);
+ if (!mNativeCaretIsCreated) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "CreateCaret() failure", this));
+ return;
+ }
+
+ nsWindow* window = static_cast<nsWindow*>(mWidget.get());
+ nsWindow* toplevelWindow = window->GetTopLevelWindow(false);
+ if (!toplevelWindow) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "no top level window", this));
+ return;
+ }
+
+ if (toplevelWindow != window) {
+ caretRect.MoveBy(toplevelWindow->WidgetToScreenOffset());
+ caretRect.MoveBy(-window->WidgetToScreenOffset());
+ }
+
+ ::SetCaretPos(caretRect.x, caretRect.y);
+}
+
+void
+TSFTextStore::MaybeDestroyNativeCaret()
+{
+ if (!mNativeCaretIsCreated) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDestroyNativeCaret(), "
+ "destroying native caret", this));
+
+ ::DestroyCaret();
+ mNativeCaretIsCreated = false;
+}
+
+void
+TSFTextStore::CommitCompositionInternal(bool aDiscard)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
+ "mSink=0x%p, mContext=0x%p, mComposition.mView=0x%p, "
+ "mComposition.mString=\"%s\"",
+ this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
+ mComposition.mView.get(),
+ GetEscapedUTF8String(mComposition.mString).get()));
+
+ // 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.IsComposing() && aDiscard) {
+ LONG endOffset = mComposition.EndOffset();
+ mComposition.mString.Truncate(0);
+ // Note that don't notify TSF of text change after this is destroyed.
+ if (mSink && !mDestroyed) {
+ TS_TEXTCHANGE textChange;
+ textChange.acpStart = mComposition.mStart;
+ textChange.acpOldEnd = endOffset;
+ textChange.acpNewEnd = mComposition.mStart;
+ 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)));
+
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(sThreadMgr,
+ GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
+ getter_AddRefs(comp))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState() FAILED due to"
+ "no compartment available"));
+ return;
+ }
+
+ VARIANT variant;
+ variant.vt = VT_I4;
+ variant.lVal = aState;
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState(), setting "
+ "0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
+ variant.lVal));
+ comp->SetValue(sClientId, &variant);
+}
+
+// static
+bool
+TSFTextStore::GetIMEOpenState()
+{
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(sThreadMgr,
+ GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
+ getter_AddRefs(comp)))
+ return false;
+
+ VARIANT variant;
+ ::VariantInit(&variant);
+ if (SUCCEEDED(comp->GetValue(&variant)) && variant.vt == VT_I4)
+ return variant.lVal != 0;
+
+ ::VariantClear(&variant); // clear up in case variant.vt != VT_I4
+ return false;
+}
+
+// static
+void
+TSFTextStore::SetInputContext(nsWindowBase* aWidget,
+ const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("TSFTextStore::SetInputContext(aWidget=%p, "
+ "aContext.mIMEState.mEnabled=%s, aAction.mFocusChange=%s), "
+ "sEnabledTextStore=0x%p, ThinksHavingFocus()=%s",
+ aWidget, GetIMEEnabledName(aContext.mIMEState.mEnabled),
+ GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
+ GetBoolName(ThinksHavingFocus())));
+
+ NS_ENSURE_TRUE_VOID(IsInTSFMode());
+
+ if (aAction.mFocusChange != InputContextAction::FOCUS_NOT_CHANGED) {
+ if (sEnabledTextStore) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ textStore->SetInputScope(aContext.mHTMLInputType,
+ aContext.mHTMLInputInputmode);
+ }
+ return;
+ }
+
+ // If focus isn't actually changed but the enabled state is changed,
+ // emulate the focus move.
+ if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
+ OnFocusChange(true, aWidget, aContext);
+ } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
+ 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 =
+ IsVistaOrLater() && Preferences::GetBool(kPrefNameEnableTSF, false);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), TSF is %s",
+ enableTsf ? "enabled" : "disabled"));
+ if (!enableTsf) {
+ return;
+ }
+
+ // XXX MSDN documents that ITfInputProcessorProfiles is available only on
+ // desktop apps. However, there is no known way to obtain
+ // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
+ // instance.
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TF_InputProcessorProfiles, nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_ITfInputProcessorProfiles,
+ getter_AddRefs(inputProcessorProfiles));
+ if (FAILED(hr) || !inputProcessorProfiles) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create input processor "
+ "profiles, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfThreadMgr> threadMgr;
+ 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;
+ }
+
+ RefPtr<ITfMessagePump> messagePump;
+ hr = threadMgr->QueryInterface(IID_ITfMessagePump,
+ getter_AddRefs(messagePump));
+ if (FAILED(hr) || !messagePump) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to "
+ "QI message pump from the thread manager, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr;
+ hr = threadMgr->QueryInterface(IID_ITfKeystrokeMgr,
+ getter_AddRefs(keystrokeMgr));
+ if (FAILED(hr) || !keystrokeMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to "
+ "QI keystroke manager from 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<ITfDisplayAttributeMgr> displayAttributeMgr;
+ hr = ::CoCreateInstance(CLSID_TF_DisplayAttributeMgr, nullptr,
+ CLSCTX_INPROC_SERVER, IID_ITfDisplayAttributeMgr,
+ getter_AddRefs(displayAttributeMgr));
+ if (FAILED(hr) || !displayAttributeMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a display attribute manager instance, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfCategoryMgr> categoryMgr;
+ hr = ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr,
+ CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr,
+ getter_AddRefs(categoryMgr));
+ if (FAILED(hr) || !categoryMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a category manager instance, 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);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ (" TSFTextStore::Initialize() is creating "
+ "a TSFStaticSink instance..."));
+ TSFStaticSink* staticSink = TSFStaticSink::GetInstance();
+ if (!staticSink->Init(threadMgr, inputProcessorProfiles)) {
+ TSFStaticSink::Shutdown();
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to initialize TSFStaticSink "
+ "instance"));
+ return;
+ }
+
+ sInputProcessorProfiles = inputProcessorProfiles;
+ sThreadMgr = threadMgr;
+ sMessagePump = messagePump;
+ sKeystrokeMgr = keystrokeMgr;
+ sDisplayAttrMgr = displayAttributeMgr;
+ sCategoryMgr = categoryMgr;
+ sDisabledDocumentMgr = disabledDocumentMgr;
+ sDisabledContext = disabledContext;
+
+ sCreateNativeCaretForLegacyATOK =
+ Preferences::GetBool("intl.tsf.hack.atok.create_native_caret", true);
+ sDoNotReturnNoLayoutErrorToATOKOfCompositionString =
+ Preferences::GetBool(
+ "intl.tsf.hack.atok.do_not_return_no_layout_error_of_composition_string",
+ true);
+ sDoNotReturnNoLayoutErrorToMSSimplifiedTIP =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_simplified_chinese.do_not_return_no_layout_error",
+ true);
+ sDoNotReturnNoLayoutErrorToMSTraditionalTIP =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_traditional_chinese.do_not_return_no_layout_error",
+ true);
+ sDoNotReturnNoLayoutErrorToFreeChangJie =
+ Preferences::GetBool(
+ "intl.tsf.hack.free_chang_jie.do_not_return_no_layout_error", true);
+ sDoNotReturnNoLayoutErrorToEasyChangjei =
+ Preferences::GetBool(
+ "intl.tsf.hack.easy_changjei.do_not_return_no_layout_error", true);
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_japanese_ime."
+ "do_not_return_no_layout_error_at_first_char", true);
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_caret",
+ true);
+ sHackQueryInsertForMSSimplifiedTIP =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_simplified_chinese.query_insert_result", true);
+ sHackQueryInsertForMSTraditionalTIP =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_traditional_chinese.query_insert_result", true);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
+ "sClientId=0x%08X, sDisplayAttrMgr=0x%p, "
+ "sCategoryMgr=0x%p, sDisabledDocumentMgr=0x%p, sDisabledContext=%p, "
+ "sCreateNativeCaretForLegacyATOK=%s, "
+ "sDoNotReturnNoLayoutErrorToATOKOfCompositionString=%s, "
+ "sDoNotReturnNoLayoutErrorToFreeChangJie=%s, "
+ "sDoNotReturnNoLayoutErrorToEasyChangjei=%s, "
+ "sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar=%s, "
+ "sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret=%s",
+ sThreadMgr.get(), sClientId, sDisplayAttrMgr.get(),
+ sCategoryMgr.get(), sDisabledDocumentMgr.get(), sDisabledContext.get(),
+ GetBoolName(sCreateNativeCaretForLegacyATOK),
+ GetBoolName(sDoNotReturnNoLayoutErrorToATOKOfCompositionString),
+ GetBoolName(sDoNotReturnNoLayoutErrorToFreeChangJie),
+ GetBoolName(sDoNotReturnNoLayoutErrorToEasyChangjei),
+ GetBoolName(sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar),
+ GetBoolName(sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret)));
+}
+
+// static
+void
+TSFTextStore::Terminate()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info, ("TSFTextStore::Terminate()"));
+
+ TSFStaticSink::Shutdown();
+
+ sDisplayAttrMgr = nullptr;
+ sCategoryMgr = nullptr;
+ sEnabledTextStore = nullptr;
+ sDisabledDocumentMgr = nullptr;
+ sDisabledContext = nullptr;
+ sInputProcessorProfiles = nullptr;
+ sClientId = 0;
+ if (sThreadMgr) {
+ sThreadMgr->Deactivate();
+ sThreadMgr = nullptr;
+ sMessagePump = nullptr;
+ sKeystrokeMgr = nullptr;
+ }
+}
+
+// static
+bool
+TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg)
+{
+ if (!sKeystrokeMgr) {
+ return false; // not in TSF mode
+ }
+
+ if (aMsg.message == WM_KEYDOWN) {
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ }
+ hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage();
+ }
+ return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr);
+ }
+ if (aMsg.message == WM_KEYUP) {
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ }
+ hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage();
+ }
+ return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr);
+ }
+ 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::GetInstance()->IsMSJapaneseIMEActive();
+}
+
+/******************************************************************/
+/* TSFTextStore::Composition */
+/******************************************************************/
+
+void
+TSFTextStore::Composition::Start(ITfCompositionView* aCompositionView,
+ LONG aCompositionStartOffset,
+ const nsAString& aCompositionString)
+{
+ mView = aCompositionView;
+ mString = aCompositionString;
+ mStart = aCompositionStartOffset;
+}
+
+void
+TSFTextStore::Composition::End()
+{
+ mView = nullptr;
+ mString.Truncate();
+}
+
+/******************************************************************************
+ * TSFTextStore::Content
+ *****************************************************************************/
+
+const nsDependentSubstring
+TSFTextStore::Content::GetSelectedText() const
+{
+ MOZ_ASSERT(mInitialized);
+ 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
+{
+ MOZ_ASSERT(mInitialized);
+ return nsDependentSubstring(mText, aStart, aLength);
+}
+
+void
+TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString)
+{
+ MOZ_ASSERT(mInitialized);
+ 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(mInitialized);
+ const nsDependentSubstring replacedString =
+ GetSubstring(static_cast<uint32_t>(aStart),
+ static_cast<uint32_t>(aLength));
+ if (aReplaceString != replacedString) {
+ uint32_t firstDifferentOffset = mMinTextModifiedOffset;
+ if (mComposition.IsComposing()) {
+ // 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.mStart);
+ MOZ_ASSERT(aStart + aLength <= mComposition.EndOffset());
+ mComposition.mString.Replace(
+ static_cast<uint32_t>(aStart - mComposition.mStart),
+ 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
+ // mLastCompositionString.
+ if (mComposition.mString != mLastCompositionString) {
+ firstDifferentOffset =
+ mComposition.mStart +
+ FirstDifferentCharOffset(mComposition.mString,
+ mLastCompositionString);
+ // The previous change to the composition string is canceled.
+ if (mMinTextModifiedOffset >=
+ static_cast<uint32_t>(mComposition.mStart) &&
+ mMinTextModifiedOffset < firstDifferentOffset) {
+ mMinTextModifiedOffset = firstDifferentOffset;
+ }
+ } else if (mMinTextModifiedOffset >=
+ static_cast<uint32_t>(mComposition.mStart) &&
+ mMinTextModifiedOffset <
+ static_cast<uint32_t>(mComposition.EndOffset())) {
+ // The previous change to the composition string is canceled.
+ mMinTextModifiedOffset = firstDifferentOffset =
+ mComposition.EndOffset();
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%d, "
+ "aLength=%d, aReplaceString=\"%s\"), mComposition={ mStart=%d, "
+ "mString=\"%s\" }, mLastCompositionString=\"%s\", "
+ "mMinTextModifiedOffset=%u, firstDifferentOffset=%u",
+ this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(),
+ mComposition.mStart, GetEscapedUTF8String(mComposition.mString).get(),
+ GetEscapedUTF8String(mLastCompositionString).get(),
+ mMinTextModifiedOffset, firstDifferentOffset));
+ } else {
+ firstDifferentOffset =
+ static_cast<uint32_t>(aStart) +
+ FirstDifferentCharOffset(aReplaceString, replacedString);
+ }
+ mMinTextModifiedOffset =
+ std::min(mMinTextModifiedOffset, 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.CollapseAt(
+ static_cast<uint32_t>(aStart) + aReplaceString.Length());
+}
+
+void
+TSFTextStore::Content::StartComposition(ITfCompositionView* aCompositionView,
+ const PendingAction& aCompStart,
+ bool aPreserveSelection)
+{
+ MOZ_ASSERT(mInitialized);
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(!mComposition.mView);
+ MOZ_ASSERT(aCompStart.mType == PendingAction::COMPOSITION_START);
+
+ mComposition.Start(aCompositionView, aCompStart.mSelectionStart,
+ GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
+ static_cast<uint32_t>(aCompStart.mSelectionLength)));
+ 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.
+ mSelection.SetSelection(mComposition.mStart, mComposition.mString.Length(),
+ false, mSelection.GetWritingMode());
+ }
+}
+
+void
+TSFTextStore::Content::RestoreCommittedComposition(
+ ITfCompositionView* aCompositionView,
+ const PendingAction& aPendingCompositionStart,
+ const PendingAction& aCanceledCompositionEnd)
+{
+ MOZ_ASSERT(mInitialized);
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(!mComposition.mView);
+ MOZ_ASSERT(aPendingCompositionStart.mType ==
+ PendingAction::COMPOSITION_START);
+ MOZ_ASSERT(aCanceledCompositionEnd.mType ==
+ PendingAction::COMPOSITION_END);
+ MOZ_ASSERT(GetSubstring(
+ static_cast<uint32_t>(aPendingCompositionStart.mSelectionStart),
+ static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
+ aCanceledCompositionEnd.mData);
+
+ // Restore the committed string as composing string.
+ mComposition.Start(aCompositionView,
+ aPendingCompositionStart.mSelectionStart,
+ aCanceledCompositionEnd.mData);
+}
+
+void
+TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd)
+{
+ MOZ_ASSERT(mInitialized);
+ MOZ_ASSERT(mComposition.mView);
+ MOZ_ASSERT(aCompEnd.mType == PendingAction::COMPOSITION_END);
+
+ mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length());
+ mComposition.End();
+}
+
+/******************************************************************************
+ * TSFTextStore::MouseTracker
+ *****************************************************************************/
+
+TSFTextStore::MouseTracker::MouseTracker()
+ : mStart(-1)
+ , mLength(-1)
+ , 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;
+ }
+
+ HRESULT hr = aTextRange->GetExtent(&mStart, &mLength);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to failure of ITfRangeACP::GetExtent()", this));
+ return hr;
+ }
+
+ if (mStart < 0 || mLength <= 0) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to odd result of ITfRangeACP::GetExtent(), "
+ "mStart=%d, mLength=%d", this, mStart, mLength));
+ 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>(mStart) ||
+ textContent.Length() < static_cast<uint32_t>(mStart + mLength)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to out of range, mStart=%d, mLength=%d, "
+ "textContent.Length()=%d",
+ this, mStart, mLength, textContent.Length()));
+ return E_INVALIDARG;
+ }
+
+ mSink = aMouseSink;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
+ "succeeded, mStart=%d, mLength=%d, textContent.Length()=%d",
+ this, mStart, mLength, textContent.Length()));
+ return S_OK;
+}
+
+void
+TSFTextStore::MouseTracker::UnadviseSink()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
+ "mCookie=%d, mSink=0x%p, mStart=%d, mLength=%d",
+ this, mCookie, mSink.get(), mStart, mLength));
+ mSink = nullptr;
+ mStart = mLength = -1;
+}
+
+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()
+{
+ if (!sInputProcessorProfiles) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
+ "there is no input processor profiles instance"));
+ return false;
+ }
+ RefPtr<ITfInputProcessorProfileMgr> profileMgr;
+ HRESULT hr =
+ sInputProcessorProfiles->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
+
+} // name widget
+} // name mozilla
diff --git a/widget/windows/TSFTextStore.h b/widget/windows/TSFTextStore.h
new file mode 100644
index 000000000..9596510d5
--- /dev/null
+++ b/widget/windows/TSFTextStore.h
@@ -0,0 +1,1045 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/WindowsVersion.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;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+/*
+ * Text Services Framework text store
+ */
+
+class TSFTextStore final : public ITextStoreACP
+ , public ITfContextOwnerCompositionSink
+ , public ITfMouseTrackerACP
+{
+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 nsIMEUpdatePreference GetIMEUpdatePreference();
+
+ // 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 ITfMessagePump* GetMessagePump()
+ {
+ return sMessagePump;
+ }
+
+ 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.IsComposing());
+ }
+
+ 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();
+
+#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);
+ HRESULT RestartComposition(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* aComposition,
+ 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);
+
+ // 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();
+
+ // 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() { ++mHandlingKeyMessage; }
+ void OnEndHandlingKeyMessage()
+ {
+ 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();
+ }
+ }
+
+ class Composition final
+ {
+ public:
+ // nullptr if no composition is active, otherwise the current composition
+ RefPtr<ITfCompositionView> mView;
+
+ // Current copy of the active composition string. Only mString is
+ // changed during a InsertTextAtSelection call if we have a composition.
+ // mString acts as a buffer until OnUpdateComposition is called
+ // and mString is flushed to editor through eCompositionChange.
+ // This way all changes are updated in batches to avoid
+ // inconsistencies/artifacts.
+ nsString mString;
+
+ // The start of the current active composition, in ACP offsets
+ LONG mStart;
+
+ bool IsComposing() const
+ {
+ return (mView != nullptr);
+ }
+
+ LONG EndOffset() const
+ {
+ return mStart + static_cast<LONG>(mString.Length());
+ }
+
+ // Start() and End() updates the members for emulating the latest state.
+ // Unless flush the pending actions, this data never matches the actual
+ // content.
+ void Start(ITfCompositionView* aCompositionView,
+ LONG aCompositionStartOffset,
+ const nsAString& aCompositionString);
+ void End();
+ };
+ // 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.
+ Composition mComposition;
+
+ /**
+ * IsComposingInContent() returns true if there is a composition in the
+ * focused editor and it's caused by native IME (either TIP of TSF or IME of
+ * IMM). I.e., returns true between eCompositionStart and
+ * eCompositionCommit(AsIs).
+ */
+ bool IsComposingInContent() const;
+
+ class Selection
+ {
+ public:
+ Selection() : mDirty(true) {}
+
+ bool IsDirty() const { return mDirty; };
+ void MarkDirty() { mDirty = true; }
+
+ TS_SELECTION_ACP& ACP()
+ {
+ MOZ_ASSERT(!mDirty);
+ return mACP;
+ }
+
+ void SetSelection(const TS_SELECTION_ACP& aSelection)
+ {
+ mDirty = false;
+ 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,
+ WritingMode aWritingMode)
+ {
+ bool changed = mDirty ||
+ mACP.acpStart != static_cast<LONG>(aStart) ||
+ mACP.acpEnd != static_cast<LONG>(aStart + aLength);
+
+ mDirty = false;
+ 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 IsCollapsed() const
+ {
+ MOZ_ASSERT(!mDirty);
+ return (mACP.acpStart == mACP.acpEnd);
+ }
+
+ void CollapseAt(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.
+ mDirty = false;
+ mACP.acpStart = mACP.acpEnd = static_cast<LONG>(aOffset);
+ mACP.style.ase = TS_AE_END;
+ mACP.style.fInterimChar = FALSE;
+ }
+
+ LONG MinOffset() const
+ {
+ MOZ_ASSERT(!mDirty);
+ LONG min = std::min(mACP.acpStart, mACP.acpEnd);
+ MOZ_ASSERT(min >= 0);
+ return min;
+ }
+
+ LONG MaxOffset() const
+ {
+ MOZ_ASSERT(!mDirty);
+ LONG max = std::max(mACP.acpStart, mACP.acpEnd);
+ MOZ_ASSERT(max >= 0);
+ return max;
+ }
+
+ LONG StartOffset() const
+ {
+ MOZ_ASSERT(!mDirty);
+ MOZ_ASSERT(mACP.acpStart >= 0);
+ return mACP.acpStart;
+ }
+
+ LONG EndOffset() const
+ {
+ MOZ_ASSERT(!mDirty);
+ MOZ_ASSERT(mACP.acpEnd >= 0);
+ return mACP.acpEnd;
+ }
+
+ LONG Length() const
+ {
+ MOZ_ASSERT(!mDirty);
+ MOZ_ASSERT(mACP.acpEnd >= mACP.acpStart);
+ return std::abs(mACP.acpEnd - mACP.acpStart);
+ }
+
+ bool IsReversed() const
+ {
+ MOZ_ASSERT(!mDirty);
+ return (mACP.style.ase == TS_AE_START);
+ }
+
+ TsActiveSelEnd ActiveSelEnd() const
+ {
+ MOZ_ASSERT(!mDirty);
+ return mACP.style.ase;
+ }
+
+ bool IsInterimChar() const
+ {
+ MOZ_ASSERT(!mDirty);
+ return (mACP.style.fInterimChar != FALSE);
+ }
+
+ WritingMode GetWritingMode() const
+ {
+ MOZ_ASSERT(!mDirty);
+ 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(!mDirty);
+ MOZ_ASSERT(aChangedSelection.IsValid());
+ return aChangedSelection.Length() == static_cast<uint32_t>(Length()) &&
+ aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset());
+ }
+
+ private:
+ TS_SELECTION_ACP mACP;
+ WritingMode mWritingMode;
+ bool mDirty;
+ };
+ // Don't access mSelection directly except at calling MarkDirty().
+ // Use SelectionForTSFRef() instead. This is modified immediately when
+ // TSF requests to set selection and not updated by selection change in
+ // content until mContentForTSF is cleared.
+ 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 ContentForTSFRef().
+ */
+ Selection& SelectionForTSFRef();
+
+ class MOZ_STACK_CLASS AutoSetTemporarySelection final
+ {
+ public:
+ AutoSetTemporarySelection(Selection& aSelection)
+ : mSelection(aSelection)
+ {
+ mDirty = mSelection.IsDirty();
+ if (mDirty) {
+ mSelection.CollapseAt(0);
+ }
+ }
+
+ ~AutoSetTemporarySelection()
+ {
+ if (mDirty) {
+ mSelection.MarkDirty();
+ }
+ }
+
+ private:
+ Selection& mSelection;
+ bool mDirty;
+ };
+
+ struct PendingAction final
+ {
+ enum ActionType : uint8_t
+ {
+ COMPOSITION_START,
+ COMPOSITION_UPDATE,
+ COMPOSITION_END,
+ SET_SELECTION
+ };
+ ActionType mType;
+ // For compositionstart and selectionset
+ LONG mSelectionStart;
+ LONG mSelectionLength;
+ // For compositionstart, compositionupdate and compositionend
+ nsString mData;
+ // For compositionupdate
+ RefPtr<TextRangeArray> mRanges;
+ // For selectionset
+ bool mSelectionReversed;
+ // For compositionupdate
+ bool mIncomplete;
+ // For compositionstart
+ 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::COMPOSITION_UPDATE) {
+ return &lastAction;
+ }
+ }
+ PendingAction* newAction = mPendingActions.AppendElement();
+ newAction->mType = PendingAction::COMPOSITION_UPDATE;
+ newAction->mRanges = new TextRangeArray();
+ newAction->mIncomplete = true;
+ return newAction;
+ }
+
+ /**
+ * WasTextInsertedWithoutCompositionAt() checks if text was inserted without
+ * composition immediately before (e.g., see InsertTextAtSelectionInternal()).
+ *
+ * @param aStart The inserted offset you expected.
+ * @param aLength The inserted text length you expected.
+ * @return true if the last pending actions are
+ * COMPOSITION_START and COMPOSITION_END and
+ * aStart and aLength match their information.
+ */
+ bool WasTextInsertedWithoutCompositionAt(LONG aStart, LONG aLength) const
+ {
+ if (mPendingActions.Length() < 2) {
+ return false;
+ }
+ const PendingAction& pendingLastAction = mPendingActions.LastElement();
+ if (pendingLastAction.mType != PendingAction::COMPOSITION_END ||
+ pendingLastAction.mData.Length() != aLength) {
+ return false;
+ }
+ const PendingAction& pendingPreLastAction =
+ mPendingActions[mPendingActions.Length() - 2];
+ return pendingPreLastAction.mType == PendingAction::COMPOSITION_START &&
+ pendingPreLastAction.mSelectionStart == aStart;
+ }
+
+ bool IsPendingCompositionUpdateIncomplete() const
+ {
+ if (mPendingActions.IsEmpty()) {
+ return false;
+ }
+ const PendingAction& lastAction = mPendingActions.LastElement();
+ return lastAction.mType == PendingAction::COMPOSITION_UPDATE &&
+ lastAction.mIncomplete;
+ }
+
+ void CompleteLastActionIfStillIncomplete()
+ {
+ if (!IsPendingCompositionUpdateIncomplete()) {
+ return;
+ }
+ RecordCompositionUpdateAction();
+ }
+
+ // 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:
+ 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::Composition& aComposition,
+ TSFTextStore::Selection& aSelection) :
+ mComposition(aComposition), mSelection(aSelection)
+ {
+ Clear();
+ }
+
+ void Clear()
+ {
+ mText.Truncate();
+ mLastCompositionString.Truncate();
+ mInitialized = false;
+ }
+
+ bool IsInitialized() const { return mInitialized; }
+
+ void Init(const nsAString& aText)
+ {
+ mText = aText;
+ if (mComposition.IsComposing()) {
+ mLastCompositionString = mComposition.mString;
+ } else {
+ mLastCompositionString.Truncate();
+ }
+ mMinTextModifiedOffset = NOT_MODIFIED;
+ mInitialized = true;
+ }
+
+ void OnLayoutChanged()
+ {
+ mMinTextModifiedOffset = NOT_MODIFIED;
+ }
+
+ 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(), there is a pending
+ * compositionstart and a pending compositionend. In this case, we
+ * need to cancel the pending compositionend and continue the composition.
+ *
+ * @param aCompositionView The composition view.
+ * @param aPendingCompositionStart The pending compositionstart which
+ * started the committed composition.
+ * @param aCanceledCompositionEnd The pending compositionend which is
+ * canceled for restarting the composition.
+ */
+ void RestoreCommittedComposition(
+ ITfCompositionView* aCompositionView,
+ const PendingAction& aPendingCompositionStart,
+ const PendingAction& aCanceledCompositionEnd);
+ void EndComposition(const PendingAction& aCompEnd);
+
+ const nsString& Text() const
+ {
+ MOZ_ASSERT(mInitialized);
+ return mText;
+ }
+ const nsString& LastCompositionString() const
+ {
+ MOZ_ASSERT(mInitialized);
+ return mLastCompositionString;
+ }
+ uint32_t MinTextModifiedOffset() const
+ {
+ MOZ_ASSERT(mInitialized);
+ return mMinTextModifiedOffset;
+ }
+
+ // Returns true if layout of the character at the aOffset has not been
+ // calculated.
+ bool IsLayoutChangedAt(uint32_t aOffset) const
+ {
+ return IsLayoutChanged() && (mMinTextModifiedOffset <= aOffset);
+ }
+ // Returns true if layout of the content has been changed, i.e., the new
+ // layout has not been calculated.
+ bool IsLayoutChanged() const
+ {
+ return mInitialized && (mMinTextModifiedOffset != NOT_MODIFIED);
+ }
+ // Returns minimum offset of modified text range.
+ uint32_t MinOffsetOfLayoutChanged() const
+ {
+ return mInitialized ? mMinTextModifiedOffset : NOT_MODIFIED;
+ }
+
+ TSFTextStore::Composition& Composition() { return mComposition; }
+ TSFTextStore::Selection& Selection() { return mSelection; }
+
+ private:
+ nsString mText;
+ // mLastCompositionString stores the composition string when the document
+ // is locked. This is necessary to compute mMinTextModifiedOffset.
+ nsString mLastCompositionString;
+ TSFTextStore::Composition& mComposition;
+ TSFTextStore::Selection& mSelection;
+
+ // The minimum offset of modified part of the text.
+ enum : uint32_t
+ {
+ NOT_MODIFIED = UINT32_MAX
+ };
+ uint32_t mMinTextModifiedOffset;
+
+ bool mInitialized;
+ };
+ // 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 ContentForTSFRef() 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.
+ Content mContentForTSF;
+
+ Content& ContentForTSFRef();
+
+ // 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; }
+ bool InRange(uint32_t aOffset) const
+ {
+ if (NS_WARN_IF(mStart < 0) ||
+ NS_WARN_IF(mLength <= 0)) {
+ return false;
+ }
+ return aOffset >= static_cast<uint32_t>(mStart) &&
+ aOffset < static_cast<uint32_t>(mStart + mLength);
+ }
+ DWORD Cookie() const { return mCookie; }
+ bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus);
+ LONG RangeStart() const { return mStart; }
+
+ private:
+ RefPtr<ITfMouseSink> mSink;
+ LONG mStart;
+ LONG mLength;
+ 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 there is native caret, this is true. Otherwise, false.
+ bool mNativeCaretIsCreated;
+ // 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;
+ // sMessagePump is QI'ed from sThreadMgr
+ static StaticRefPtr<ITfMessagePump> sMessagePump;
+ // sKeystrokeMgr is QI'ed from sThreadMgr
+ static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr;
+ // TSF display attribute manager
+ static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr;
+ // TSF category manager
+ static StaticRefPtr<ITfCategoryMgr> sCategoryMgr;
+
+ // 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;
+
+ // TSF client ID for the current application
+ static DWORD sClientId;
+
+ // Enables/Disables hack for specific TIP.
+ static bool sCreateNativeCaretForLegacyATOK;
+ static bool sDoNotReturnNoLayoutErrorToATOKOfCompositionString;
+ static bool sDoNotReturnNoLayoutErrorToMSSimplifiedTIP;
+ static bool sDoNotReturnNoLayoutErrorToMSTraditionalTIP;
+ static bool sDoNotReturnNoLayoutErrorToFreeChangJie;
+ static bool sDoNotReturnNoLayoutErrorToEasyChangjei;
+ static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar;
+ static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret;
+ static bool sHackQueryInsertForMSSimplifiedTIP;
+ static bool sHackQueryInsertForMSTraditionalTIP;
+};
+
+} // 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 000000000..c897af021
--- /dev/null
+++ b/widget/windows/TaskbarPreview.cpp
@@ -0,0 +1,431 @@
+/* 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 <nsIBaseWindow.h>
+#include <nsIObserverService.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/Telemetry.h"
+
+// Defined in dwmapi in a header that needs a higher numbered _WINNT #define
+#define DWM_SIT_DISPLAYFRAME 0x1
+
+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 may outlive the WinTaskbar that created it
+ ::CoInitialize(nullptr);
+
+ WindowHook &hook = GetWindowHook();
+ hook.AddMonitor(WM_DESTROY, MainWindowHook, this);
+}
+
+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();
+}
+
+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 (!mWnd)
+ 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 (!nsUXThemeData::CheckForCompositor())
+ return NS_OK;
+
+ HWND previewWindow = PreviewWindow();
+ return FAILED(WinUtils::dwmInvalidateIconicBitmapsPtr(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 {
+ WindowHook &hook = GetWindowHook();
+ hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this);
+ }
+ return rv;
+}
+
+nsresult
+TaskbarPreview::Disable() {
+ if (!IsWindowAvailable()) {
+ // Window is already destroyed
+ return NS_OK;
+ }
+
+ WindowHook &hook = GetWindowHook();
+ (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() {
+ 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 = nsIWidget::DefaultScaleOverride();
+ 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->HasTaskbarIconBeenCreated();
+ }
+ return false;
+}
+
+WindowHook&
+TaskbarPreview::GetWindowHook() {
+ nsWindow *window = WinUtils::GetNSWindowPtr(mWnd);
+ NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!");
+
+ return window->GetWindowHook();
+}
+
+void
+TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) {
+ BOOL enabled = aEnable;
+ WinUtils::dwmSetWindowAttributePtr(
+ aHWND,
+ DWMWA_FORCE_ICONIC_REPRESENTATION,
+ &enabled,
+ sizeof(enabled));
+
+ WinUtils::dwmSetWindowAttributePtr(
+ 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<nsIDOMHTMLCanvasElement> domcanvas(do_QueryInterface(aCanvas));
+ dom::HTMLCanvasElement * canvas = ((dom::HTMLCanvasElement*)domcanvas.get());
+ 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 = WinUtils::dwmSetIconicLivePreviewBitmapPtr(mPreview->PreviewWindow(),
+ hBitmap, &pptClient, flags);
+ } else {
+ hr = WinUtils::dwmSetIconicThumbnailPtr(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 000000000..d5f6b86f1
--- /dev/null
+++ b/widget/windows/TaskbarPreview.h
@@ -0,0 +1,140 @@
+/* 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 <nsWeakPtr.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);
+
+ 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 000000000..69cea3809
--- /dev/null
+++ b/widget/windows/TaskbarPreviewButton.cpp
@@ -0,0 +1,145 @@
+/* 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, 0, 0,
+ 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 000000000..2b4342d15
--- /dev/null
+++ b/widget/windows/TaskbarPreviewButton.h
@@ -0,0 +1,48 @@
+/* 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 000000000..c89fecb32
--- /dev/null
+++ b/widget/windows/TaskbarTabPreview.cpp
@@ -0,0 +1,357 @@
+/* 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)
+{
+ WindowHook &hook = GetWindowHook();
+ hook.AddMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
+}
+
+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::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, 0, 0,
+ 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);
+ 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 {
+ NS_NOTREACHED("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 000000000..3d043a141
--- /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);
+
+ 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 000000000..c3d005252
--- /dev/null
+++ b/widget/windows/TaskbarWindowPreview.cpp
@@ -0,0 +1,326 @@
+/* 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;
+}
+}
+
+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;
+ }
+
+ WindowHook &hook = GetWindowHook();
+ if (!CanMakeTaskbarCalls())
+ hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ TaskbarWindowHook, this);
+}
+
+TaskbarWindowPreview::~TaskbarWindowPreview() {
+ if (mOverlayIcon) {
+ ::DestroyIcon(mOverlayIcon);
+ mOverlayIcon = nullptr;
+ }
+
+ if (IsWindowAvailable()) {
+ DetachFromNSWindow();
+ } else {
+ mWnd = nullptr;
+ }
+}
+
+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();
+ (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;
+ mCustomDrawing = aEnable;
+ TaskbarPreview::EnableCustomDrawing(mWnd, aEnable);
+
+ WindowHook &hook = GetWindowHook();
+ 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, 0, 0,
+ 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);
+
+ 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 000000000..d0719f739
--- /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);
+
+ 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/WidgetTraceEvent.cpp b/widget/windows/WidgetTraceEvent.cpp
new file mode 100644
index 000000000..589449af8
--- /dev/null
+++ b/widget/windows/WidgetTraceEvent.cpp
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIXULWindow.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() : hidden_window_hwnd(nullptr) {
+ MOZ_COUNT_CTOR(HWNDGetter);
+ }
+ ~HWNDGetter() {
+ MOZ_COUNT_DTOR(HWNDGetter);
+ }
+
+ 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<nsIXULWindow> 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 000000000..f660bd019
--- /dev/null
+++ b/widget/windows/WinCompositorWidget.cpp
@@ -0,0 +1,329 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "gfxPrefs.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+#include "VsyncDispatcher.h"
+
+#include <ddraw.h>
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+
+WinCompositorWidget::WinCompositorWidget(const CompositorWidgetInitData& aInitData)
+ : mWidgetKey(aInitData.widgetKey()),
+ mWnd(reinterpret_cast<HWND>(aInitData.hWnd())),
+ mTransparencyMode(static_cast<nsTransparencyMode>(aInitData.transparencyMode())),
+ mMemoryDC(nullptr),
+ mCompositeDC(nullptr),
+ mLockedBackBufferData(nullptr)
+{
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+
+ // mNotDeferEndRemoteDrawing is set on the main thread during init,
+ // but is only accessed after on the compositor thread.
+ mNotDeferEndRemoteDrawing = gfxPrefs::LayersCompositionFrameRate() == 0 ||
+ gfxPlatform::IsInLayoutAsapMode() ||
+ gfxPlatform::ForceSoftwareVsync();
+}
+
+void
+WinCompositorWidget::OnDestroyWindow()
+{
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+}
+
+bool
+WinCompositorWidget::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
+WinCompositorWidget::PostRender(WidgetRenderingContext* aContext)
+{
+ mPresentLock.Leave();
+}
+
+LayoutDeviceIntSize
+WinCompositorWidget::GetClientSize()
+{
+ RECT r;
+ if (!::GetClientRect(mWnd, &r)) {
+ return LayoutDeviceIntSize();
+ }
+ return LayoutDeviceIntSize(
+ r.right - r.left,
+ r.bottom - r.top);
+}
+
+already_AddRefed<gfx::DrawTarget>
+WinCompositorWidget::StartRemoteDrawing()
+{
+ 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;
+ }
+
+ MOZ_ASSERT(!mCompositeDC);
+ mCompositeDC = dc;
+
+ return mozilla::gfx::Factory::CreateDrawTargetForCairoSurface(surf->CairoSurface(), size);
+}
+
+void
+WinCompositorWidget::EndRemoteDrawing()
+{
+ MOZ_ASSERT(!mLockedBackBufferData);
+
+ if (mTransparencyMode == eTransparencyTransparent) {
+ MOZ_ASSERT(mTransparentSurface);
+ RedrawTransparentWindow();
+ }
+ if (mCompositeDC) {
+ FreeWindowSurface(mCompositeDC);
+ }
+ mCompositeDC = nullptr;
+}
+
+bool
+WinCompositorWidget::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>
+WinCompositorWidget::GetBackBufferDrawTarget(gfx::DrawTarget* aScreenTarget,
+ const LayoutDeviceIntRect& aRect,
+ const LayoutDeviceIntRect& aClearRect)
+{
+ MOZ_ASSERT(!mLockedBackBufferData);
+
+ RefPtr<gfx::DrawTarget> target =
+ CompositorWidget::GetBackBufferDrawTarget(aScreenTarget, aRect, aClearRect);
+ 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>
+WinCompositorWidget::EndBackBufferDrawing()
+{
+ if (mLockedBackBufferData) {
+ MOZ_ASSERT(mLastBackBuffer);
+ mLastBackBuffer->ReleaseBits(mLockedBackBufferData);
+ mLockedBackBufferData = nullptr;
+ }
+ return CompositorWidget::EndBackBufferDrawing();
+}
+
+bool
+WinCompositorWidget::InitCompositor(layers::Compositor* aCompositor)
+{
+ if (aCompositor->GetBackendType() == layers::LayersBackend::LAYERS_BASIC) {
+ DeviceManagerDx::Get()->InitializeDirectDraw();
+ }
+ return true;
+}
+
+uintptr_t
+WinCompositorWidget::GetWidgetKey()
+{
+ return mWidgetKey;
+}
+
+void
+WinCompositorWidget::EnterPresentLock()
+{
+ mPresentLock.Enter();
+}
+
+void
+WinCompositorWidget::LeavePresentLock()
+{
+ mPresentLock.Leave();
+}
+
+RefPtr<gfxASurface>
+WinCompositorWidget::EnsureTransparentSurface()
+{
+ 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
+WinCompositorWidget::CreateTransparentSurface(const gfx::IntSize& aSize)
+{
+ MOZ_ASSERT(!mTransparentSurface && !mMemoryDC);
+ RefPtr<gfxWindowsSurface> surface = new gfxWindowsSurface(aSize, SurfaceFormat::A8R8G8B8_UINT32);
+ mTransparentSurface = surface;
+ mMemoryDC = surface->GetDC();
+}
+
+void
+WinCompositorWidget::UpdateTransparency(nsTransparencyMode aMode)
+{
+ if (mTransparencyMode == aMode) {
+ return;
+ }
+
+ mTransparencyMode = aMode;
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+
+ if (mTransparencyMode == eTransparencyTransparent) {
+ EnsureTransparentSurface();
+ }
+}
+
+void
+WinCompositorWidget::ClearTransparentWindow()
+{
+ 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();
+ }
+}
+
+bool
+WinCompositorWidget::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
+WinCompositorWidget::GetWindowSurface()
+{
+ return eTransparencyTransparent == mTransparencyMode
+ ? mMemoryDC
+ : ::GetDC(mWnd);
+}
+
+void
+WinCompositorWidget::FreeWindowSurface(HDC dc)
+{
+ if (eTransparencyTransparent != mTransparencyMode)
+ ::ReleaseDC(mWnd, dc);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinCompositorWidget.h b/widget/windows/WinCompositorWidget.h
new file mode 100644
index 000000000..9661cab45
--- /dev/null
+++ b/widget/windows/WinCompositorWidget.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "CompositorWidget.h"
+#include "gfxASurface.h"
+#include "mozilla/gfx/CriticalSection.h"
+#include "mozilla/gfx/Point.h"
+#include "nsIWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetDelegate
+{
+public:
+ // Callbacks for nsWindow.
+ virtual void EnterPresentLock() = 0;
+ virtual void LeavePresentLock() = 0;
+ virtual void OnDestroyWindow() = 0;
+
+ // Transparency handling.
+ virtual void UpdateTransparency(nsTransparencyMode aMode) = 0;
+ virtual void ClearTransparentWindow() = 0;
+
+ // If in-process and using software rendering, return the backing transparent
+ // DC.
+ virtual HDC GetTransparentDC() const = 0;
+};
+
+// 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 CompositorWidgetDelegate
+{
+public:
+ WinCompositorWidget(const CompositorWidgetInitData& aInitData);
+
+ 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 LayoutDeviceIntRect& aRect,
+ const LayoutDeviceIntRect& aClearRect) override;
+ already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override;
+ bool InitCompositor(layers::Compositor* aCompositor) override;
+ uintptr_t GetWidgetKey() override;
+ WinCompositorWidget* AsWindows() override {
+ return this;
+ }
+ CompositorWidgetDelegate* AsDelegate() override {
+ return this;
+ }
+
+ // CompositorWidgetDelegate overrides.
+ void EnterPresentLock() override;
+ void LeavePresentLock() override;
+ void OnDestroyWindow() 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 override {
+ return mMemoryDC;
+ }
+ HWND GetHwnd() const {
+ return mWnd;
+ }
+
+private:
+ HDC GetWindowSurface();
+ void FreeWindowSurface(HDC dc);
+
+ void CreateTransparentSurface(const gfx::IntSize& aSize);
+
+private:
+ uintptr_t mWidgetKey;
+ HWND mWnd;
+ gfx::CriticalSection mPresentLock;
+
+ // Transparency handling.
+ nsTransparencyMode 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_CompositorWidgetParent_h
diff --git a/widget/windows/WinIMEHandler.cpp b/widget/windows/WinIMEHandler.cpp
new file mode 100644
index 000000000..d44f729c4
--- /dev/null
+++ b/widget/windows/WinIMEHandler.cpp
@@ -0,0 +1,1055 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/WindowsVersion.h"
+#include "nsWindowDefs.h"
+#include "WinTextEventDispatcherListener.h"
+
+#ifdef NS_ENABLE_TSF
+#include "TSFTextStore.h"
+#endif // #ifdef NS_ENABLE_TSF
+
+#include "nsLookAndFeel.h"
+#include "nsWindow.h"
+#include "WinUtils.h"
+#include "nsIWindowsRegKey.h"
+#include "nsIWindowsUIUtils.h"
+
+#include "shellapi.h"
+#include "shlobj.h"
+#include "powrprof.h"
+#include "setupapi.h"
+#include "cfgmgr32.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::sPluginHasFocus = false;
+
+#ifdef NS_ENABLE_TSF
+bool IMEHandler::sIsInTSFMode = false;
+bool IMEHandler::sIsIMMEnabled = true;
+bool IMEHandler::sAssociateIMCOnlyWhenIMM_IMEActive = false;
+decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
+#endif // #ifdef NS_ENABLE_TSF
+
+static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified;
+static bool sDeterminedPowerPlatformRole = false;
+
+// static
+void
+IMEHandler::Initialize()
+{
+#ifdef NS_ENABLE_TSF
+ 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"));
+ }
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ IMMHandler::Initialize();
+}
+
+// static
+void
+IMEHandler::Terminate()
+{
+#ifdef NS_ENABLE_TSF
+ if (sIsInTSFMode) {
+ TSFTextStore::Terminate();
+ sIsInTSFMode = false;
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ IMMHandler::Terminate();
+ WinTextEventDispatcherListener::Shutdown();
+}
+
+// static
+void*
+IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType)
+{
+ if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) {
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ return TSFTextStore::GetThreadManager();
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ 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;
+ }
+
+#ifdef NS_ENABLE_TSF
+ 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;
+#else // #ifdef NS_ENABLE_TSF
+ return nullptr;
+#endif // #ifdef NS_ENABLE_TSF #else
+}
+
+// static
+bool
+IMEHandler::ProcessRawKeyMessage(const MSG& aMsg)
+{
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ return TSFTextStore::ProcessRawKeyMessage(aMsg);
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ return false; // noting to do in IMM mode.
+}
+
+// static
+bool
+IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage,
+ WPARAM& aWParam, LPARAM& aLParam,
+ MSGResult& aResult)
+{
+ if (aMessage == MOZ_WM_DISMISS_ONSCREEN_KEYBOARD) {
+ if (!sFocusedWindow) {
+ DismissOnScreenKeyboard();
+ }
+ return true;
+ }
+
+#ifdef NS_ENABLE_TSF
+ 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 (!TSFTextStore::IsIMM_IMEActive()) {
+ return false;
+ }
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ return IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam,
+ aResult);
+}
+
+#ifdef NS_ENABLE_TSF
+// static
+bool
+IMEHandler::IsIMMActive()
+{
+ return TSFTextStore::IsIMM_IMEActive();
+}
+
+#endif // #ifdef NS_ENABLE_TSF
+
+// static
+bool
+IMEHandler::IsComposing()
+{
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ return IMMHandler::IsComposing();
+}
+
+// static
+bool
+IMEHandler::IsComposingOn(nsWindow* aWindow)
+{
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ return TSFTextStore::IsComposingOn(aWindow) ||
+ IMMHandler::IsComposingOn(aWindow);
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ return IMMHandler::IsComposingOn(aWindow);
+}
+
+// static
+nsresult
+IMEHandler::NotifyIME(nsWindow* aWindow,
+ const IMENotification& aIMENotification)
+{
+#ifdef NS_ENABLE_TSF
+ 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());
+ IMEHandler::MaybeShowOnScreenKeyboard();
+ 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;
+ }
+ }
+#endif //NS_ENABLE_TSF
+
+ 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);
+ 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();
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ sFocusedWindow = nullptr;
+ IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
+ IMMHandler::OnFocusChange(false, aWindow);
+#ifdef NS_ENABLE_TSF
+ // 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());
+ }
+#endif //NS_ENABLE_TSF
+ return NS_OK;
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+// static
+nsIMEUpdatePreference
+IMEHandler::GetUpdatePreference()
+{
+ // While a plugin has focus, neither TSFTextStore nor IMMHandler needs
+ // notifications.
+ if (sPluginHasFocus) {
+ return nsIMEUpdatePreference();
+ }
+
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ if (!sIsIMMEnabled) {
+ return TSFTextStore::GetIMEUpdatePreference();
+ }
+ // Even if TSF is available, the active IME may be an IMM-IME.
+ // Unfortunately, changing the result of GetUpdatePreference() 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::GetIMEUpdatePreference() |
+ TSFTextStore::GetIMEUpdatePreference();
+ }
+#endif //NS_ENABLE_TSF
+
+ return IMMHandler::GetIMEUpdatePreference();
+}
+
+// static
+TextEventDispatcherListener*
+IMEHandler::GetNativeTextEventDispatcherListener()
+{
+ return WinTextEventDispatcherListener::GetInstance();
+}
+
+// static
+bool
+IMEHandler::GetOpenState(nsWindow* aWindow)
+{
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable() && !IsIMMActive()) {
+ return TSFTextStore::GetIMEOpenState();
+ }
+#endif //NS_ENABLE_TSF
+
+ 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 TabParent already lost the reference to the nsWindow when
+ // it receives from the remote process.
+ if (sFocusedWindow == aWindow) {
+ NS_ASSERTION(aWindow->GetInputContext().IsOriginContentProcess(),
+ "input context of focused widget should be set from a remote process");
+ NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR));
+ }
+
+#ifdef NS_ENABLE_TSF
+ // 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, EmptyString(), EmptyString());
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ AssociateIMEContext(aWindow, true);
+}
+
+#ifdef NS_ENABLE_TSF
+// static
+bool
+IMEHandler::NeedsToAssociateIMC()
+{
+ if (sAssociateIMCOnlyWhenIMM_IMEActive) {
+ return TSFTextStore::IsIMM_IMEActive();
+ }
+
+ // Even if IMC should be associated with focused widget with non-IMM-IME,
+ // we need to avoid crash bug of MS-IME for Japanese on Win10. It crashes
+ // while we're associating default IME to a window when it's active.
+ static const bool sDoNotAssociateIMCWhenMSJapaneseIMEActiveOnWin10 =
+ IsWin10OrLater() &&
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_japanese_ime.do_not_associate_imc_on_win10", true);
+ return !sDoNotAssociateIMCWhenMSJapaneseIMEActiveOnWin10 ||
+ !TSFTextStore::IsMSJapaneseIMEActive();
+}
+#endif // #ifdef NS_ENABLE_TSF
+
+// 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));
+
+ const InputContext& oldInputContext = aWindow->GetInputContext();
+
+ // Assume that SetInputContext() is called only when aWindow has focus.
+ sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN);
+
+ if (aAction.UserMightRequestOpenVKB()) {
+ IMEHandler::MaybeShowOnScreenKeyboard();
+ }
+
+ bool enable = WinUtils::IsIMEEnabled(aInputContext);
+ bool adjustOpenState = (enable &&
+ aInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE);
+ bool open = (adjustOpenState &&
+ aInputContext.mIMEState.mOpen == IMEState::OPEN);
+
+#ifdef NS_ENABLE_TSF
+ // 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());
+ } else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
+ // Disassociate the IME context from the window when plugin loses focus
+ // in pure TSF mode.
+ AssociateIMEContext(aWindow, false);
+ }
+ if (adjustOpenState) {
+ TSFTextStore::SetIMEOpenState(open);
+ }
+ return;
+ }
+ } else {
+ // Set at least InputScope even when TextStore is not available.
+ SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType,
+ aInputContext.mHTMLInputInputmode);
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ 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)
+{
+ // For a11y, the default enabled state should be 'enabled'.
+ aInputContext.mIMEState.mEnabled = IMEState::ENABLED;
+
+#ifdef NS_ENABLE_TSF
+ if (sIsInTSFMode) {
+ TSFTextStore::SetInputContext(aWindow, aInputContext,
+ InputContextAction(InputContextAction::CAUSE_UNKNOWN,
+ InputContextAction::GOT_FOCUS));
+ // IME context isn't necessary in pure TSF mode.
+ if (!sIsIMMEnabled) {
+ AssociateIMEContext(aWindow, false);
+ }
+ return;
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+#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()
+{
+#ifdef NS_ENABLE_TSF
+ if (sIsInTSFMode) {
+ return TSFTextStore::CurrentKeyboardLayoutHasIME();
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ return IMMHandler::IsIMEAvailable();
+}
+#endif // #ifdef DEBUG
+
+// static
+void
+IMEHandler::OnKeyboardLayoutChanged()
+{
+ if (!sIsIMMEnabled || !IsTSFAvailable()) {
+ 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)
+{
+ if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) {
+ return;
+ }
+ UINT arraySize = 0;
+ const InputScope* scopes = nullptr;
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
+ if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) {
+ if (aHTMLInputInputmode.EqualsLiteral("url")) {
+ static const InputScope inputScopes[] = { IS_URL };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputInputmode.EqualsLiteral("email")) {
+ static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputInputmode.EqualsLiteral("tel")) {
+ static const InputScope inputScopes[] =
+ {IS_TELEPHONE_LOCALNUMBER, IS_TELEPHONE_FULLTELEPHONENUMBER};
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputInputmode.EqualsLiteral("numeric")) {
+ static const InputScope inputScopes[] = { IS_NUMBER };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else {
+ static const InputScope inputScopes[] = { IS_DEFAULT };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ }
+ } else if (aHTMLInputType.EqualsLiteral("url")) {
+ static const InputScope inputScopes[] = { IS_URL };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("search")) {
+ static const InputScope inputScopes[] = { IS_SEARCH };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("email")) {
+ static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("password")) {
+ static const InputScope inputScopes[] = { IS_PASSWORD };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("datetime") ||
+ aHTMLInputType.EqualsLiteral("datetime-local")) {
+ static const InputScope inputScopes[] = {
+ IS_DATE_FULLDATE, IS_TIME_FULLTIME };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("date") ||
+ aHTMLInputType.EqualsLiteral("month") ||
+ aHTMLInputType.EqualsLiteral("week")) {
+ static const InputScope inputScopes[] = { IS_DATE_FULLDATE };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("time")) {
+ static const InputScope inputScopes[] = { IS_TIME_FULLTIME };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("tel")) {
+ static const InputScope inputScopes[] = {
+ IS_TELEPHONE_FULLTELEPHONENUMBER, IS_TELEPHONE_LOCALNUMBER };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("number")) {
+ static const InputScope inputScopes[] = { IS_NUMBER };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ }
+ if (scopes && arraySize > 0) {
+ sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0,
+ nullptr, nullptr);
+ }
+}
+
+// static
+void
+IMEHandler::MaybeShowOnScreenKeyboard()
+{
+ if (sPluginHasFocus ||
+ !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();
+}
+
+// static
+void
+IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow)
+{
+ if (sPluginHasFocus ||
+ !IsWin8OrLater()) {
+ return;
+ }
+
+ ::PostMessage(aWindow->GetWindowHandle(), MOZ_WM_DISMISS_ONSCREEN_KEYBOARD,
+ 0, 0);
+}
+
+// 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::IsUserAction(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.
+ typedef POWER_PLATFORM_ROLE (WINAPI* PowerDeterminePlatformRoleEx)(ULONG Version);
+ if (!sDeterminedPowerPlatformRole) {
+ sDeterminedPowerPlatformRole = true;
+ PowerDeterminePlatformRoleEx power_determine_platform_role =
+ reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress(
+ ::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx"));
+ if (power_determine_platform_role) {
+ sPowerPlatformRole = power_determine_platform_role(POWER_PLATFORM_ROLE_V2);
+ } else {
+ sPowerPlatformRole = PlatformRoleUnspecified;
+ }
+ }
+
+ // 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
+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;
+ }
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("SOFTWARE\\Microsoft\\TabletTip\\1.7"),
+ 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.
+ uint32_t value;
+ rv = regKey->ReadIntValue(NS_LITERAL_STRING("EnableDesktopModeAutoInvoke"),
+ &value);
+ if (NS_FAILED(rv)) {
+ Preferences::SetString(kOskDebugReason,
+ L"AIOSKIDM: failed reading value of regkey.");
+ 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()
+{
+ 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 =
+ WinUtils::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()
+{
+ // Dismiss the virtual keyboard if it's open
+ 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;
+}
+
+// static
+void
+IMEHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm)
+{
+ if (!sPluginHasFocus) {
+ return;
+ }
+
+ IMMHandler::SetCandidateWindow(aWindow, aForm);
+}
+
+// static
+void
+IMEHandler::DefaultProcOfPluginEvent(nsWindow* aWindow,
+ const NPEvent* aPluginEvent)
+{
+ if (!sPluginHasFocus) {
+ return;
+ }
+ IMMHandler::DefaultProcOfPluginEvent(aWindow, aPluginEvent);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinIMEHandler.h b/widget/windows/WinIMEHandler.h
new file mode 100644
index 000000000..c18a4437e
--- /dev/null
+++ b/widget/windows/WinIMEHandler.h
@@ -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/. */
+
+#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
+{
+public:
+ static void Initialize();
+ 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);
+
+ /**
+ * 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 update preferences.
+ */
+ static nsIMEUpdatePreference GetUpdatePreference();
+
+ /**
+ * 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);
+
+ /*
+ * For windowless plugin helper.
+ */
+ static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm);
+
+ /*
+ * For WM_IME_*COMPOSITION messages and e10s with windowless plugin
+ */
+ static void DefaultProcOfPluginEvent(nsWindow* aWindow,
+ const NPEvent* aPluginEvent);
+
+#ifdef NS_ENABLE_TSF
+ /**
+ * This is called by TSFStaticSink when active IME is changed.
+ */
+ static void OnKeyboardLayoutChanged();
+#endif // #ifdef NS_ENABLE_TSF
+
+#ifdef DEBUG
+ /**
+ * Returns true when current keyboard layout has IME. Otherwise, false.
+ */
+ static bool CurrentKeyboardLayoutHasIME();
+#endif // #ifdef DEBUG
+
+private:
+ static nsWindow* sFocusedWindow;
+ static InputContextAction::Cause sLastContextActionCause;
+
+ static bool sPluginHasFocus;
+
+#ifdef NS_ENABLE_TSF
+ static decltype(SetInputScopes)* sSetInputScopes;
+ static void SetInputScopeForIMM32(nsWindow* aWindow,
+ const nsAString& aHTMLInputType,
+ const nsAString& aHTMLInputInputmode);
+ 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 && !sPluginHasFocus); }
+ static bool IsIMMActive();
+
+ static void MaybeShowOnScreenKeyboard();
+ static void MaybeDismissOnScreenKeyboard(nsWindow* aWindow);
+ 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();
+
+ /**
+ * Dismiss the Windows on-screen keyboard. Only allowed for
+ * Windows 8 and higher.
+ */
+ static void DismissOnScreenKeyboard();
+
+ /**
+ * Get the HWND for the on-screen keyboard, if it's up. Only
+ * allowed for Windows 8 and higher.
+ */
+ static HWND GetOnScreenKeyboardWindow();
+#endif // #ifdef NS_ENABLE_TSF
+};
+
+} // 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 000000000..e722a3836
--- /dev/null
+++ b/widget/windows/WinMessages.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 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)
+// 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 hiding the on-screen keyboard
+#define MOZ_WM_DISMISS_ONSCREEN_KEYBOARD (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)
+
+// Internal message for ensuring the file picker is visible on multi monitor
+// systems, and when the screen resolution changes.
+#define MOZ_WM_ENSUREVISIBLE (WM_APP+0x374F)
+
+// 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)
+
+/*****************************************************************************
+ * WM_* messages and related constants which may not be defined by
+ * old Windows SDK
+ ****************************************************************************/
+
+#ifndef SM_CXPADDEDBORDER
+#define SM_CXPADDEDBORDER 92
+#endif
+
+// require WINVER >= 0x601
+#ifndef SM_MAXIMUMTOUCHES
+#define SM_MAXIMUMTOUCHES 95
+#endif
+
+#ifndef WM_THEMECHANGED
+#define WM_THEMECHANGED 0x031A
+#endif
+
+#ifndef WM_GETOBJECT
+#define WM_GETOBJECT 0x03d
+#endif
+
+#ifndef PBT_APMRESUMEAUTOMATIC
+#define PBT_APMRESUMEAUTOMATIC 0x0012
+#endif
+
+#ifndef WM_MOUSEHWHEEL
+#define WM_MOUSEHWHEEL 0x020E
+#endif
+
+#ifndef MOUSEEVENTF_HWHEEL
+#define MOUSEEVENTF_HWHEEL 0x01000
+#endif
+
+#ifndef WM_MOUSELEAVE
+#define WM_MOUSELEAVE 0x02A3
+#endif
+
+#ifndef SPI_GETWHEELSCROLLCHARS
+#define SPI_GETWHEELSCROLLCHARS 0x006C
+#endif
+
+#ifndef SPI_SETWHEELSCROLLCHARS
+#define SPI_SETWHEELSCROLLCHARS 0x006D
+#endif
+
+#ifndef MAPVK_VSC_TO_VK
+#define MAPVK_VK_TO_VSC 0
+#define MAPVK_VSC_TO_VK 1
+#define MAPVK_VK_TO_CHAR 2
+#define MAPVK_VSC_TO_VK_EX 3
+#define MAPVK_VK_TO_VSC_EX 4
+#endif
+
+#ifndef WM_DWMCOMPOSITIONCHANGED
+#define WM_DWMCOMPOSITIONCHANGED 0x031E
+#endif
+#ifndef WM_DWMNCRENDERINGCHANGED
+#define WM_DWMNCRENDERINGCHANGED 0x031F
+#endif
+#ifndef WM_DWMCOLORIZATIONCOLORCHANGED
+#define WM_DWMCOLORIZATIONCOLORCHANGED 0x0320
+#endif
+#ifndef WM_DWMWINDOWMAXIMIZEDCHANGE
+#define WM_DWMWINDOWMAXIMIZEDCHANGE 0x0321
+#endif
+
+// Drop shadow window style
+#define CS_XP_DROPSHADOW 0x00020000
+
+// App Command messages for IntelliMouse and Natural Keyboard Pro
+// These messages are not included in Visual C++ 6.0, but are in 7.0+
+#ifndef WM_APPCOMMAND
+#define WM_APPCOMMAND 0x0319
+#endif
+
+#define FAPPCOMMAND_MASK 0xF000
+
+#ifndef WM_GETTITLEBARINFOEX
+#define WM_GETTITLEBARINFOEX 0x033F
+#endif
+
+#ifndef CCHILDREN_TITLEBAR
+#define CCHILDREN_TITLEBAR 5
+#endif
+
+#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 000000000..f2c1704b9
--- /dev/null
+++ b/widget/windows/WinModifierKeyState.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 mozilla_widget_WinModifierKeyState_h_
+#define mozilla_widget_WinModifierKeyState_h_
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/EventForwards.h"
+#include <windows.h>
+
+class nsCString;
+
+namespace mozilla {
+namespace widget {
+
+class MOZ_STACK_CLASS ModifierKeyState final
+{
+public:
+ ModifierKeyState();
+ ModifierKeyState(bool aIsShiftDown, bool aIsControlDown, bool aIsAltDown);
+ ModifierKeyState(Modifiers aModifiers);
+
+ void Update();
+
+ void Unset(Modifiers aRemovingModifiers);
+ void Set(Modifiers aAddingModifiers);
+
+ void InitInputEvent(WidgetInputEvent& aInputEvent) const;
+
+ bool IsShift() const;
+ bool IsControl() const;
+ bool IsAlt() const;
+ bool IsAltGr() 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;
+
+ MOZ_ALWAYS_INLINE void EnsureAltGr();
+
+ 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 000000000..10937ba51
--- /dev/null
+++ b/widget/windows/WinMouseScrollHandler.cpp
@@ -0,0 +1,1799 @@
+/* -*- 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 "nsIDOMWheelEvent.h"
+
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/WindowsVersion.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);
+
+ 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,
+ LayoutDeviceIntPoint* aPoint)
+{
+ NS_ENSURE_TRUE_VOID(aWidget);
+ LayoutDeviceIntPoint point;
+ if (aPoint) {
+ point = *aPoint;
+ } else {
+ POINTS pts = GetCurrentMessagePos();
+ POINT pt;
+ pt.x = pts.x;
+ pt.y = pts.y;
+ ::ScreenToClient(aWidget->GetWindowHandle(), &pt);
+ point.x = pt.x;
+ point.y = pt.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()) {
+ 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");
+
+ // 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)) {
+ 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 = nsIDOMWheelEvent::DOM_DELTA_PAGE;
+ break;
+
+ case SB_LINEUP:
+ delta = -1.0;
+ lineOrPageDelta = -1;
+ case SB_LINEDOWN:
+ wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE;
+ break;
+
+ default:
+ return;
+ }
+ modKeyState.InitInputEvent(wheelEvent);
+ // XXX Current mouse position may not be same as when the original message
+ // is received. We need to know the actual mouse cursor position when
+ // the original message was received.
+ InitEvent(aWidget, wheelEvent);
+
+ 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)
+{
+ MOZ_ASSERT(aWheelEvent.mMessage == eWheel);
+
+ // XXX Why don't we use lParam value? We should use lParam value because
+ // our internal message is always posted by original message handler.
+ // So, GetMessagePos() may return different cursor position.
+ InitEvent(aWidget, aWheelEvent);
+
+ 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 ? nsIDOMWheelEvent::DOM_DELTA_PAGE :
+ nsIDOMWheelEvent::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 != nsIDOMWheelEvent::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, %s",
+ IsVistaOrLater() ?
+ "this is unexpected on Vista or later" :
+ "but on XP or earlier, this is not a problem"));
+ // 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() &&
+ (!IsVistaOrLater() || 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::RegisterCallback(OnChange, "mousewheel.", this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "Failed to register callback for mousewheel.");
+}
+
+MouseScrollHandler::UserPrefs::~UserPrefs()
+{
+ DebugOnly<nsresult> rv =
+ Preferences::UnregisterCallback(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);
+ 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, "
+ "mOverriddenVerticalScrollAmount=%d, "
+ "mOverriddenHorizontalScrollAmount=%d, "
+ "mMouseScrollTransactionTimeout=%d",
+ GetBoolName(mScrollMessageHandledAsWheelMessage),
+ GetBoolName(mEnableSystemSettingCache),
+ GetBoolName(mForceEnableSystemSettingCache),
+ 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 commandEvent(true, nsGkAtoms::onAppCommand,
+ (aWParam == VK_NEXT) ? nsGkAtoms::Forward : nsGkAtoms::Back, aWidget);
+ InitEvent(aWidget, commandEvent);
+ aWidget->DispatchWindowEvent(&commandEvent);
+ }
+ 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 000000000..5d8c58ba0
--- /dev/null
+++ b/widget/windows/WinMouseScrollHandler.h
@@ -0,0 +1,625 @@
+/* -*- 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,
+ LayoutDeviceIntPoint* aPoint = nullptr);
+
+ /**
+ * 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);
+
+ 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;
+ }
+
+ 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* aClosure)
+ {
+ static_cast<UserPrefs*>(aClosure)->MarkDirty();
+ }
+
+ bool mInitialized;
+ bool mScrollMessageHandledAsWheelMessage;
+ bool mEnableSystemSettingCache;
+ bool mForceEnableSystemSettingCache;
+ 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 000000000..5ed5ca44a
--- /dev/null
+++ b/widget/windows/WinNativeEventData.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/WinTaskbar.cpp b/widget/windows/WinTaskbar.cpp
new file mode 100644
index 000000000..698b7ec0e
--- /dev/null
+++ b/widget/windows/WinTaskbar.cpp
@@ -0,0 +1,506 @@
+/* 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 <nsIObserverService.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 "mozilla/WindowsVersion.h"
+#include <io.h>
+#include <propvarutil.h>
+#include <propkey.h>
+#include <shellapi.h>
+
+const wchar_t kShellLibraryName[] = L"shell32.dll";
+
+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;
+
+ typedef HRESULT (WINAPI * SHGetPropertyStoreForWindowPtr)
+ (HWND hwnd, REFIID riid, void** ppv);
+ SHGetPropertyStoreForWindowPtr funcGetProStore = nullptr;
+
+ HMODULE hDLL = ::LoadLibraryW(kShellLibraryName);
+ funcGetProStore = (SHGetPropertyStoreForWindowPtr)
+ GetProcAddress(hDLL, "SHGetPropertyStoreForWindow");
+
+ if (!funcGetProStore) {
+ FreeLibrary(hDLL);
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ IPropertyStore* pPropStore;
+ if (FAILED(funcGetProStore(toplevelHWND,
+ IID_PPV_ARGS(&pPropStore)))) {
+ FreeLibrary(hDLL);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PROPVARIANT pv;
+ if (FAILED(InitPropVariantFromString(aIdentifier.get(), &pv))) {
+ pPropStore->Release();
+ FreeLibrary(hDLL);
+ 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);
+ pPropStore->Release();
+ FreeLibrary(hDLL);
+
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// default nsITaskbarPreviewController
+
+class DefaultController final : public nsITaskbarPreviewController
+{
+ ~DefaultController() {}
+ HWND mWnd;
+public:
+ 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;
+}
+
+// deprecated
+NS_IMETHODIMP
+DefaultController::DrawPreview(nsISupports *ctx, bool *rDrawFrame) {
+ *rDrawFrame = true;
+ return NS_ERROR_UNEXPECTED;
+}
+
+// deprecated
+NS_IMETHODIMP
+DefaultController::DrawThumbnail(nsISupports *ctx, uint32_t width, uint32_t height, bool *rDrawFrame) {
+ *rDrawFrame = false;
+ return NS_ERROR_UNEXPECTED;
+}
+
+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) {
+ NS_NOTREACHED("OnClose should not be called for TaskbarWindowPreviews");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::OnActivate(bool *rAcceptActivation) {
+ *rAcceptActivation = true;
+ NS_NOTREACHED("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 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 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->GetNativePath(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. We skip setting the global id in this case as well.
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ if (!appInfo)
+ return false;
+
+ nsCString appName;
+ if (NS_FAILED(appInfo->GetName(appName))) {
+ // We just won't register then, let Windows handle it.
+ return false;
+ }
+
+ 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);
+ }
+ }
+
+ return !aDefaultGroupId.IsEmpty();
+
+ return true;
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetDefaultGroupId(nsAString & aDefaultGroupId) {
+ if (!GetAppUserModelID(aDefaultGroupId))
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+// (static) Called from AppShell
+bool
+WinTaskbar::RegisterAppUserModelID() {
+ if (!IsWin7OrLater())
+ return false;
+
+ SetCurrentProcessExplicitAppUserModelIDPtr funcAppUserModelID = nullptr;
+ bool retVal = false;
+
+ nsAutoString uid;
+ if (!GetAppUserModelID(uid))
+ return false;
+
+ HMODULE hDLL = ::LoadLibraryW(kShellLibraryName);
+
+ funcAppUserModelID = (SetCurrentProcessExplicitAppUserModelIDPtr)
+ GetProcAddress(hDLL, "SetCurrentProcessExplicitAppUserModelID");
+
+ if (!funcAppUserModelID) {
+ ::FreeLibrary(hDLL);
+ return false;
+ }
+
+ if (SUCCEEDED(funcAppUserModelID(uid.get())))
+ retVal = true;
+
+ if (hDLL)
+ ::FreeLibrary(hDLL);
+
+ return retVal;
+}
+
+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 = IsWin7OrLater() && 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;
+
+ 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);
+ preview = new TaskbarWindowPreview(mTaskbar, defaultController, toplevelHWND, shell);
+ if (!preview)
+ return NS_ERROR_OUT_OF_MEMORY;
+ 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 000000000..46968737d
--- /dev/null
+++ b/widget/windows/WinTaskbar.h
@@ -0,0 +1,46 @@
+/* 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 000000000..156c7a79b
--- /dev/null
+++ b/widget/windows/WinTextEventDispatcherListener.cpp
@@ -0,0 +1,78 @@
+/* -*- 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_(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 000000000..d4e7965d9
--- /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_(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 000000000..149513b2f
--- /dev/null
+++ b/widget/windows/WinUtils.cpp
@@ -0,0 +1,2110 @@
+/* -*- 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 "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+#include "KeyboardLayout.h"
+#include "nsIDOMMouseEvent.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/HangMonitor.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/Unused.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/Logging.h"
+
+#include "nsString.h"
+#include "nsDirectoryServiceUtils.h"
+#include "imgIContainer.h"
+#include "imgITools.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "nsIOutputStream.h"
+#include "nsNetCID.h"
+#include "prtime.h"
+#ifdef MOZ_PLACES
+#include "mozIAsyncFavicons.h"
+#endif
+#include "nsIIconURI.h"
+#include "nsIDownloader.h"
+#include "nsINetUtil.h"
+#include "nsIChannel.h"
+#include "nsIObserver.h"
+#include "imgIEncoder.h"
+#include "nsIThread.h"
+#include "MainThreadUtils.h"
+#include "nsLookAndFeel.h"
+#include "nsUnicharUtils.h"
+#include "nsWindowsHelpers.h"
+
+#ifdef NS_ENABLE_TSF
+#include <textstor.h>
+#include "TSFTextStore.h"
+#endif // #ifdef NS_ENABLE_TSF
+
+#include <shlwapi.h>
+
+mozilla::LazyLogModule gWindowsLog("Widget");
+
+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(AsyncDeleteIconFromDisk, nsIRunnable)
+NS_IMPL_ISUPPORTS(AsyncDeleteAllFaviconsFromDisk, nsIRunnable)
+
+
+const char FaviconHelper::kJumpListCacheDir[] = "jumpListCache";
+const char FaviconHelper::kShortcutCacheDir[] = "shortcutCache";
+
+// apis available on vista and up.
+WinUtils::SHCreateItemFromParsingNamePtr WinUtils::sCreateItemFromParsingName = nullptr;
+WinUtils::SHGetKnownFolderPathPtr WinUtils::sGetKnownFolderPath = nullptr;
+
+// We just leak these DLL HMODULEs. There's no point in calling FreeLibrary
+// on them during shutdown anyway.
+static const wchar_t kShellLibraryName[] = L"shell32.dll";
+static HMODULE sShellDll = nullptr;
+static const wchar_t kDwmLibraryName[] = L"dwmapi.dll";
+static HMODULE sDwmDll = nullptr;
+
+WinUtils::DwmExtendFrameIntoClientAreaProc WinUtils::dwmExtendFrameIntoClientAreaPtr = nullptr;
+WinUtils::DwmIsCompositionEnabledProc WinUtils::dwmIsCompositionEnabledPtr = nullptr;
+WinUtils::DwmSetIconicThumbnailProc WinUtils::dwmSetIconicThumbnailPtr = nullptr;
+WinUtils::DwmSetIconicLivePreviewBitmapProc WinUtils::dwmSetIconicLivePreviewBitmapPtr = nullptr;
+WinUtils::DwmGetWindowAttributeProc WinUtils::dwmGetWindowAttributePtr = nullptr;
+WinUtils::DwmSetWindowAttributeProc WinUtils::dwmSetWindowAttributePtr = nullptr;
+WinUtils::DwmInvalidateIconicBitmapsProc WinUtils::dwmInvalidateIconicBitmapsPtr = nullptr;
+WinUtils::DwmDefWindowProcProc WinUtils::dwmDwmDefWindowProcPtr = nullptr;
+WinUtils::DwmGetCompositionTimingInfoProc WinUtils::dwmGetCompositionTimingInfoPtr = nullptr;
+WinUtils::DwmFlushProc WinUtils::dwmFlushProcPtr = nullptr;
+
+// 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;
+#ifdef ACCESSIBILITY
+typedef NTSTATUS (NTAPI* NtTestAlertPtr)(VOID);
+static NtTestAlertPtr sNtTestAlert = nullptr;
+#endif
+
+
+/* static */
+void
+WinUtils::Initialize()
+{
+ if (!sDwmDll && IsVistaOrLater()) {
+ sDwmDll = ::LoadLibraryW(kDwmLibraryName);
+
+ if (sDwmDll) {
+ dwmExtendFrameIntoClientAreaPtr = (DwmExtendFrameIntoClientAreaProc)::GetProcAddress(sDwmDll, "DwmExtendFrameIntoClientArea");
+ dwmIsCompositionEnabledPtr = (DwmIsCompositionEnabledProc)::GetProcAddress(sDwmDll, "DwmIsCompositionEnabled");
+ dwmSetIconicThumbnailPtr = (DwmSetIconicThumbnailProc)::GetProcAddress(sDwmDll, "DwmSetIconicThumbnail");
+ dwmSetIconicLivePreviewBitmapPtr = (DwmSetIconicLivePreviewBitmapProc)::GetProcAddress(sDwmDll, "DwmSetIconicLivePreviewBitmap");
+ dwmGetWindowAttributePtr = (DwmGetWindowAttributeProc)::GetProcAddress(sDwmDll, "DwmGetWindowAttribute");
+ dwmSetWindowAttributePtr = (DwmSetWindowAttributeProc)::GetProcAddress(sDwmDll, "DwmSetWindowAttribute");
+ dwmInvalidateIconicBitmapsPtr = (DwmInvalidateIconicBitmapsProc)::GetProcAddress(sDwmDll, "DwmInvalidateIconicBitmaps");
+ dwmDwmDefWindowProcPtr = (DwmDefWindowProcProc)::GetProcAddress(sDwmDll, "DwmDefWindowProc");
+ dwmGetCompositionTimingInfoPtr = (DwmGetCompositionTimingInfoProc)::GetProcAddress(sDwmDll, "DwmGetCompositionTimingInfo");
+ dwmFlushProcPtr = (DwmFlushProc)::GetProcAddress(sDwmDll, "DwmFlush");
+ }
+ }
+
+ if (IsWin10OrLater()) {
+ HMODULE user32Dll = ::GetModuleHandleW(L"user32");
+ if (user32Dll) {
+ sEnableNonClientDpiScaling = (EnableNonClientDpiScalingProc)
+ ::GetProcAddress(user32Dll, "EnableNonClientDpiScaling");
+ sSetThreadDpiAwarenessContext = (SetThreadDpiAwarenessContextProc)
+ ::GetProcAddress(user32Dll, "SetThreadDpiAwarenessContext");
+ }
+ }
+
+#ifdef ACCESSIBILITY
+ sNtTestAlert = reinterpret_cast<NtTestAlertPtr>(
+ ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtTestAlert"));
+ MOZ_ASSERT(sNtTestAlert);
+#endif
+}
+
+// static
+LRESULT WINAPI
+WinUtils::NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ 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
+double
+WinUtils::SystemScaleFactor()
+{
+ // 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 double systemScale = 0;
+ if (systemScale == 0) {
+ HDC screenDC = GetDC(nullptr);
+ systemScale = GetDeviceCaps(screenDC, LOGPIXELSY) / 96.0;
+ ReleaseDC(nullptr, screenDC);
+
+ if (systemScale == 0) {
+ // Bug 1012487 - This can occur 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 1.0;
+ }
+ }
+ return systemScale;
+}
+
+#ifndef WM_DPICHANGED
+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()
+{
+ if (IsVistaOrLater()) {
+ // 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()
+{
+ static bool perMonitorDPIAware = SlowIsPerMonitorDPIAware();
+ return perMonitorDPIAware;
+}
+
+/* static */
+double
+WinUtils::LogToPhysFactor(HMONITOR aMonitor)
+{
+ if (IsPerMonitorDPIAware()) {
+ UINT dpiX, dpiY = 96;
+ sGetDpiForMonitor(aMonitor ? aMonitor : GetPrimaryMonitor(),
+ MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
+ return dpiY / 96.0;
+ }
+
+ return SystemScaleFactor();
+}
+
+/* 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.x + rect.width)),
+ NSToIntRound(dpiScale * (rect.y + rect.height))
+ };
+
+ return ::MonitorFromRect(&globalWindowBounds, MONITOR_DEFAULTTONEAREST);
+}
+
+#ifdef ACCESSIBILITY
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif
+
+static Atomic<bool> sAPCPending;
+
+/* static */
+void
+WinUtils::SetAPCPending()
+{
+ sAPCPending = true;
+}
+#endif // ACCESSIBILITY
+
+/* static */
+bool
+WinUtils::PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage, UINT aOption)
+{
+#ifdef ACCESSIBILITY
+ if (NS_IsMainThread() && sAPCPending.exchange(false)) {
+ while (sNtTestAlert() != STATUS_SUCCESS) ;
+ }
+#endif
+#ifdef NS_ENABLE_TSF
+ 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;
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ return ::PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, aOption);
+}
+
+/* static */
+bool
+WinUtils::GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage)
+{
+#ifdef NS_ENABLE_TSF
+ 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;
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ return ::GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage);
+}
+
+#if defined(ACCESSIBILITY)
+static DWORD
+GetWaitFlags()
+{
+ DWORD result = MWMO_INPUTAVAILABLE;
+ if (IsVistaOrLater() && 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 = ::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()) {
+ if (sAPCPending.exchange(false)) {
+ // Clear out any pending APCs
+ while (sNtTestAlert() != STATUS_SUCCESS) ;
+ }
+ // 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::HangMonitor::Suspend();
+ }
+ 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)
+{
+ NS_PRECONDITION(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 = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
+ LPARAM lParamExtraInfo = ::GetMessageExtraInfo();
+ if ((lParamExtraInfo & TABLET_INK_SIGNATURE) == TABLET_INK_CHECK) {
+ inputSource = (lParamExtraInfo & TABLET_INK_TOUCH) ?
+ nsIDOMMouseEvent::MOZ_SOURCE_TOUCH : nsIDOMMouseEvent::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 == 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 */
+HRESULT
+WinUtils::SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx *pbc,
+ REFIID riid, void **ppv)
+{
+ if (sCreateItemFromParsingName) {
+ return sCreateItemFromParsingName(pszPath, pbc, riid, ppv);
+ }
+
+ if (!sShellDll) {
+ sShellDll = ::LoadLibraryW(kShellLibraryName);
+ if (!sShellDll) {
+ return false;
+ }
+ }
+
+ sCreateItemFromParsingName = (SHCreateItemFromParsingNamePtr)
+ GetProcAddress(sShellDll, "SHCreateItemFromParsingName");
+ if (!sCreateItemFromParsingName)
+ return E_FAIL;
+
+ return sCreateItemFromParsingName(pszPath, pbc, riid, ppv);
+}
+
+/* static */
+HRESULT
+WinUtils::SHGetKnownFolderPath(REFKNOWNFOLDERID rfid,
+ DWORD dwFlags,
+ HANDLE hToken,
+ PWSTR *ppszPath)
+{
+ if (sGetKnownFolderPath) {
+ return sGetKnownFolderPath(rfid, dwFlags, hToken, ppszPath);
+ }
+
+ if (!sShellDll) {
+ sShellDll = ::LoadLibraryW(kShellLibraryName);
+ if (!sShellDll) {
+ return false;
+ }
+ }
+
+ sGetKnownFolderPath = (SHGetKnownFolderPathPtr)
+ GetProcAddress(sShellDll, "SHGetKnownFolderPath");
+ if (!sGetKnownFolderPath)
+ return E_FAIL;
+
+ return sGetKnownFolderPath(rfid, dwFlags, hToken, ppszPath);
+}
+
+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
+ ************************************************************************/
+
+AsyncFaviconDataReady::AsyncFaviconDataReady(nsIURI *aNewURI,
+ nsCOMPtr<nsIThread> &aIOThread,
+ const bool aURLShortcut):
+ mNewURI(aNewURI),
+ mIOThread(aIOThread),
+ 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_DATA_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->AsyncOpen2(listener);
+}
+
+NS_IMETHODIMP
+AsyncFaviconDataReady::OnComplete(nsIURI *aFaviconURI,
+ uint32_t aDataLen,
+ const uint8_t *aData,
+ const nsACString &aMimeType)
+{
+ 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);
+
+ // Convert the obtained favicon data to an input stream
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ reinterpret_cast<const char*>(aData),
+ aDataLen,
+ NS_ASSIGNMENT_DEPEND);
+ 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->DecodeImageData(stream, 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) {
+ // Create a 48x48 surface and paint the icon into the central 16x16 rect.
+ size.width = 48;
+ size.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(Color(1.0f, 1.0f, 1.0f, 1.0f)));
+ dt->DrawSurface(surface,
+ Rect(16, 16, 16, 16),
+ 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, Move(data),
+ stride,
+ size.width,
+ size.height,
+ mURLShortcut);
+ 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,
+ const bool aURLShortcut) :
+ mURLShortcut(aURLShortcut),
+ mIconPath(aIconPath),
+ mBuffer(Move(aBuffer)),
+ mStride(aStride),
+ mWidth(aWidth),
+ mHeight(aHeight)
+{
+}
+
+NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run()
+{
+ NS_PRECONDITION(!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 = fopen(NS_ConvertUTF16toUTF8(mIconPath).get(), "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) {
+ //NS_ConvertUTF8toUTF16 utf16path(mIconPath);
+ 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 = fopen(NS_ConvertUTF16toUTF8(mIconPath).get(), "wb");
+ if (!file) {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+ }
+ if (!file) {
+ return rv;
+ }
+ }
+ nsresult rv =
+ gfxUtils::EncodeSourceSurface(surface,
+ NS_LITERAL_CSTRING("image/vnd.microsoft.icon"),
+ EmptyString(),
+ gfxUtils::eBinaryEncode,
+ file);
+ fclose(file);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mURLShortcut) {
+ SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETNONCLIENTMETRICS, 0);
+ }
+ return rv;
+}
+
+AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon()
+{
+}
+
+AsyncDeleteIconFromDisk::AsyncDeleteIconFromDisk(const nsAString &aIconPath)
+ : mIconPath(aIconPath)
+{
+}
+
+NS_IMETHODIMP AsyncDeleteIconFromDisk::Run()
+{
+ // Construct the parent path of the passed in path
+ nsCOMPtr<nsIFile> icoFile = do_CreateInstance("@mozilla.org/file/local;1");
+ NS_ENSURE_TRUE(icoFile, NS_ERROR_FAILURE);
+ nsresult rv = icoFile->InitWithPath(mIconPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if the cached ICO file exists
+ bool exists;
+ rv = icoFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check that we aren't deleting some arbitrary file that is not an icon
+ if (StringTail(mIconPath, 4).LowerCaseEqualsASCII(".ico")) {
+ // Check if the cached ICO file exists
+ bool exists;
+ if (NS_FAILED(icoFile->Exists(&exists)) || !exists)
+ return NS_ERROR_FAILURE;
+
+ // We found an ICO file that exists, so we should remove it
+ icoFile->Remove(false);
+ }
+
+ return NS_OK;
+}
+
+AsyncDeleteIconFromDisk::~AsyncDeleteIconFromDisk()
+{
+}
+
+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;
+}
+
+NS_IMETHODIMP AsyncDeleteAllFaviconsFromDisk::Run()
+{
+ // 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(FaviconHelper::kJumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through each directory entry and remove all ICO files found
+ do {
+ bool hasMore = false;
+ if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> supp;
+ if (NS_FAILED(entries->GetNext(getter_AddRefs(supp))))
+ break;
+
+ nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp));
+ 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)
+ */
+nsresult FaviconHelper::ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsString &aICOFilePath,
+ nsCOMPtr<nsIThread> &aIOThread,
+ bool aURLShortcut)
+{
+ // 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);
+ 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);
+ 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)
+{
+#ifdef MOZ_PLACES
+ // Obtain the favicon service and get the favicon for the specified page
+ nsCOMPtr<mozIAsyncFavicons> 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);
+
+ favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback);
+#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 */
+nsIntRegion
+WinUtils::ConvertHRGNToRegion(HRGN aRgn)
+{
+ NS_ASSERTION(aRgn, "Don't pass NULL region here");
+
+ nsIntRegion 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;
+}
+
+nsIntRect
+WinUtils::ToIntRect(const RECT& aRect)
+{
+ return nsIntRect(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(IMEState::Enabled aIMEState)
+{
+ return (aIMEState == IMEState::ENABLED ||
+ aIMEState == IMEState::PLUGIN);
+}
+
+/* static */
+void
+WinUtils::SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
+ uint32_t aModifiers)
+{
+ 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]));
+ }
+ }
+}
+
+/* static */
+bool
+WinUtils::ShouldHideScrollbars()
+{
+ return false;
+}
+
+// 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 (IsWin7OrLater() && IsTouchDeviceSupportPresent()) {
+ return GetSystemMetrics(SM_MAXIMUMTOUCHES);
+ }
+ return 0;
+}
+
+typedef DWORD (WINAPI * GetFinalPathNameByHandlePtr)(HANDLE hFile,
+ LPTSTR lpszFilePath,
+ DWORD cchFilePath,
+ DWORD dwFlags);
+
+/* static */
+bool
+WinUtils::ResolveJunctionPointsAndSymLinks(std::wstring& aPath)
+{
+ // Users folder was introduced with Vista.
+ if (!IsVistaOrLater()) {
+ return true;
+ }
+
+ 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) {
+ return false;
+ }
+
+ // GetFinalPathNameByHandleW is a Vista and later API. Since ESR builds with
+ // XP support still, we need to load the function manually.
+ GetFinalPathNameByHandlePtr getFinalPathNameFnPtr = nullptr;
+ HMODULE kernel32Dll = ::GetModuleHandleW(L"Kernel32");
+ if (kernel32Dll) {
+ getFinalPathNameFnPtr = (GetFinalPathNameByHandlePtr)
+ ::GetProcAddress(kernel32Dll, "GetFinalPathNameByHandleW");
+ }
+
+ if (!getFinalPathNameFnPtr) {
+ return false;
+ }
+
+ DWORD pathLen = getFinalPathNameFnPtr(
+ handle, path, MAX_PATH, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
+ if (pathLen == 0 || pathLen >= MAX_PATH) {
+ 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);
+ }
+
+ return true;
+}
+
+/* static */
+bool
+WinUtils::SanitizePath(const wchar_t* aInputPath, nsAString& aOutput)
+{
+ aOutput.Truncate();
+ wchar_t buffer[MAX_PATH + 1] = {0};
+ if (!PathCanonicalizeW(buffer, aInputPath)) {
+ return false;
+ }
+ wchar_t longBuffer[MAX_PATH + 1] = {0};
+ DWORD longResult = GetLongPathNameW(buffer, longBuffer, MAX_PATH);
+ if (longResult == 0 || longResult > MAX_PATH - 1) {
+ return false;
+ }
+ aOutput.SetLength(MAX_PATH + 1);
+ wchar_t* output = reinterpret_cast<wchar_t*>(aOutput.BeginWriting());
+ if (!PathUnExpandEnvStringsW(longBuffer, output, MAX_PATH)) {
+ return false;
+ }
+ // Truncate to correct length
+ aOutput.Truncate(wcslen(char16ptr_t(aOutput.BeginReading())));
+ MOZ_ASSERT(aOutput.Length() <= MAX_PATH);
+ return true;
+}
+
+/**
+ * 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 GetAppInitDLLs for an example of its usage.
+ */
+/* static */
+void
+WinUtils::GetWhitelistedPaths(
+ nsTArray<mozilla::Pair<nsString,nsDependentString>>& aOutput)
+{
+ aOutput.Clear();
+ aOutput.AppendElement(mozilla::MakePair(
+ nsString(NS_LITERAL_STRING("%ProgramFiles%")),
+ nsDependentString()));
+ // When no substitution is required, set the void flag
+ aOutput.LastElement().second().SetIsVoid(true);
+ wchar_t tmpPath[MAX_PATH + 1] = {0};
+ 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;
+ if (SanitizePath(tmpPath, cleanTmpPath)) {
+ aOutput.AppendElement(mozilla::MakePair(nsString(cleanTmpPath),
+ nsDependentString(L"%TEMP%")));
+ }
+ }
+}
+
+/**
+ * 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;
+ if (IsVistaOrLater()) {
+ 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;
+ }
+ nsTArray<mozilla::Pair<nsString,nsDependentString>> whitelistedPaths;
+ GetWhitelistedPaths(whitelistedPaths);
+ // 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;
+ // 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 (SanitizePath(token, cleanPath)) {
+ bool needsStrip = true;
+ for (uint32_t i = 0; i < whitelistedPaths.Length(); ++i) {
+ const nsString& testPath = whitelistedPaths[i].first();
+ const nsDependentString& substitution = whitelistedPaths[i].second();
+ if (StringBeginsWith(cleanPath, testPath,
+ nsCaseInsensitiveStringComparator())) {
+ if (!substitution.IsVoid()) {
+ cleanPath.Replace(0, testPath.Length(), substitution);
+ }
+ // Whitelisted paths may be used as-is provided that they have been
+ // previously sanitized.
+ needsStrip = false;
+ break;
+ }
+ }
+ if (!aOutput.IsEmpty()) {
+ aOutput += L";";
+ }
+ // For non-whitelisted paths, we strip the path component and just leave
+ // the filename.
+ if (needsStrip) {
+ // nsLocalFile doesn't like non-absolute paths. Since these paths might
+ // contain environment variables instead of roots, we can't use it.
+ wchar_t tmpPath[MAX_PATH + 1] = {0};
+ wcsncpy(tmpPath, cleanPath.get(), cleanPath.Length());
+ PathStripPath(tmpPath);
+ aOutput += tmpPath;
+ } else {
+ aOutput += cleanPath;
+ }
+ }
+ token = wcstok_s(nullptr, kDelimiters, &tokenContext);
+ }
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinUtils.h b/widget/windows/WinUtils.h
new file mode 100644
index 000000000..37469ce07
--- /dev/null
+++ b/widget/windows/WinUtils.h
@@ -0,0 +1,648 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+/**
+ * NS_INLINE_DECL_IUNKNOWN_REFCOUNTING should be used for defining and
+ * implementing AddRef() and Release() of IUnknown interface.
+ * This depends on xpcom/glue/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;
+
+#if !defined(DPI_AWARENESS_CONTEXT_DECLARED) && !defined(DPI_AWARENESS_CONTEXT_UNAWARE)
+
+DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
+
+typedef enum DPI_AWARENESS {
+ DPI_AWARENESS_INVALID = -1,
+ DPI_AWARENESS_UNAWARE = 0,
+ DPI_AWARENESS_SYSTEM_AWARE = 1,
+ DPI_AWARENESS_PER_MONITOR_AWARE = 2
+} DPI_AWARENESS;
+
+#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
+#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
+#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
+
+#define DPI_AWARENESS_CONTEXT_DECLARED
+#endif // (DPI_AWARENESS_CONTEXT_DECLARED)
+
+typedef DPI_AWARENESS_CONTEXT(WINAPI * SetThreadDpiAwarenessContextProc)(DPI_AWARENESS_CONTEXT);
+typedef BOOL(WINAPI * EnableNonClientDpiScalingProc)(HWND);
+
+namespace mozilla {
+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;
+
+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();
+ /**
+ * 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);
+
+ /**
+ * 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 nsIDOMMouseEvent::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);
+
+ /**
+ * SHCreateItemFromParsingName() calls native SHCreateItemFromParsingName()
+ * API which is available on Vista and up.
+ */
+ static HRESULT SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx *pbc,
+ REFIID riid, void **ppv);
+
+ /**
+ * SHGetKnownFolderPath() calls native SHGetKnownFolderPath()
+ * API which is available on Vista and up.
+ */
+ static HRESULT SHGetKnownFolderPath(REFKNOWNFOLDERID rfid,
+ DWORD dwFlags,
+ HANDLE hToken,
+ PWSTR *ppszPath);
+ /**
+ * 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 nsIntRegion.
+ *
+ * aRgn the HRGN to convert.
+ * returns the nsIntRegion.
+ */
+ static nsIntRegion ConvertHRGNToRegion(HRGN aRgn);
+
+ /**
+ * ToIntRect converts a Windows RECT to a nsIntRect.
+ *
+ * aRect the RECT to convert.
+ * returns the nsIntRect.
+ */
+ static nsIntRect 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(IMEState::Enabled aIMEState);
+
+ /**
+ * Returns modifier key array for aModifiers. This is for
+ * nsIWidget::SynthethizeNative*Event().
+ */
+ static void SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
+ uint32_t aModifiers);
+
+ /**
+ * 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();
+
+ /**
+ * 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);
+
+ /**
+ * dwmapi.dll function typedefs and declarations
+ */
+ typedef HRESULT (WINAPI*DwmExtendFrameIntoClientAreaProc)(HWND hWnd, const MARGINS *pMarInset);
+ typedef HRESULT (WINAPI*DwmIsCompositionEnabledProc)(BOOL *pfEnabled);
+ typedef HRESULT (WINAPI*DwmSetIconicThumbnailProc)(HWND hWnd, HBITMAP hBitmap, DWORD dwSITFlags);
+ typedef HRESULT (WINAPI*DwmSetIconicLivePreviewBitmapProc)(HWND hWnd, HBITMAP hBitmap, POINT *pptClient, DWORD dwSITFlags);
+ typedef HRESULT (WINAPI*DwmGetWindowAttributeProc)(HWND hWnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute);
+ typedef HRESULT (WINAPI*DwmSetWindowAttributeProc)(HWND hWnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute);
+ typedef HRESULT (WINAPI*DwmInvalidateIconicBitmapsProc)(HWND hWnd);
+ typedef HRESULT (WINAPI*DwmDefWindowProcProc)(HWND hWnd, UINT msg, LPARAM lParam, WPARAM wParam, LRESULT *aRetValue);
+ typedef HRESULT (WINAPI*DwmGetCompositionTimingInfoProc)(HWND hWnd, DWM_TIMING_INFO *info);
+ typedef HRESULT (WINAPI*DwmFlushProc)(void);
+
+ static DwmExtendFrameIntoClientAreaProc dwmExtendFrameIntoClientAreaPtr;
+ static DwmIsCompositionEnabledProc dwmIsCompositionEnabledPtr;
+ static DwmSetIconicThumbnailProc dwmSetIconicThumbnailPtr;
+ static DwmSetIconicLivePreviewBitmapProc dwmSetIconicLivePreviewBitmapPtr;
+ static DwmGetWindowAttributeProc dwmGetWindowAttributePtr;
+ static DwmSetWindowAttributeProc dwmSetWindowAttributePtr;
+ static DwmInvalidateIconicBitmapsProc dwmInvalidateIconicBitmapsPtr;
+ static DwmDefWindowProcProc dwmDwmDefWindowProcPtr;
+ static DwmGetCompositionTimingInfoProc dwmGetCompositionTimingInfoPtr;
+ static DwmFlushProc dwmFlushProcPtr;
+
+ static void Initialize();
+
+ static bool ShouldHideScrollbars();
+
+ /**
+ * This function normalizes the input path, converts short filenames to long
+ * filenames, and substitutes environment variables for system paths.
+ * The resulting output string length is guaranteed to be <= MAX_PATH.
+ */
+ static bool SanitizePath(const wchar_t* aInputPath, nsAString& aOutput);
+
+ /**
+ * Retrieve a semicolon-delimited list of DLL files derived from AppInit_DLLs
+ */
+ static bool GetAppInitDLLs(nsAString& aOutput);
+
+#ifdef ACCESSIBILITY
+ static void SetAPCPending();
+#endif
+
+private:
+ typedef HRESULT (WINAPI * SHCreateItemFromParsingNamePtr)(PCWSTR pszPath,
+ IBindCtx *pbc,
+ REFIID riid,
+ void **ppv);
+ static SHCreateItemFromParsingNamePtr sCreateItemFromParsingName;
+ typedef HRESULT (WINAPI * SHGetKnownFolderPathPtr)(REFKNOWNFOLDERID rfid,
+ DWORD dwFlags,
+ HANDLE hToken,
+ PWSTR *ppszPath);
+ static SHGetKnownFolderPathPtr sGetKnownFolderPath;
+
+ static void GetWhitelistedPaths(
+ nsTArray<mozilla::Pair<nsString,nsDependentString>>& aOutput);
+};
+
+#ifdef MOZ_PLACES
+class AsyncFaviconDataReady final : public nsIFaviconDataCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFAVICONDATACALLBACK
+
+ AsyncFaviconDataReady(nsIURI *aNewURI,
+ nsCOMPtr<nsIThread> &aIOThread,
+ const bool aURLShortcut);
+ nsresult OnFaviconDataNotAvailable(void);
+private:
+ ~AsyncFaviconDataReady() {}
+
+ nsCOMPtr<nsIURI> mNewURI;
+ nsCOMPtr<nsIThread> mIOThread;
+ const bool mURLShortcut;
+};
+#endif
+
+/**
+ * Asynchronously tries add the list to the build
+ */
+class AsyncEncodeAndWriteIcon : public nsIRunnable
+{
+public:
+ const bool mURLShortcut;
+ 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,
+ const bool aURLShortcut);
+
+private:
+ virtual ~AsyncEncodeAndWriteIcon();
+
+ nsAutoString mIconPath;
+ UniquePtr<uint8_t[]> mBuffer;
+ uint32_t mStride;
+ uint32_t mWidth;
+ uint32_t mHeight;
+};
+
+
+class AsyncDeleteIconFromDisk : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ AsyncDeleteIconFromDisk(const nsAString &aIconPath);
+
+private:
+ virtual ~AsyncDeleteIconFromDisk();
+
+ nsAutoString mIconPath;
+};
+
+class AsyncDeleteAllFaviconsFromDisk : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ AsyncDeleteAllFaviconsFromDisk(bool aIgnoreRecent = false);
+
+private:
+ virtual ~AsyncDeleteAllFaviconsFromDisk();
+
+ int32_t mIcoNoDeleteSeconds;
+ bool mIgnoreRecent;
+};
+
+class FaviconHelper
+{
+public:
+ static const char kJumpListCacheDir[];
+ static const char kShortcutCacheDir[];
+ static nsresult ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsString &aICOFilePath,
+ nsCOMPtr<nsIThread> &aIOThread,
+ bool aURLShortcut);
+
+ 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);
+
+ static int32_t GetICOCacheSecondsTimeout();
+};
+
+} // 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 000000000..b2a3167d3
--- /dev/null
+++ b/widget/windows/WindowHook.cpp
@@ -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/. */
+
+#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)))
+ ? 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 000000000..69df4eb2f
--- /dev/null
+++ b/widget/windows/WindowHook.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_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);
+ }
+ 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;
+};
+
+}
+}
+
+#endif // __mozilla_WindowHook_h__
diff --git a/widget/windows/WindowsUIUtils.cpp b/widget/windows/WindowsUIUtils.cpp
new file mode 100644
index 000000000..223511859
--- /dev/null
+++ b/widget/windows/WindowsUIUtils.cpp
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <winsdkver.h>
+#include "mozwrlbase.h"
+#include "nsServiceManagerUtils.h"
+
+#include "WindowsUIUtils.h"
+
+#include "nsIObserverService.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "nsIXULWindow.h"
+#include "mozilla/Services.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsString.h"
+#include "nsIWidget.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 mozilla;
+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;
+
+/* 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
+ };
+ }
+ }
+ }
+}
+
+#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);
+ }
+ }
+ }
+}
+#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
+
+#endif
+
+WindowsUIUtils::WindowsUIUtils() :
+ mInTabletMode(eTabletModeUnknown)
+{
+}
+
+WindowsUIUtils::~WindowsUIUtils()
+{
+}
+
+/*
+ * Implement the nsISupports methods...
+ */
+NS_IMPL_ISUPPORTS(WindowsUIUtils,
+ nsIWindowsUIUtils)
+
+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;
+ }
+
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ nsCOMPtr<nsIXULWindow> 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;
+
+ 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();
+ NS_NAMED_LITERAL_STRING(tabletMode, "tablet-mode");
+ NS_NAMED_LITERAL_STRING(normalMode, "normal-mode");
+ observerService->NotifyObservers(nullptr, "tablet-mode-change",
+ ((mInTabletMode == eTabletModeOn) ? tabletMode.get() : normalMode.get()));
+ }
+ }
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
diff --git a/widget/windows/WindowsUIUtils.h b/widget/windows/WindowsUIUtils.h
new file mode 100644
index 000000000..a33c93a94
--- /dev/null
+++ b/widget/windows/WindowsUIUtils.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 mozilla_widget_WindowsUIUtils_h__
+#define mozilla_widget_WindowsUIUtils_h__
+
+#include "nsIWindowsUIUtils.h"
+
+enum TabletModeState {
+ eTabletModeUnknown = 0,
+ eTabletModeOff,
+ eTabletModeOn
+};
+
+class WindowsUIUtils final : public nsIWindowsUIUtils {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWINDOWSUIUTILS
+
+ WindowsUIUtils();
+protected:
+ ~WindowsUIUtils();
+
+ TabletModeState mInTabletMode;
+};
+
+#endif // mozilla_widget_WindowsUIUtils_h__
diff --git a/widget/windows/moz.build b/widget/windows/moz.build
new file mode 100644
index 000000000..d4f089eea
--- /dev/null
+++ b/widget/windows/moz.build
@@ -0,0 +1,123 @@
+# -*- 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/.
+
+TEST_DIRS += ['tests']
+
+EXPORTS += [
+ 'nsdefs.h',
+ 'WindowHook.h',
+ 'WinUtils.h',
+]
+
+EXPORTS.mozilla.widget += [
+ 'AudioSession.h',
+ 'CompositorWidgetChild.h',
+ 'CompositorWidgetParent.h',
+ 'InProcessWinCompositorWidget.h',
+ 'WinCompositorWidget.h',
+ 'WinMessages.h',
+ 'WinModifierKeyState.h',
+ 'WinNativeEventData.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AudioSession.cpp',
+ 'CompositorWidgetChild.cpp',
+ 'CompositorWidgetParent.cpp',
+ 'GfxInfo.cpp',
+ 'IEnumFE.cpp',
+ 'IMMHandler.cpp',
+ 'InkCollector.cpp',
+ 'InProcessWinCompositorWidget.cpp',
+ 'JumpListItem.cpp',
+ 'KeyboardLayout.cpp',
+ 'nsAppShell.cpp',
+ 'nsClipboard.cpp',
+ 'nsColorPicker.cpp',
+ 'nsDataObj.cpp',
+ 'nsDataObjCollection.cpp',
+ 'nsDragService.cpp',
+ 'nsIdleServiceWin.cpp',
+ 'nsImageClipboard.cpp',
+ 'nsLookAndFeel.cpp',
+ 'nsNativeDragSource.cpp',
+ 'nsNativeDragTarget.cpp',
+ 'nsNativeThemeWin.cpp',
+ 'nsScreenManagerWin.cpp',
+ 'nsScreenWin.cpp',
+ 'nsSound.cpp',
+ 'nsToolkit.cpp',
+ 'nsUXThemeData.cpp',
+ 'nsWindow.cpp',
+ 'nsWindowBase.cpp',
+ 'nsWindowDbg.cpp',
+ 'nsWindowGfx.cpp',
+ 'nsWinGesture.cpp',
+ 'TaskbarPreview.cpp',
+ 'TaskbarPreviewButton.cpp',
+ 'TaskbarTabPreview.cpp',
+ 'TaskbarWindowPreview.cpp',
+ 'WidgetTraceEvent.cpp',
+ 'WindowHook.cpp',
+ 'WinIMEHandler.cpp',
+ 'WinTaskbar.cpp',
+ 'WinTextEventDispatcherListener.cpp',
+ 'WinUtils.cpp',
+]
+
+# The following files cannot be built in unified mode because of name clashes.
+SOURCES += [
+ 'JumpListBuilder.cpp',
+ 'nsBidiKeyboard.cpp',
+ 'nsFilePicker.cpp',
+ 'nsWidgetFactory.cpp',
+ 'WinCompositorWidget.cpp',
+ 'WindowsUIUtils.cpp',
+ 'WinMouseScrollHandler.cpp',
+]
+
+if CONFIG['MOZ_CRASHREPORTER']:
+ UNIFIED_SOURCES += [
+ 'LSPAnnotator.cpp',
+ ]
+
+if CONFIG['NS_PRINTING']:
+ UNIFIED_SOURCES += [
+ 'nsDeviceContextSpecWin.cpp',
+ 'nsPrintOptionsWin.cpp',
+ 'nsPrintSettingsWin.cpp',
+ ]
+
+if CONFIG['NS_ENABLE_TSF']:
+ SOURCES += [
+ 'TSFTextStore.cpp',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/layout/generic',
+ '/layout/xul',
+ '/toolkit/xre',
+ '/widget',
+ '/xpcom/base',
+]
+
+DEFINES['MOZ_UNICODE'] = True
+
+for var in ('MOZ_ENABLE_D3D10_LAYER'):
+ if CONFIG[var]:
+ DEFINES[var] = True
+
+RESFILE = 'widget.res'
+
+CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
+
+OS_LIBS += [
+ 'rpcrt4',
+]
diff --git a/widget/windows/mozwrlbase.h b/widget/windows/mozwrlbase.h
new file mode 100644
index 000000000..d82be8f04
--- /dev/null
+++ b/widget/windows/mozwrlbase.h
@@ -0,0 +1,77 @@
+/* -*- 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/. */
+
+#pragma once
+
+/*
+ * Includes <wrl.h> and it's children. Defines imports needed by
+ * corewrappers.h in the case where windows.h has already been
+ * included w/WINVER < 0x600. Also ups WINVER/_WIN32_WINNT prior
+ * to including wrl.h. Mozilla's build currently has WINVER set to
+ * 0x502 for XP support.
+ */
+
+#if _WIN32_WINNT < 0x600
+
+#include <windows.h>
+
+VOID
+WINAPI
+ReleaseSRWLockExclusive(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+VOID
+WINAPI
+ReleaseSRWLockShared(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+BOOL
+WINAPI
+InitializeCriticalSectionEx(
+ _Out_ LPCRITICAL_SECTION lpCriticalSection,
+ _In_ DWORD dwSpinCount,
+ _In_ DWORD Flags
+ );
+
+VOID
+WINAPI
+InitializeSRWLock(
+ _Out_ PSRWLOCK SRWLock
+ );
+
+VOID
+WINAPI
+AcquireSRWLockExclusive(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+BOOLEAN
+WINAPI
+TryAcquireSRWLockExclusive(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+BOOLEAN
+WINAPI
+TryAcquireSRWLockShared(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+VOID
+WINAPI
+AcquireSRWLockShared(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+#undef WINVER
+#undef _WIN32_WINNT
+#define WINVER 0x600
+#define _WIN32_WINNT 0x600
+
+#endif // _WIN32_WINNT < 0x600
+
+#include <wrl.h>
diff --git a/widget/windows/nsAppShell.cpp b/widget/windows/nsAppShell.cpp
new file mode 100644
index 000000000..c8820e7d1
--- /dev/null
+++ b/widget/windows/nsAppShell.cpp
@@ -0,0 +1,436 @@
+/* -*- 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 "nsString.h"
+#include "WinIMEHandler.h"
+#include "mozilla/widget/AudioSession.h"
+#include "mozilla/HangMonitor.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+#include "mozilla/StaticPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "GeckoProfiler.h"
+#include "nsComponentManagerUtils.h"
+#include "nsITimer.h"
+
+// These are two messages that the code in winspool.drv on Windows 7 explicitly
+// waits for while it is pumping other Windows messages, during display of the
+// Printer Properties dialog.
+#define MOZ_WM_PRINTER_PROPERTIES_COMPLETION 0x5b7a
+#define MOZ_WM_PRINTER_PROPERTIES_FAILURE 0x5b7f
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define WAKE_LOCK_LOG(...) MOZ_LOG(GetWinWakeLockLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
+PRLogModuleInfo* GetWinWakeLockLog() {
+ static PRLogModuleInfo* log = nullptr;
+ if (!log) {
+ log = PR_NewLogModule("WinWakeLock");
+ }
+ return log;
+}
+
+// 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 WinWakeLockListener final : public nsIDOMMozWakeLockListener
+ , public nsITimerCallback {
+public:
+ NS_DECL_ISUPPORTS;
+
+ NS_IMETHOD Notify(nsITimer *timer) override {
+ WAKE_LOCK_LOG("WinWakeLock: periodic timer fired");
+ ResetScreenSaverTimeout();
+ return NS_OK;
+ }
+private:
+ ~WinWakeLockListener() {}
+
+ NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) {
+ if (!aTopic.EqualsASCII("screen")) {
+ return NS_OK;
+ }
+ // Note the wake lock code ensures that we're not sent duplicate
+ // "locked-foreground" notifications when multipe wake locks are held.
+ if (aState.EqualsASCII("locked-foreground")) {
+ WAKE_LOCK_LOG("WinWakeLock: Blocking screen saver");
+ // We block the screen saver by periodically resetting the screen
+ // saver timeout.
+ StartTimer();
+ // Prevent the display turning off. On Win7 and later this also
+ // blocks the screen saver, but we need the timer started above
+ // to block on Win XP and Vista.
+ SetThreadExecutionState(ES_DISPLAY_REQUIRED|ES_CONTINUOUS);
+ } else {
+ WAKE_LOCK_LOG("WinWakeLock: Unblocking screen saver");
+ // Re-enable screen saver.
+ StopTimer();
+ // Unblock display turning off.
+ SetThreadExecutionState(ES_CONTINUOUS);
+ }
+ return NS_OK;
+ }
+
+ void StartTimer() {
+ ResetScreenSaverTimeout();
+ MOZ_ASSERT(!mTimer);
+ if (mTimer) {
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create screen saver timeout reset timer");
+ return;
+ }
+ // The minimum screensaver timeout that can be specified with Windows' UI
+ // is 60 seconds. We set a timer to re-jig the screen saver 10 seconds
+ // before we expect the timer to run out, but always at least in 1 second
+ // intervals. We reset the timer at a max of 50 seconds, so that if the
+ // user changes the timeout using the UI, we won't be caught out.
+ int32_t timeout = std::max(std::min(50, (int32_t)mScreenSaverTimeout - 10), 1);
+ uint32_t timeoutMs = (uint32_t)timeout * 1000;
+ WAKE_LOCK_LOG("WinWakeLock: Setting periodic timer for %d ms", timeoutMs);
+ rv = timer->InitWithCallback(this,
+ timeoutMs,
+ nsITimer::TYPE_REPEATING_SLACK);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to initialize screen saver timeout reset timer");
+ return;
+ }
+
+ mTimer = timer.forget();
+ }
+
+ void StopTimer() {
+ WAKE_LOCK_LOG("WinWakeLock: StopTimer()");
+ if (!mTimer) {
+ return;
+ }
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ // Resets the operating system's timeout for when to disable the screen.
+ // Called periodically to keep the screensaver off.
+ void ResetScreenSaverTimeout() {
+ if (SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT, 0, &mScreenSaverTimeout, 0)) {
+ SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT, mScreenSaverTimeout, NULL, 0);
+ }
+ WAKE_LOCK_LOG("WinWakeLock: ResetScreenSaverTimeout() mScreenSaverTimeout=%d", mScreenSaverTimeout);
+ }
+
+ UINT mScreenSaverTimeout = 60;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener, nsITimerCallback)
+StaticRefPtr<WinWakeLockListener> sWakeLockListener;
+
+static void
+AddScreenWakeLockListener()
+{
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sWakeLockListener = new WinWakeLockListener();
+ 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;
+ }
+}
+
+namespace mozilla {
+namespace widget {
+// Native event callback message.
+UINT sAppShellGeckoMsgId = RegisterWindowMessageW(L"nsAppShell:EventID");
+} }
+
+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;
+
+//-------------------------------------------------------------------------
+
+/*static*/ LRESULT CALLBACK
+nsAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == sAppShellGeckoMsgId) {
+ nsAppShell *as = reinterpret_cast<nsAppShell *>(lParam);
+ as->NativeEventCallback();
+ NS_RELEASE(as);
+ return TRUE;
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+nsAppShell::~nsAppShell()
+{
+ 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);
+ }
+}
+
+nsresult
+nsAppShell::Init()
+{
+#ifdef MOZ_CRASHREPORTER
+ LSPAnnotate();
+#endif
+
+ mLastNativeEventScheduled = TimeStamp::NowLoRes();
+
+ mozilla::ipc::windows::InitUIThread();
+
+ sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId);
+ NS_ASSERTION(sTaskbarButtonCreatedMsg, "Could not register taskbar button creation message");
+
+ 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, nullptr, nullptr, module, nullptr);
+ NS_ENSURE_STATE(mEventWnd);
+
+ 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.
+ AddScreenWakeLockListener();
+
+ nsresult rv = nsBaseAppShell::Run();
+
+ RemoveScreenWakeLockListener();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void)
+{
+ 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(nullptr);
+ mNativeCallbackPending = false;
+ } else {
+ mNativeCallbackPending = true;
+ }
+}
+
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ // Post a message to the hidden message window
+ NS_ADDREF_THIS(); // will be released when the event is processed
+ {
+ 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::HangMonitor::NotifyActivity(
+ uiMessage ? mozilla::HangMonitor::kUIActivity :
+ mozilla::HangMonitor::kActivityNoUIAVail);
+
+ if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST &&
+ IMEHandler::ProcessRawKeyMessage(msg)) {
+ continue; // the message is consumed.
+ }
+
+ // Store Printer Properties messages for reposting, because 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.
+ if (msg.message == MOZ_WM_PRINTER_PROPERTIES_COMPLETION ||
+ msg.message == MOZ_WM_PRINTER_PROPERTIES_FAILURE) {
+ mMsgsToRepost.push_back(msg);
+ continue;
+ }
+
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ } else if (mayWait) {
+ // Block and wait for any posted application message
+ mozilla::HangMonitor::Suspend();
+ {
+ GeckoProfilerSleepRAII profiler_sleep;
+ 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 000000000..199c30009
--- /dev/null
+++ b/widget/windows/nsAppShell.h
@@ -0,0 +1,59 @@
+/* -*- 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();
+ NS_IMETHOD Exit();
+ 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 000000000..1fac24d6c
--- /dev/null
+++ b/widget/windows/nsBidiKeyboard.cpp
@@ -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/. */
+
+#include <stdio.h>
+#include "nsBidiKeyboard.h"
+#include "WidgetUtils.h"
+#include "prmem.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*) PR_Malloc(keyboards * sizeof(HKL));
+ if (!buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Call again to fill the buffer
+ if (::GetKeyboardLayoutList(keyboards, buf) != keyboards) {
+ PR_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;
+ }
+ }
+ PR_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();
+}
diff --git a/widget/windows/nsBidiKeyboard.h b/widget/windows/nsBidiKeyboard.h
new file mode 100644
index 000000000..b3b56f48f
--- /dev/null
+++ b/widget/windows/nsBidiKeyboard.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 __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 000000000..1afee3496
--- /dev/null
+++ b/widget/windows/nsClipboard.cpp
@@ -0,0 +1,1036 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIClipboardOwner.h"
+#include "nsString.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIFormatConverter.h"
+#include "nsITransferable.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsImageClipboard.h"
+#include "nsIWidget.h"
+#include "nsIComponentManager.h"
+#include "nsWidgetsCID.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIOutputStream.h"
+#include "nsEscape.h"
+#include "nsIObserverService.h"
+
+using mozilla::LogLevel;
+
+PRLogModuleInfo* gWin32ClipboardLog = nullptr;
+
+// 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()
+{
+ if (!gWin32ClipboardLog) {
+ gWin32ClipboardLog = PR_NewLogModule("nsClipboard");
+ }
+
+ 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_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 {
+ delete dataObj;
+ }
+ 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
+ nsCOMPtr<nsIArray> dfList;
+ aTransferable->FlavorsTransferableCanExport(getter_AddRefs(dfList));
+
+ // Walk through flavors that contain data and register them
+ // into the DataObj as supported flavors
+ uint32_t i;
+ uint32_t cnt;
+ dfList->GetLength(&cnt);
+ for (i=0;i<cnt;i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(dfList, i);
+ if ( currentFlavor ) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ // 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, 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, &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 ( strcmp(flavorStr, kUnicodeMime) == 0 ) {
+ // 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 ( strcmp(flavorStr, kHTMLMime) == 0 ) {
+ // 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 ( strcmp(flavorStr, kURLMime) == 0 ) {
+ // 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 ( strcmp(flavorStr, kPNGImageMime) == 0 || strcmp(flavorStr, kJPEGImageMime) == 0 ||
+ strcmp(flavorStr, kJPGImageMime) == 0 || strcmp(flavorStr, kGIFImageMime) == 0 ||
+ strcmp(flavorStr, kNativeImageMime) == 0 ) {
+ // 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, &imageFE);
+ // Add DIBv3
+ SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(flavorStr, &imageFE);
+ }
+ else if ( strcmp(flavorStr, kFilePromiseMime) == 0 ) {
+ // 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 2 bytes (but don't report them!) and
+ // null them out to ensure that all of our strlen calls will succeed.
+ nsresult result = NS_ERROR_FAILURE;
+ if (aHGBL != nullptr) {
+ LPSTR lpStr = (LPSTR) GlobalLock(aHGBL);
+ DWORD allocSize = GlobalSize(aHGBL);
+ char* data = static_cast<char*>(malloc(allocSize + sizeof(char16_t)));
+ if ( data ) {
+ memcpy ( data, lpStr, allocSize );
+ data[allocSize] = data[allocSize + 1] = '\0'; // null terminate for safety
+
+ GlobalUnlock(aHGBL);
+ *aData = data;
+ *aLen = allocSize;
+
+ 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;
+ unsigned char * clipboardData;
+ if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, (void **)&clipboardData, &allocLen)))
+ {
+ nsImageFromClipboard converter;
+ nsIInputStream * inputStream;
+ converter.GetEncodedImageStream(clipboardData, aMIMEImageFormat, &inputStream); // addrefs for us, don't release
+ if ( inputStream ) {
+ *aData = inputStream;
+ *aLen = sizeof(nsIInputStream*);
+ result = NS_OK;
+ }
+ }
+ } 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)));
+ if ( buffer ) {
+ ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
+ *aData = buffer;
+ *aLen = fileNameLen * sizeof(char16_t);
+ result = NS_OK;
+ }
+ else
+ result = NS_ERROR_OUT_OF_MEMORY;
+ }
+ 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)
+ nsCOMPtr<nsIArray> flavorList;
+ res = aTransferable->FlavorsTransferableCanImport ( getter_AddRefs(flavorList) );
+ if ( NS_FAILED(res) )
+ return NS_ERROR_FAILURE;
+
+ // Walk through flavors and see which flavor is on the clipboard them on the native clipboard,
+ uint32_t i;
+ uint32_t cnt;
+ flavorList->GetLength(&cnt);
+ for (i=0;i<cnt;i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if ( currentFlavor ) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ UINT format = GetFormat(flavorStr);
+
+ // 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, &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 ( strcmp(flavorStr, kUnicodeMime) == 0 )
+ dataFound = FindUnicodeFromPlainText ( aDataObject, anIndex, &data, &dataLen );
+ else if ( strcmp(flavorStr, kURLMime) == 0 ) {
+ // 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 ( strcmp(flavorStr, kFileMime) == 0 ) {
+ // 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 ( strcmp(flavorStr, kNativeHTMLMime) == 0 ) {
+ 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 ( strcmp(flavorStr, kHTMLMime) == 0 ) {
+ 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 ( strcmp(flavorStr, kJPEGImageMime) == 0 ||
+ strcmp(flavorStr, kJPGImageMime) == 0 ||
+ strcmp(flavorStr, kPNGImageMime) == 0) {
+ nsIInputStream * imageStream = reinterpret_cast<nsIInputStream*>(data);
+ genericDataWrapper = do_QueryInterface(imageStream);
+ NS_IF_RELEASE(imageStream);
+ }
+ else {
+ // Treat custom types as a string of bytes.
+ if (strcmp(flavorStr, kCustomTypesMime) != 0) {
+ // 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 (strcmp(flavorStr, kRTFMime) == 0) {
+ // 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, genericDataWrapper, dataLen);
+ 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 + NS_LITERAL_STRING("\n") + 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 + NS_LITERAL_STRING("\n") + 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 + NS_LITERAL_STRING("\n") + 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, NS_LITERAL_STRING(".url"), 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 char** aFlavorList,
+ uint32_t aLength,
+ int32_t aWhichClipboard,
+ bool *_retval)
+{
+ *_retval = false;
+ if (aWhichClipboard != kGlobalClipboard || !aFlavorList)
+ return NS_OK;
+
+ for (uint32_t i = 0;i < aLength; ++i) {
+#ifdef DEBUG
+ if (strcmp(aFlavorList[i], kTextMime) == 0)
+ NS_WARNING ( "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD" );
+#endif
+
+ UINT format = GetFormat(aFlavorList[i]);
+ 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 (strcmp(aFlavorList[i], 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.
+ if (IsClipboardFormatAvailable(GetFormat(kTextMime)))
+ *_retval = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsClipboard.h b/widget/windows/nsClipboard.h
new file mode 100644
index 000000000..18308d6db
--- /dev/null
+++ b/widget/windows/nsClipboard.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 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 char** aFlavorList, uint32_t aLength,
+ 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 000000000..777169f97
--- /dev/null
+++ b/widget/windows/nsColorPicker.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+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)
+ : 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_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));
+ }
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIColorPicker
+
+nsColorPicker::nsColorPicker()
+{
+}
+
+nsColorPicker::~nsColorPicker()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+NS_IMETHODIMP
+nsColorPicker::Init(mozIDOMWindowProxy* parent,
+ const nsAString& title,
+ const nsAString& aInitialColor)
+{
+ NS_PRECONDITION(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 000000000..2227ba604
--- /dev/null
+++ b/widget/windows/nsColorPicker.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 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);
+
+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 000000000..fc45968ae
--- /dev/null
+++ b/widget/windows/nsDataObj.cpp
@@ -0,0 +1,2166 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 <ole2.h>
+#include <shlobj.h>
+
+#include "nsDataObj.h"
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsReadableUtils.h"
+#include "nsITransferable.h"
+#include "nsISupportsPrimitives.h"
+#include "IEnumFE.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsXPIDLString.h"
+#include "nsImageClipboard.h"
+#include "nsCRT.h"
+#include "nsPrintfCString.h"
+#include "nsIStringBundle.h"
+#include "nsEscape.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "mozilla/Services.h"
+#include "nsIOutputStream.h"
+#include "nsXPCOMStrings.h"
+#include "nscore.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "nsIPrincipal.h"
+
+#include "WinUtils.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/WindowsVersion.h"
+#include <algorithm>
+
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener)
+
+//-----------------------------------------------------------------------------
+// CStream implementation
+nsDataObj::CStream::CStream() :
+ mChannelRead(false),
+ mStreamRead(0)
+{
+}
+
+//-----------------------------------------------------------------------------
+nsDataObj::CStream::~CStream()
+{
+}
+
+//-----------------------------------------------------------------------------
+// helper - initializes the stream
+nsresult nsDataObj::CStream::Init(nsIURI *pSourceURI,
+ uint32_t aContentPolicyType,
+ nsIPrincipal* aRequestingPrincipal)
+{
+ // 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_DATA_INHERITS,
+ aContentPolicyType,
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_FROM_CACHE);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mChannel->AsyncOpen2(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,
+ nsISupports *aContext,
+ 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,
+ nsISupports *aContext)
+{
+ mChannelResult = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ 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.
+ while (!mChannelRead) {
+ // Pump messages
+ NS_ProcessNextEvent(nullptr, true);
+ }
+
+ if (!mChannelData.Length())
+ mChannelResult = NS_ERROR_FAILURE;
+
+ return mChannelResult;
+}
+
+//-----------------------------------------------------------------------------
+// IStream
+STDMETHODIMP nsDataObj::CStream::Clone(IStream** ppStream)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::Commit(DWORD dwFrags)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::CopyTo(IStream* pDestStream,
+ ULARGE_INTEGER nBytesToCopy,
+ ULARGE_INTEGER* nBytesRead,
+ ULARGE_INTEGER* nBytesWritten)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::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::CStream::Revert(void)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::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::CStream::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.LowPart = (DWORD)mChannelData.Length();
+ statstg->grfMode = STGM_READ;
+ statstg->grfLocksSupported = LOCK_ONLYONCE;
+ statstg->clsid = CLSID_NULL;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::UnlockRegion(ULARGE_INTEGER nStart,
+ ULARGE_INTEGER nBytes,
+ DWORD dwFlags)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::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(getter_AddRefs(requestingPrincipal));
+ MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal");
+ // default transferable content policy is nsIContentPolicy::TYPE_OTHER
+ uint32_t contentPolicyType = nsIContentPolicy::TYPE_OTHER;
+ mTransferable->GetContentPolicyType(&contentPolicyType);
+ rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal);
+ if (NS_FAILED(rv))
+ {
+ pStream->Release();
+ return E_FAIL;
+ }
+ *outStream = pStream;
+
+ return S_OK;
+}
+
+static GUID CLSID_nsDataObj =
+ { 0x1bba7640, 0xdf52, 0x11cf, { 0x82, 0x7b, 0, 0xa0, 0x24, 0x3a, 0xe5, 0x05 } };
+
+/*
+ * 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,
+ NS_LITERAL_CSTRING("nsDataObj"),
+ 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_IAsyncOperation == riid) {
+ *ppv = static_cast<IAsyncOperation*>(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;
+}
+
+
+//-----------------------------------------------------
+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. Delete file anyway and destroy this obj if there's a problem.
+ if (mCachedTempFile) {
+ nsresult rv;
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mTimer->InitWithFuncCallback(nsDataObj::RemoveTempFile, this,
+ 500, nsITimer::TYPE_ONE_SHOT);
+ return AddRef();
+ }
+ mCachedTempFile->Remove(false);
+ mCachedTempFile = nullptr;
+ }
+
+ 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;
+}
+
+// IAsyncOperation 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;
+}
+
+//-----------------------------------------------------
+// GetData and SetData helper functions
+//-----------------------------------------------------
+HRESULT nsDataObj::AddSetFormat(FORMATETC& aFE)
+{
+ return S_OK;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::AddGetFormat(FORMATETC& aFE)
+{
+ 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)
+{
+ ULONG result = E_FAIL;
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(), getter_AddRefs(genericDataWrapper), &len);
+ nsCOMPtr<imgIContainer> image ( do_QueryInterface(genericDataWrapper) );
+ if ( !image ) {
+ // Check if the image was put in an nsISupportsInterfacePointer wrapper.
+ // This might not be necessary any more, but could be useful for backwards
+ // compatibility.
+ nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericDataWrapper));
+ if ( ptr ) {
+ nsCOMPtr<nsISupports> supports;
+ ptr->GetData(getter_AddRefs(supports));
+ image = do_QueryInterface(supports);
+ }
+ }
+
+ if ( image ) {
+ // use the |nsImageToClipboard| helper class to build up a bitmap. We now own
+ // the bits, and pass them back to the OS in |aSTG|.
+ nsImageToClipboard converter(image, aFormat.cfFormat == CF_DIBV5);
+ HANDLE bits = nullptr;
+ nsresult rv = converter.GetPicture ( &bits );
+ if ( NS_SUCCEEDED(rv) && bits ) {
+ aSTG.hGlobal = bits;
+ aSTG.tymed = TYMED_HGLOBAL;
+ result = S_OK;
+ }
+ } // if we have an image
+ else
+ NS_WARNING ( "Definitely not an image on clipboard" );
+ return result;
+}
+
+
+
+//
+// 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 char16_t * aName, nsXPIDLString & 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, getter_Copies(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)) {
+ nsXPIDLString untitled;
+ if (!GetLocalizedString(u"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)) {
+ nsXPIDLString untitled;
+ if (!GetLocalizedString(u"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;
+ }
+
+ const char *shortcutFormatStr;
+ int totalLen;
+ nsCString path;
+ if (!Preferences::GetBool(kShellIconPref, true) ||
+ !IsVistaOrLater()) {
+ 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;
+
+ mozilla::widget::FaviconHelper::ObtainCachedIconFile(aUri, aUriHash, mIOThread, true);
+
+ rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true);
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+ rv = icoFile->GetNativePath(path);
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+
+ shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n"
+ "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
+ "IconIndex=0\r\n";
+ const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice
+ totalLen = formatLen + asciiUrl.Length() +
+ path.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
+ HGLOBAL hGlobalMemory = ::GlobalAlloc(GMEM_SHARE, totalLen);
+ if ( !hGlobalMemory )
+ return E_OUTOFMEMORY;
+
+ char* contents = reinterpret_cast<char*>(::GlobalLock(hGlobalMemory));
+ if ( !contents ) {
+ ::GlobalFree( hGlobalMemory );
+ 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(), path.get());
+ }
+
+ ::GlobalUnlock(hGlobalMemory);
+ aSTG.hGlobal = hGlobalMemory;
+ 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
+ nsCOMPtr<nsIArray> flavorList;
+ mTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ NS_ENSURE_TRUE(flavorList, false);
+
+ // try to find requested flavour
+ uint32_t cnt;
+ flavorList->GetLength(&cnt);
+ for (uint32_t i = 0; i < cnt; ++i) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (currentFlavor) {
+ nsAutoCString flavorStr;
+ currentFlavor->GetData(flavorStr);
+ if (flavorStr.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;
+ uint32_t len;
+
+ // 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;
+ mTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &len);
+ if ( !len )
+ return E_FAIL;
+ nsPrimitiveHelpers::CreateDataFromPrimitive ( 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;
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+
+ mTransferable->GetTransferData(kFileMime, getter_AddRefs(genericDataWrapper),
+ &len);
+ nsCOMPtr<nsIFile> file ( do_QueryInterface(genericDataWrapper) );
+
+ if (!file)
+ {
+ nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericDataWrapper));
+ if (ptr) {
+ nsCOMPtr<nsISupports> supports;
+ ptr->GetData(getter_AddRefs(supports));
+ file = do_QueryInterface(supports);
+ }
+ }
+
+ 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) {
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+
+ mTransferable->GetTransferData(kNativeImageMime, getter_AddRefs(genericDataWrapper), &len);
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper));
+
+ if (!image) {
+ // Check if the image was put in an nsISupportsInterfacePointer wrapper.
+ // This might not be necessary any more, but could be useful for backwards
+ // compatibility.
+ nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericDataWrapper));
+ if (ptr) {
+ nsCOMPtr<nsISupports> supports;
+ ptr->GetData(getter_AddRefs(supports));
+ image = do_QueryInterface(supports);
+ }
+ }
+
+ if (!image)
+ return E_FAIL;
+
+ // Use the clipboard helper class to build up a memory bitmap.
+ nsImageToClipboard converter(image);
+ HANDLE bits = nullptr;
+ rv = converter.GetPicture(&bits); // Clipboard routines return a global handle we own.
+
+ if (NS_FAILED(rv) || !bits)
+ return E_FAIL;
+
+ // We now own these bits!
+ uint32_t bitmapSize = GlobalSize(bits);
+ if (!bitmapSize) {
+ GlobalFree(bits);
+ 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) {
+ GlobalFree(bits);
+ 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)) {
+ GlobalFree(bits);
+ 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)) {
+ GlobalFree(bits);
+ return E_FAIL;
+ }
+
+ char * bm = (char *)GlobalLock(bits);
+
+ BITMAPFILEHEADER fileHdr;
+ BITMAPINFOHEADER *bmpHdr = (BITMAPINFOHEADER*)bm;
+
+ fileHdr.bfType = ((WORD) ('M' << 8) | 'B');
+ fileHdr.bfSize = GlobalSize (bits) + sizeof(fileHdr);
+ fileHdr.bfReserved1 = 0;
+ fileHdr.bfReserved2 = 0;
+ fileHdr.bfOffBits = (DWORD) (sizeof(fileHdr) + bmpHdr->biSize);
+
+ uint32_t writeCount = 0;
+ if (NS_FAILED(outStream->Write((const char *)&fileHdr, sizeof(fileHdr), &writeCount)) ||
+ NS_FAILED(outStream->Write((const char *)bm, bitmapSize, &writeCount)))
+ rv = NS_ERROR_FAILURE;
+
+ outStream->Close();
+
+ GlobalUnlock(bits);
+ GlobalFree(bits);
+
+ if (NS_FAILED(rv))
+ return E_FAIL;
+ }
+
+ // 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_UTF16ToCString(wideFileName, NS_CSTRING_ENCODING_NATIVE_FILESYSTEM, 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;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::GetMetafilePict(FORMATETC&, STGMEDIUM&)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::SetBitmap(FORMATETC&, STGMEDIUM&)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::SetDib(FORMATETC&, STGMEDIUM&)
+{
+ return E_FAIL;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::SetText (FORMATETC& aFE, STGMEDIUM& aSTG)
+{
+ return E_FAIL;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::SetMetafilePict(FORMATETC&, STGMEDIUM&)
+{
+ return E_FAIL;
+}
+
+
+
+//-----------------------------------------------------
+//-----------------------------------------------------
+CLSID nsDataObj::GetClassID() const
+{
+ return CLSID_nsDataObj;
+}
+
+//-----------------------------------------------------
+// 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;
+
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericURL;
+ if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLMime, getter_AddRefs(genericURL), &len)) ) {
+ 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), &len)) ||
+ NS_SUCCEEDED(mTransferable->GetTransferData(kURLPrivateMime, getter_AddRefs(genericURL), &len)) ) {
+ 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;
+
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericURL;
+ if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLMime, getter_AddRefs(genericURL), &len)) ) {
+ 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, (len/2) - (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);
+
+ NS_NAMED_LITERAL_CSTRING(htmlHeaderString, "<html><body>\r\n");
+
+ NS_NAMED_LITERAL_CSTRING(fragmentHeaderString, "<!--StartFragment-->");
+
+ 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);
+ 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;
+ uint32_t dataSize = 0;
+ mTransferable->GetTransferData(kFilePromiseURLMime, getter_AddRefs(urlPrimitive), &dataSize);
+ 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;
+ mTransferable->GetTransferData(kFilePromiseDestFilename, getter_AddRefs(fileNamePrimitive), &dataSize);
+ 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_UTF16ToCString(wideFileName, NS_CSTRING_ENCODING_NATIVE_FILESYSTEM, 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;
+}
+
+void nsDataObj::RemoveTempFile(nsITimer* aTimer, void* aClosure)
+{
+ nsDataObj *timedDataObj = static_cast<nsDataObj *>(aClosure);
+ if (timedDataObj->mCachedTempFile) {
+ timedDataObj->mCachedTempFile->Remove(false);
+ timedDataObj->mCachedTempFile = nullptr;
+ }
+ timedDataObj->Release();
+}
diff --git a/widget/windows/nsDataObj.h b/widget/windows/nsDataObj.h
new file mode 100644
index 000000000..2d7fb75ee
--- /dev/null
+++ b/widget/windows/nsDataObj.h
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsIInputStream.h"
+#include "nsIStreamListener.h"
+#include "nsIChannel.h"
+#include "nsCOMArray.h"
+#include "nsITimer.h"
+
+class nsIThread;
+class nsIPrincipal;
+
+// The SDK shipping with VC11 has renamed IAsyncOperation to
+// IDataObjectAsyncCapability. We try to detect this, and rename this in our
+// code too to make sure that we pick the correct name when building.
+#ifdef __IDataObjectAsyncCapability_INTERFACE_DEFINED__
+#define IAsyncOperation IDataObjectAsyncCapability
+#define IID_IAsyncOperation IID_IDataObjectAsyncCapability
+#else
+// XXX for older version of PSDK where IAsyncOperation and related stuff is not available
+// but thisdefine should be removed when parocles config is updated
+#ifndef __IAsyncOperation_INTERFACE_DEFINED__
+// IAsyncOperation interface definition
+EXTERN_C const IID IID_IAsyncOperation;
+
+MIDL_INTERFACE("3D8B0590-F691-11d2-8EA9-006097DF5BD4")
+IAsyncOperation : public IUnknown
+{
+ virtual HRESULT STDMETHODCALLTYPE SetAsyncMode(BOOL fDoOpAsync) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetAsyncMode(BOOL *pfIsOpAsync) = 0;
+ virtual HRESULT STDMETHODCALLTYPE StartOperation(IBindCtx *pbcReserved) = 0;
+ virtual HRESULT STDMETHODCALLTYPE InOperation(BOOL *pfInAsyncOp) = 0;
+ virtual HRESULT STDMETHODCALLTYPE EndOperation(HRESULT hResult,
+ IBindCtx *pbcReserved,
+ DWORD dwEffects) = 0;
+};
+// this is not defined in the old headers for some reason
+#ifndef FD_PROGRESSUI
+ #define FD_PROGRESSUI 0x4000
+#endif
+
+#endif // __IAsyncOperation_INTERFACE_DEFINED__
+#endif // __IDataObjectAsyncCapability_INTERFACE_DEFINED__
+
+/*
+ * CFSTR_SHELLURL is deprecated and doesn't have a Unicode version.
+ * Therefore we are using CFSTR_INETURL instead of CFSTR_SHELLURL.
+ * See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_basics/shell_basics_programming/transferring/clipboard.asp
+ */
+#ifndef CFSTR_INETURLA
+#define CFSTR_INETURLA L"UniformResourceLocator"
+#endif
+#ifndef CFSTR_INETURLW
+#define CFSTR_INETURLW L"UniformResourceLocatorW"
+#endif
+
+// For support of MinGW w32api v2.4.
+// When the next version of w32api is released with shlobj.h rev 1.35
+// http://sources.redhat.com/cgi-bin/cvsweb.cgi/src/winsup/w32api/include/shlobj.h?cvsroot=src
+// then that can be made the base required version and this code should be removed.
+#ifndef CFSTR_FILEDESCRIPTORA
+# define CFSTR_FILEDESCRIPTORA L"FileGroupDescriptor"
+#endif
+#ifndef CFSTR_FILEDESCRIPTORW
+# define CFSTR_FILEDESCRIPTORW L"FileGroupDescriptorW"
+#endif
+
+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 IAsyncOperation
+{
+
+protected:
+ nsCOMPtr<nsIThread> mIOThread;
+
+ public: // construction, destruction
+ nsDataObj(nsIURI *uri = nullptr);
+ virtual ~nsDataObj();
+
+ public: // IUnknown methods - see iunknown.h for documentation
+ STDMETHODIMP_(ULONG) AddRef ();
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) Release ();
+
+ // support for clipboard
+ virtual void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE);
+ void SetTransferable(nsITransferable * aTransferable);
+
+ // Return the registered OLE class ID of this object's CfDataObj.
+ CLSID GetClassID() const;
+
+ 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);
+
+ // Similar to GetData except that the caller allocates the structure
+ // referenced by pSTM.
+ STDMETHODIMP GetDataHere (LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+
+ // Returns S_TRUE if this object supports the format specified by pSTM,
+ // S_FALSE otherwise.
+ STDMETHODIMP QueryGetData (LPFORMATETC pFE);
+
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // Turn off advising of a previous call to DAdvise which set pConn.
+ STDMETHODIMP DUnadvise (DWORD pConn);
+
+ // 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);
+
+ // IAsyncOperation methods
+ STDMETHOD(EndOperation)(HRESULT hResult, IBindCtx *pbcReserved, DWORD dwEffects);
+ STDMETHOD(GetAsyncMode)(BOOL *pfIsOpAsync);
+ STDMETHOD(InOperation)(BOOL *pfInAsyncOp);
+ STDMETHOD(SetAsyncMode)(BOOL fDoOpAsync);
+ STDMETHOD(StartOperation)(IBindCtx *pbcReserved);
+
+ public: // other methods
+
+ // Gets the filename from the kFilePromiseURLMime flavour
+ HRESULT GetDownloadDetails(nsIURI **aSourceURI,
+ nsAString &aFilename);
+
+ protected:
+ // help determine the kind of drag
+ bool IsFlavourPresent(const char *inFlavour);
+
+ virtual HRESULT AddSetFormat(FORMATETC& FE);
+ virtual HRESULT AddGetFormat(FORMATETC& FE);
+
+ virtual HRESULT GetFile ( FORMATETC& aFE, STGMEDIUM& aSTG );
+ virtual HRESULT GetText ( const nsACString& aDF, FORMATETC& aFE, STGMEDIUM & aSTG );
+ virtual HRESULT GetDib ( const nsACString& inFlavor, FORMATETC &, STGMEDIUM & aSTG );
+ virtual HRESULT GetMetafilePict(FORMATETC& FE, STGMEDIUM& STM);
+
+ virtual HRESULT DropImage( FORMATETC& aFE, STGMEDIUM& aSTG );
+ virtual HRESULT DropFile( FORMATETC& aFE, STGMEDIUM& aSTG );
+ virtual HRESULT DropTempFile( FORMATETC& aFE, STGMEDIUM& aSTG );
+
+ virtual HRESULT GetUniformResourceLocator ( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) ;
+ virtual HRESULT ExtractUniformResourceLocatorA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT ExtractUniformResourceLocatorW ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetFileDescriptor ( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) ;
+ virtual HRESULT GetFileContents ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetPreferredDropEffect ( FORMATETC& aFE, STGMEDIUM& aSTG );
+
+ virtual HRESULT SetBitmap(FORMATETC& FE, STGMEDIUM& STM);
+ virtual HRESULT SetDib (FORMATETC& FE, STGMEDIUM& STM);
+ virtual HRESULT SetText (FORMATETC& FE, STGMEDIUM& STM);
+ virtual HRESULT SetMetafilePict(FORMATETC& FE, STGMEDIUM& STM);
+
+ // Provide the structures needed for an internet shortcut by the shell
+ virtual HRESULT GetFileDescriptorInternetShortcutA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetFileDescriptorInternetShortcutW ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetFileContentsInternetShortcut ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+
+ // IStream implementation
+ virtual HRESULT GetFileDescriptor_IStreamA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetFileDescriptor_IStreamW ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual 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;
+
+ BOOL FormatsMatch(const FORMATETC& source, const FORMATETC& target) const;
+
+ ULONG m_cRef; // the reference count
+
+ nsTArray<nsCString> mDataFlavors;
+
+ nsITransferable * mTransferable; // nsDataObj owns and ref counts nsITransferable,
+ // the nsITransferable does know anything about the nsDataObj
+
+ CEnumFormatEtc * m_enumFE; // Ownership Rules:
+ // nsDataObj owns and ref counts CEnumFormatEtc,
+
+ 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 CStream : public IStream, public nsIStreamListener
+ {
+ nsCOMPtr<nsIChannel> mChannel;
+ FallibleTArray<uint8_t> mChannelData;
+ bool mChannelRead;
+ nsresult mChannelResult;
+ uint32_t mStreamRead;
+
+ protected:
+ virtual ~CStream();
+ nsresult WaitForCompletion();
+
+ public:
+ CStream();
+ nsresult Init(nsIURI *pSourceURI,
+ uint32_t aContentPolicyType,
+ nsIPrincipal* aRequestingPrincipal);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ // IUnknown
+ STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult);
+
+ // IStream
+ STDMETHOD(Clone)(IStream** ppStream);
+ STDMETHOD(Commit)(DWORD dwFrags);
+ STDMETHOD(CopyTo)(IStream* pDestStream, ULARGE_INTEGER nBytesToCopy, ULARGE_INTEGER* nBytesRead, ULARGE_INTEGER* nBytesWritten);
+ STDMETHOD(LockRegion)(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, DWORD dwFlags);
+ STDMETHOD(Read)(void* pvBuffer, ULONG nBytesToRead, ULONG* nBytesRead);
+ STDMETHOD(Revert)(void);
+ STDMETHOD(Seek)(LARGE_INTEGER nMove, DWORD dwOrigin, ULARGE_INTEGER* nNewPos);
+ STDMETHOD(SetSize)(ULARGE_INTEGER nNewSize);
+ STDMETHOD(Stat)(STATSTG* statstg, DWORD dwFlags);
+ STDMETHOD(UnlockRegion)(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, DWORD dwFlags);
+ STDMETHOD(Write)(const void* pvBuffer, ULONG nBytesToRead, ULONG* nBytesRead);
+ };
+
+ HRESULT CreateStream(IStream **outStream);
+
+ 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);
+ static void RemoveTempFile(nsITimer* aTimer, void* aClosure);
+};
+
+
+#endif // _NSDATAOBJ_H_
diff --git a/widget/windows/nsDataObjCollection.cpp b/widget/windows/nsDataObjCollection.cpp
new file mode 100644
index 000000000..7399272e7
--- /dev/null
+++ b/widget/windows/nsDataObjCollection.cpp
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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()
+ : m_cRef(0)
+{
+}
+
+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_IAsyncOperation == riid) {
+ *ppv = static_cast<IAsyncOperation*>(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 000000000..06bd36a7d
--- /dev/null
+++ b/widget/windows/nsDataObjCollection.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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();
+ ~nsDataObjCollection();
+
+ public: // IUnknown methods - see iunknown.h for documentation
+ STDMETHODIMP_(ULONG) AddRef ();
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) Release ();
+
+ public: // DataGet and DataSet helper methods
+ virtual HRESULT GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ virtual HRESULT GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ virtual HRESULT GetFileDescriptors(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ virtual HRESULT GetFileContents(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ virtual 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);
+
+ // from nsPIDataObjCollection
+ void AddDataObject(IDataObject * aDataObj);
+ int32_t GetNumDataObjects() { return mDataObjects.Length(); }
+ nsDataObj* GetDataObjectAt(uint32_t aItem)
+ { return mDataObjects.SafeElementAt(aItem, RefPtr<nsDataObj>()); }
+
+ // Return the registered OLE class ID of this object's CfDataObj.
+ CLSID GetClassID() const;
+
+ 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);
+
+ // Similar to GetData except that the caller allocates the structure
+ // referenced by pSTM.
+ STDMETHODIMP GetDataHere (LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+
+ // Returns S_TRUE if this object supports the format specified by pSTM,
+ // S_FALSE otherwise.
+ STDMETHODIMP QueryGetData (LPFORMATETC pFE);
+
+ // 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);
+
+ protected:
+ ULONG m_cRef; // the reference count
+
+ nsTArray<RefPtr<nsDataObj> > mDataObjects;
+};
+
+#endif //
diff --git a/widget/windows/nsDeviceContextSpecWin.cpp b/widget/windows/nsDeviceContextSpecWin.cpp
new file mode 100644
index 000000000..b7750be58
--- /dev/null
+++ b/widget/windows/nsDeviceContextSpecWin.cpp
@@ -0,0 +1,669 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "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 "prmem.h"
+
+#include <winspool.h>
+
+#include "nsIWidget.h"
+
+#include "nsTArray.h"
+#include "nsIPrintSettingsWin.h"
+
+#include "nsString.h"
+#include "nsIServiceManager.h"
+#include "nsReadableUtils.h"
+#include "nsStringEnumerator.h"
+
+#include "gfxWindowsSurface.h"
+
+#include "nsIFileStreams.h"
+#include "nsIWindowWatcher.h"
+#include "nsIDOMWindow.h"
+#include "mozilla/Services.h"
+#include "nsWindowsHelpers.h"
+
+#include "mozilla/gfx/Logging.h"
+
+#include "mozilla/Logging.h"
+static mozilla::LazyLogModule kWidgetPrintingLogMod("printing-widget");
+#define PR_PL(_p1) MOZ_LOG(kWidgetPrintingLogMod, mozilla::LogLevel::Debug, _p1)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+static const wchar_t kDriverName[] = L"WINSPOOL";
+
+//----------------------------------------------------------------------------------
+// The printer data is shared between the PrinterEnumerator and the nsDeviceContextSpecWin
+// The PrinterEnumerator creates the printer info
+// but the nsDeviceContextSpecWin cleans it up
+// If it gets created (via the Page Setup Dialog) but the user never prints anything
+// then it will never be delete, so this class takes care of that.
+class GlobalPrinters {
+public:
+ static GlobalPrinters* GetInstance() { return &mGlobalPrinters; }
+ ~GlobalPrinters() { FreeGlobalPrinters(); }
+
+ void FreeGlobalPrinters();
+
+ bool PrintersAreAllocated() { return mPrinters != nullptr; }
+ LPWSTR GetItemFromList(int32_t aInx) { return mPrinters?mPrinters->ElementAt(aInx):nullptr; }
+ nsresult EnumeratePrinterList();
+ void GetDefaultPrinterName(nsString& aDefaultPrinterName);
+ uint32_t GetNumPrinters() { return mPrinters?mPrinters->Length():0; }
+
+protected:
+ GlobalPrinters() {}
+ nsresult EnumerateNativePrinters();
+ void ReallocatePrinters();
+
+ static GlobalPrinters mGlobalPrinters;
+ static nsTArray<LPWSTR>* mPrinters;
+};
+//---------------
+// static members
+GlobalPrinters GlobalPrinters::mGlobalPrinters;
+nsTArray<LPWSTR>* GlobalPrinters::mPrinters = nullptr;
+
+struct AutoFreeGlobalPrinters
+{
+ ~AutoFreeGlobalPrinters() {
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+ }
+};
+
+//----------------------------------------------------------------------------------
+nsDeviceContextSpecWin::nsDeviceContextSpecWin()
+{
+ mDriverName = nullptr;
+ mDeviceName = nullptr;
+ mDevMode = nullptr;
+
+}
+
+
+//----------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecWin, nsIDeviceContextSpec)
+
+nsDeviceContextSpecWin::~nsDeviceContextSpecWin()
+{
+ SetDeviceName(nullptr);
+ SetDriverName(nullptr);
+ SetDevMode(nullptr);
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(mPrintSettings));
+ if (psWin) {
+ psWin->SetDeviceName(nullptr);
+ psWin->SetDriverName(nullptr);
+ psWin->SetDevMode(nullptr);
+ }
+
+ // Free them, we won't need them for a while
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+}
+
+
+//------------------------------------------------------------------
+// helper
+static char16_t * GetDefaultPrinterNameFromGlobalPrinters()
+{
+ nsAutoString printerName;
+ GlobalPrinters::GetInstance()->GetDefaultPrinterName(printerName);
+ return ToNewUnicode(printerName);
+}
+
+//----------------------------------------------------------------------------------
+NS_IMETHODIMP nsDeviceContextSpecWin::Init(nsIWidget* aWidget,
+ nsIPrintSettings* aPrintSettings,
+ bool aIsPrintPreview)
+{
+ mPrintSettings = aPrintSettings;
+
+ nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE;
+ if (aPrintSettings) {
+ // 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.
+ mPrintSettings->GetOutputFormat(&mOutputFormat);
+ if ((XRE_IsContentProcess() &&
+ Preferences::GetBool("print.print_via_parent")) ||
+ mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
+ if (psWin) {
+ char16_t* deviceName;
+ char16_t* driverName;
+ psWin->GetDeviceName(&deviceName); // creates new memory (makes a copy)
+ psWin->GetDriverName(&driverName); // creates new memory (makes a copy)
+
+ LPDEVMODEW devMode;
+ psWin->GetDevMode(&devMode); // creates new memory (makes a copy)
+
+ if (deviceName && driverName && 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);
+
+ // clean up
+ free(deviceName);
+ free(driverName);
+
+ return NS_OK;
+ } else {
+ PR_PL(("***** nsDeviceContextSpecWin::Init - deviceName/driverName/devMode was NULL!\n"));
+ if (deviceName) free(deviceName);
+ if (driverName) free(driverName);
+ if (devMode) ::HeapFree(::GetProcessHeap(), 0, devMode);
+ }
+ }
+ } else {
+ PR_PL(("***** nsDeviceContextSpecWin::Init - aPrintSettingswas NULL!\n"));
+ }
+
+ // Get the Printer Name to be used and output format.
+ char16_t * printerName = nullptr;
+ if (mPrintSettings) {
+ mPrintSettings->GetPrinterName(&printerName);
+ }
+
+ // If there is no name then use the default printer
+ if (!printerName || (printerName && !*printerName)) {
+ printerName = GetDefaultPrinterNameFromGlobalPrinters();
+ }
+
+ NS_ASSERTION(printerName, "We have to have a printer name");
+ if (!printerName || !*printerName) return rv;
+
+ return GetDataFromPrinter(printerName, mPrintSettings);
+}
+
+//----------------------------------------------------------
+// Helper Function - Free and reallocate the string
+static void CleanAndCopyString(wchar_t*& aStr, const wchar_t* aNewStr)
+{
+ if (aStr != nullptr) {
+ if (aNewStr != nullptr && wcslen(aStr) > wcslen(aNewStr)) { // reuse it if we can
+ wcscpy(aStr, aNewStr);
+ return;
+ } else {
+ PR_Free(aStr);
+ aStr = nullptr;
+ }
+ }
+
+ if (nullptr != aNewStr) {
+ aStr = (wchar_t *)PR_Malloc(sizeof(wchar_t)*(wcslen(aNewStr) + 1));
+ wcscpy(aStr, aNewStr);
+ }
+}
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecWin::MakePrintTarget()
+{
+ NS_ASSERTION(mDevMode, "DevMode can't be NULL here");
+
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ nsXPIDLString filename;
+ mPrintSettings->GetToFileName(getter_Copies(filename));
+
+ 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;
+
+ nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
+ nsresult rv = file->InitWithPath(filename);
+ 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::Truncate(width, height));
+ }
+
+ if (mDevMode) {
+ NS_WARNING_ASSERTION(mDriverName, "No driver!");
+ HDC dc = ::CreateDCW(mDriverName, mDeviceName, 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()
+{
+ // To match the previous printing code we need to return 72 when printing to
+ // PDF and 144 when printing to a Windows surface.
+ return mOutputFormat == nsIPrintSettings::kOutputFormatPDF ? 72.0f : 144.0f;
+}
+
+float
+nsDeviceContextSpecWin::GetPrintingScale()
+{
+ MOZ_ASSERT(mPrintSettings);
+
+ // To match the previous printing code there is no scaling for PDF.
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ return 1.0f;
+ }
+
+ // The print settings will have the resolution stored from the real device.
+ int32_t resolution;
+ mPrintSettings->GetResolution(&resolution);
+ return float(resolution) / GetDPI();
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDeviceName(char16ptr_t aDeviceName)
+{
+ CleanAndCopyString(mDeviceName, aDeviceName);
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDriverName(char16ptr_t aDriverName)
+{
+ CleanAndCopyString(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(char16ptr_t aName, nsIPrintSettings* aPS)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (!GlobalPrinters::GetInstance()->PrintersAreAllocated()) {
+ rv = GlobalPrinters::GetInstance()->EnumeratePrinterList();
+ if (NS_FAILED(rv)) {
+ PR_PL(("***** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't enumerate printers!\n"));
+ DISPLAY_LAST_ERROR
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsHPRINTER hPrinter = nullptr;
+ wchar_t *name = (wchar_t*)aName; // 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",
+ aName ? NS_ConvertUTF16toUTF8(aName).get() : "", GetLastError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ 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, aName, 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(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 Enumerator
+//***********************************************************
+nsPrinterEnumeratorWin::nsPrinterEnumeratorWin()
+{
+}
+
+nsPrinterEnumeratorWin::~nsPrinterEnumeratorWin()
+{
+ // Do not free printers here
+ // GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+}
+
+NS_IMPL_ISUPPORTS(nsPrinterEnumeratorWin, nsIPrinterEnumerator)
+
+//----------------------------------------------------------------------------------
+// Return the Default Printer name
+NS_IMETHODIMP
+nsPrinterEnumeratorWin::GetDefaultPrinterName(char16_t * *aDefaultPrinterName)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPrinterName);
+
+ *aDefaultPrinterName = GetDefaultPrinterNameFromGlobalPrinters(); // helper
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrinterEnumeratorWin::InitPrintSettingsFromPrinter(const char16_t *aPrinterName, nsIPrintSettings *aPrintSettings)
+{
+ NS_ENSURE_ARG_POINTER(aPrinterName);
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ if (!*aPrinterName) {
+ 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 (NS_FAILED(GlobalPrinters::GetInstance()->EnumeratePrinterList())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AutoFreeGlobalPrinters autoFreeGlobalPrinters;
+
+ // 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.
+ char16ptr_t printerName = aPrinterName;
+ 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;
+}
+
+
+//----------------------------------------------------------------------------------
+// Enumerate all the Printers from the global array and pass their
+// names back (usually to script)
+NS_IMETHODIMP
+nsPrinterEnumeratorWin::GetPrinterNameList(nsIStringEnumerator **aPrinterNameList)
+{
+ NS_ENSURE_ARG_POINTER(aPrinterNameList);
+ *aPrinterNameList = nullptr;
+
+ nsresult rv = GlobalPrinters::GetInstance()->EnumeratePrinterList();
+ if (NS_FAILED(rv)) {
+ PR_PL(("***** nsDeviceContextSpecWin::GetPrinterNameList - Couldn't enumerate printers!\n"));
+ return rv;
+ }
+
+ uint32_t numPrinters = GlobalPrinters::GetInstance()->GetNumPrinters();
+ nsTArray<nsString> *printers = new nsTArray<nsString>(numPrinters);
+ if (!printers)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsString* names = printers->AppendElements(numPrinters);
+ for (uint32_t printerInx = 0; printerInx < numPrinters; ++printerInx) {
+ LPWSTR name = GlobalPrinters::GetInstance()->GetItemFromList(printerInx);
+ names[printerInx].Assign(name);
+ }
+
+ return NS_NewAdoptingStringEnumerator(aPrinterNameList, printers);
+}
+
+//----------------------------------------------------------------------------------
+//-- Global Printers
+//----------------------------------------------------------------------------------
+
+//----------------------------------------------------------------------------------
+// THe array hold the name and port for each printer
+void
+GlobalPrinters::ReallocatePrinters()
+{
+ if (PrintersAreAllocated()) {
+ FreeGlobalPrinters();
+ }
+ mPrinters = new nsTArray<LPWSTR>();
+ NS_ASSERTION(mPrinters, "Printers Array is NULL!");
+}
+
+//----------------------------------------------------------------------------------
+void
+GlobalPrinters::FreeGlobalPrinters()
+{
+ if (mPrinters != nullptr) {
+ for (uint32_t i=0;i<mPrinters->Length();i++) {
+ free(mPrinters->ElementAt(i));
+ }
+ delete mPrinters;
+ mPrinters = nullptr;
+ }
+}
+
+//----------------------------------------------------------------------------------
+nsresult
+GlobalPrinters::EnumerateNativePrinters()
+{
+ nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE;
+ PR_PL(("-----------------------\n"));
+ PR_PL(("EnumerateNativePrinters\n"));
+
+ WCHAR szDefaultPrinterName[1024];
+ DWORD status = GetProfileStringW(L"devices", 0, L",",
+ szDefaultPrinterName,
+ ArrayLength(szDefaultPrinterName));
+ if (status > 0) {
+ DWORD count = 0;
+ LPWSTR sPtr = szDefaultPrinterName;
+ LPWSTR ePtr = szDefaultPrinterName + status;
+ LPWSTR prvPtr = sPtr;
+ while (sPtr < ePtr) {
+ if (*sPtr == 0) {
+ LPWSTR name = wcsdup(prvPtr);
+ mPrinters->AppendElement(name);
+ PR_PL(("Printer Name: %s\n", prvPtr));
+ prvPtr = sPtr+1;
+ count++;
+ }
+ sPtr++;
+ }
+ rv = NS_OK;
+ }
+ PR_PL(("-----------------------\n"));
+ return rv;
+}
+
+//------------------------------------------------------------------
+// Uses the GetProfileString to get the default printer from the registry
+void
+GlobalPrinters::GetDefaultPrinterName(nsString& aDefaultPrinterName)
+{
+ aDefaultPrinterName.Truncate();
+ WCHAR szDefaultPrinterName[1024];
+ DWORD status = GetProfileStringW(L"windows", L"device", 0,
+ szDefaultPrinterName,
+ ArrayLength(szDefaultPrinterName));
+ if (status > 0) {
+ WCHAR comma = ',';
+ LPWSTR sPtr = szDefaultPrinterName;
+ while (*sPtr != comma && *sPtr != 0)
+ sPtr++;
+ if (*sPtr == comma) {
+ *sPtr = 0;
+ }
+ aDefaultPrinterName = szDefaultPrinterName;
+ } else {
+ aDefaultPrinterName = EmptyString();
+ }
+
+ PR_PL(("DEFAULT PRINTER [%s]\n", aDefaultPrinterName.get()));
+}
+
+//----------------------------------------------------------------------------------
+// This goes and gets the list of available printers and puts
+// the default printer at the beginning of the list
+nsresult
+GlobalPrinters::EnumeratePrinterList()
+{
+ // reallocate and get a new list each time it is asked for
+ // this deletes the list and re-allocates them
+ ReallocatePrinters();
+
+ // any of these could only fail with an OUT_MEMORY_ERROR
+ // PRINTER_ENUM_LOCAL should get the network printers on Win95
+ nsresult rv = EnumerateNativePrinters();
+ if (NS_FAILED(rv)) return rv;
+
+ // get the name of the default printer
+ nsAutoString defPrinterName;
+ GetDefaultPrinterName(defPrinterName);
+
+ // put the default printer at the beginning of list
+ if (!defPrinterName.IsEmpty()) {
+ for (uint32_t i=0;i<mPrinters->Length();i++) {
+ LPWSTR name = mPrinters->ElementAt(i);
+ if (defPrinterName.Equals(name)) {
+ if (i > 0) {
+ LPWSTR ptr = mPrinters->ElementAt(0);
+ mPrinters->ElementAt(0) = name;
+ mPrinters->ElementAt(i) = ptr;
+ }
+ break;
+ }
+ }
+ }
+
+ // make sure we at least tried to get the printers
+ if (!PrintersAreAllocated()) {
+ PR_PL(("***** nsDeviceContextSpecWin::EnumeratePrinterList - Printers aren`t allocated\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
diff --git a/widget/windows/nsDeviceContextSpecWin.h b/widget/windows/nsDeviceContextSpecWin.h
new file mode 100644
index 000000000..e3706a291
--- /dev/null
+++ b/widget/windows/nsDeviceContextSpecWin.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 nsDeviceContextSpecWin_h___
+#define nsDeviceContextSpecWin_h___
+
+#include "nsCOMPtr.h"
+#include "nsIDeviceContextSpec.h"
+#include "nsIPrinterEnumerator.h"
+#include "nsIPrintSettings.h"
+#include "nsISupportsPrimitives.h"
+#include <windows.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+
+class nsIWidget;
+
+class nsDeviceContextSpecWin : public nsIDeviceContextSpec
+{
+public:
+ nsDeviceContextSpecWin();
+
+ NS_DECL_ISUPPORTS
+
+ virtual 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;
+
+ void GetDriverName(wchar_t *&aDriverName) const { aDriverName = mDriverName; }
+ void GetDeviceName(wchar_t *&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(char16ptr_t aName, nsIPrintSettings* aPS = nullptr);
+
+protected:
+
+ void SetDeviceName(char16ptr_t aDeviceName);
+ void SetDriverName(char16ptr_t aDriverName);
+ void SetDevMode(LPDEVMODEW aDevMode);
+
+ virtual ~nsDeviceContextSpecWin();
+
+ wchar_t* mDriverName;
+ wchar_t* mDeviceName;
+ LPDEVMODEW mDevMode;
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+ int16_t mOutputFormat = nsIPrintSettings::kOutputFormatNative;
+};
+
+
+//-------------------------------------------------------------------------
+// Printer Enumerator
+//-------------------------------------------------------------------------
+class nsPrinterEnumeratorWin final : public nsIPrinterEnumerator
+{
+ ~nsPrinterEnumeratorWin();
+
+public:
+ nsPrinterEnumeratorWin();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTERENUMERATOR
+};
+
+#endif
diff --git a/widget/windows/nsDragService.cpp b/widget/windows/nsDragService.cpp
new file mode 100644
index 000000000..0040f550f
--- /dev/null
+++ b/widget/windows/nsDragService.cpp
@@ -0,0 +1,641 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIDocument.h"
+#include "nsDataObjCollection.h"
+
+#include "nsArrayUtils.h"
+#include "nsString.h"
+#include "nsEscape.h"
+#include "nsIScreenManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIURL.h"
+#include "nsCWebBrowserPersist.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsUnicharUtils.h"
+#include "gfxContext.h"
+#include "nsRect.h"
+#include "nsMathUtils.h"
+#include "WinUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Tools.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+//-------------------------------------------------------------------------
+//
+// DragService constructor
+//
+//-------------------------------------------------------------------------
+nsDragService::nsDragService()
+ : mDataObject(nullptr), mSentLocalDropEvent(false)
+{
+}
+
+//-------------------------------------------------------------------------
+//
+// DragService destructor
+//
+//-------------------------------------------------------------------------
+nsDragService::~nsDragService()
+{
+ NS_IF_RELEASE(mDataObject);
+}
+
+bool
+nsDragService::CreateDragImage(nsIDOMNode *aDOMNode,
+ nsIScriptableRegion *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,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType)
+{
+ // Try and get source URI of the items that are being dragged
+ nsIURI *uri = nullptr;
+
+ nsCOMPtr<nsIDocument> doc(do_QueryInterface(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) {
+ // set the requestingPrincipal on the transferable
+ nsCOMPtr<nsINode> node = do_QueryInterface(mSourceNode);
+ trans->SetRequestingPrincipal(node->NodePrincipal());
+ trans->SetContentPolicyType(mContentPolicyType);
+ 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) {
+ // set the requestingPrincipal on the transferable
+ nsCOMPtr<nsINode> node = do_QueryInterface(mSourceNode);
+ trans->SetRequestingPrincipal(node->NodePrincipal());
+ trans->SetContentPolicyType(mContentPolicyType);
+ 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;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+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<IAsyncOperation> pAsyncOp;
+ // Offer to do an async drag
+ if (SUCCEEDED(aDataObj->QueryInterface(IID_IAsyncOperation,
+ getter_AddRefs(pAsyncOp)))) {
+ pAsyncOp->SetAsyncMode(VARIANT_TRUE);
+ } else {
+ NS_NOTREACHED("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 nsIDOMDataTransfer 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)));
+ EndDragSession(true);
+
+ 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)
+{
+ // 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);
+ NS_IF_RELEASE(mDataObject);
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsDragService.h b/widget/windows/nsDragService.h
new file mode 100644
index 000000000..7a2d29709
--- /dev/null
+++ b/widget/windows/nsDragService.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 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
+ virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType);
+
+ // nsIDragSession
+ NS_IMETHOD GetData(nsITransferable * aTransferable, uint32_t anItem);
+ NS_IMETHOD GetNumDropItems(uint32_t * aNumItems);
+ NS_IMETHOD IsDataFlavorSupported(const char *aDataFlavor, bool *_retval);
+ NS_IMETHOD EndDragSession(bool aDoneDrag);
+
+ // native impl.
+ NS_IMETHOD SetIDataObject(IDataObject * aDataObj);
+ NS_IMETHOD StartInvokingDragSession(IDataObject * aDataObj,
+ uint32_t aActionType);
+
+ // A drop occurred within the application vs. outside of it.
+ void SetDroppedLocal();
+
+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(nsIDOMNode *aDOMNode,
+ nsIScriptableRegion *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 000000000..53857cf5e
--- /dev/null
+++ b/widget/windows/nsFilePicker.cpp
@@ -0,0 +1,1383 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/mscom/EnsureMTA.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsReadableUtils.h"
+#include "nsNetUtil.h"
+#include "nsWindow.h"
+#include "nsILoadContext.h"
+#include "nsIServiceManager.h"
+#include "nsIURL.h"
+#include "nsIStringBundle.h"
+#include "nsEnumeratorUtils.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsToolkit.h"
+#include "WinUtils.h"
+#include "nsPIDOMWindow.h"
+#include "GeckoProfiler.h"
+
+using mozilla::IsVistaOrLater;
+using mozilla::IsWin8OrLater;
+using mozilla::MakeUnique;
+using mozilla::mscom::EnsureMTA;
+using mozilla::UniquePtr;
+using namespace mozilla::widget;
+
+char16_t *nsFilePicker::mLastUsedUnicodeDirectory;
+char nsFilePicker::mLastUsedDirectory[MAX_PATH+1] = { 0 };
+
+static const wchar_t kDialogPtrProp[] = L"DialogPtrProperty";
+static const DWORD kDialogTimerID = 9999;
+static const unsigned long kDialogTimerTimeout = 300;
+
+#define MAX_EXTENSION_LENGTH 10
+#define FILE_BUFFER_SIZE 4096
+
+typedef DWORD FILEOPENDIALOGOPTIONS;
+
+///////////////////////////////////////////////////////////////////////////////
+// Helper classes
+
+// Manages matching SuppressBlurEvents calls on the parent widget.
+class AutoSuppressEvents
+{
+public:
+ explicit AutoSuppressEvents(nsIWidget* aWidget) :
+ mWindow(static_cast<nsWindow *>(aWidget)) {
+ SuppressWidgetEvents(true);
+ }
+
+ ~AutoSuppressEvents() {
+ SuppressWidgetEvents(false);
+ }
+private:
+ void SuppressWidgetEvents(bool aFlag) {
+ if (mWindow) {
+ mWindow->SuppressBlurEvents(aFlag);
+ }
+ }
+ RefPtr<nsWindow> mWindow;
+};
+
+// Manages the current working path.
+class AutoRestoreWorkingPath
+{
+public:
+ AutoRestoreWorkingPath() {
+ DWORD bufferLength = GetCurrentDirectoryW(0, nullptr);
+ mWorkingPath = MakeUnique<wchar_t[]>(bufferLength);
+ if (GetCurrentDirectoryW(bufferLength, mWorkingPath.get()) == 0) {
+ mWorkingPath = nullptr;
+ }
+ }
+
+ ~AutoRestoreWorkingPath() {
+ if (HasWorkingPath()) {
+ ::SetCurrentDirectoryW(mWorkingPath.get());
+ }
+ }
+
+ inline bool HasWorkingPath() const {
+ return mWorkingPath != nullptr;
+ }
+private:
+ UniquePtr<wchar_t[]> mWorkingPath;
+};
+
+// 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;
+};
+
+// Manages a simple callback timer
+class AutoTimerCallbackCancel
+{
+public:
+ AutoTimerCallbackCancel(nsFilePicker* aTarget,
+ nsTimerCallbackFunc aCallbackFunc) {
+ Init(aTarget, aCallbackFunc);
+ }
+
+ ~AutoTimerCallbackCancel() {
+ if (mPickerCallbackTimer) {
+ mPickerCallbackTimer->Cancel();
+ }
+ }
+
+private:
+ void Init(nsFilePicker* aTarget,
+ nsTimerCallbackFunc aCallbackFunc) {
+ mPickerCallbackTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mPickerCallbackTimer) {
+ NS_WARNING("do_CreateInstance for timer failed??");
+ return;
+ }
+ mPickerCallbackTimer->InitWithFuncCallback(aCallbackFunc,
+ aTarget,
+ kDialogTimerTimeout,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+ nsCOMPtr<nsITimer> mPickerCallbackTimer;
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIFilePicker
+
+nsFilePicker::nsFilePicker() :
+ mSelectedType(1)
+ , mDlgWnd(nullptr)
+ , mFDECookie(0)
+{
+ CoInitialize(nullptr);
+}
+
+nsFilePicker::~nsFilePicker()
+{
+ if (mLastUsedUnicodeDirectory) {
+ free(mLastUsedUnicodeDirectory);
+ mLastUsedUnicodeDirectory = nullptr;
+ }
+ CoUninitialize();
+}
+
+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);
+}
+
+STDMETHODIMP nsFilePicker::QueryInterface(REFIID refiid, void** ppvResult)
+{
+ *ppvResult = nullptr;
+ if (IID_IUnknown == refiid ||
+ refiid == IID_IFileDialogEvents) {
+ *ppvResult = this;
+ }
+
+ if (nullptr != *ppvResult) {
+ ((LPUNKNOWN)*ppvResult)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+/*
+ * XP picker callbacks
+ */
+
+// Show - Display the file dialog
+int CALLBACK
+BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ if (uMsg == BFFM_INITIALIZED)
+ {
+ char16_t * filePath = (char16_t *) lpData;
+ if (filePath)
+ ::SendMessageW(hwnd, BFFM_SETSELECTIONW,
+ TRUE /* true because lpData is a path string */,
+ lpData);
+ }
+ return 0;
+}
+
+static void
+EnsureWindowVisible(HWND hwnd)
+{
+ // Obtain the monitor which has the largest area of intersection
+ // with the window, or nullptr if there is no intersection.
+ HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
+ if (!monitor) {
+ // The window is not visible, we should reposition it to the same place as its parent
+ HWND parentHwnd = GetParent(hwnd);
+ RECT parentRect;
+ GetWindowRect(parentHwnd, &parentRect);
+ SetWindowPos(hwnd, nullptr, parentRect.left, parentRect.top, 0, 0,
+ SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
+ }
+}
+
+// Callback hook which will ensure that the window is visible. Currently
+// only in use on os <= XP.
+UINT_PTR CALLBACK
+nsFilePicker::FilePickerHook(HWND hwnd,
+ UINT msg,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ switch(msg) {
+ case WM_NOTIFY:
+ {
+ LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam;
+ if (!lpofn || !lpofn->lpOFN) {
+ return 0;
+ }
+
+ if (CDN_INITDONE == lpofn->hdr.code) {
+ // The Window will be automatically moved to the last position after
+ // CDN_INITDONE. We post a message to ensure the window will be visible
+ // so it will be done after the automatic last position window move.
+ PostMessage(hwnd, MOZ_WM_ENSUREVISIBLE, 0, 0);
+ }
+ }
+ break;
+ case MOZ_WM_ENSUREVISIBLE:
+ EnsureWindowVisible(GetParent(hwnd));
+ break;
+ case WM_INITDIALOG:
+ {
+ OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam);
+ SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData);
+ nsFilePicker* picker = reinterpret_cast<nsFilePicker*>(pofn->lCustData);
+ if (picker) {
+ picker->SetDialogHandle(hwnd);
+ SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr);
+ }
+ }
+ break;
+ case WM_TIMER:
+ {
+ // Check to see if our parent has been torn down, if so, we close too.
+ if (wParam == kDialogTimerID) {
+ nsFilePicker* picker =
+ reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp));
+ if (picker && picker->ClosePickerIfNeeded(true)) {
+ KillTimer(hwnd, kDialogTimerID);
+ }
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+
+// Callback hook which will dynamically allocate a buffer large enough
+// for the file picker dialog. Currently only in use on os <= XP.
+UINT_PTR CALLBACK
+nsFilePicker::MultiFilePickerHook(HWND hwnd,
+ UINT msg,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ // Finds the child drop down of a File Picker dialog and sets the
+ // maximum amount of text it can hold when typed in manually.
+ // A wParam of 0 mean 0x7FFFFFFE characters.
+ HWND comboBox = FindWindowEx(GetParent(hwnd), nullptr,
+ L"ComboBoxEx32", nullptr );
+ if(comboBox)
+ SendMessage(comboBox, CB_LIMITTEXT, 0, 0);
+ // Store our nsFilePicker ptr for future use
+ OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam);
+ SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData);
+ nsFilePicker* picker =
+ reinterpret_cast<nsFilePicker*>(pofn->lCustData);
+ if (picker) {
+ picker->SetDialogHandle(hwnd);
+ SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr);
+ }
+ }
+ break;
+ case WM_NOTIFY:
+ {
+ LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam;
+ if (!lpofn || !lpofn->lpOFN) {
+ return 0;
+ }
+ // CDN_SELCHANGE is sent when the selection in the list box of the file
+ // selection dialog changes
+ if (lpofn->hdr.code == CDN_SELCHANGE) {
+ HWND parentHWND = GetParent(hwnd);
+
+ // Get the required size for the selected files buffer
+ UINT newBufLength = 0;
+ int requiredBufLength = CommDlg_OpenSave_GetSpecW(parentHWND,
+ nullptr, 0);
+ if(requiredBufLength >= 0)
+ newBufLength += requiredBufLength;
+ else
+ newBufLength += MAX_PATH;
+
+ // If the user selects multiple files, the buffer contains the
+ // current directory followed by the file names of the selected
+ // files. So make room for the directory path. If the user
+ // selects a single file, it is no harm to add extra space.
+ requiredBufLength = CommDlg_OpenSave_GetFolderPathW(parentHWND,
+ nullptr, 0);
+ if(requiredBufLength >= 0)
+ newBufLength += requiredBufLength;
+ else
+ newBufLength += MAX_PATH;
+
+ // Check if lpstrFile and nMaxFile are large enough
+ if (newBufLength > lpofn->lpOFN->nMaxFile) {
+ if (lpofn->lpOFN->lpstrFile)
+ delete[] lpofn->lpOFN->lpstrFile;
+
+ // We allocate FILE_BUFFER_SIZE more bytes than is needed so that
+ // if the user selects a file and holds down shift and down to
+ // select additional items, we will not continuously reallocate
+ newBufLength += FILE_BUFFER_SIZE;
+
+ wchar_t* filesBuffer = new wchar_t[newBufLength];
+ ZeroMemory(filesBuffer, newBufLength * sizeof(wchar_t));
+
+ lpofn->lpOFN->lpstrFile = filesBuffer;
+ lpofn->lpOFN->nMaxFile = newBufLength;
+ }
+ }
+ }
+ break;
+ case WM_TIMER:
+ {
+ // Check to see if our parent has been torn down, if so, we close too.
+ if (wParam == kDialogTimerID) {
+ nsFilePicker* picker =
+ reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp));
+ if (picker && picker->ClosePickerIfNeeded(true)) {
+ KillTimer(hwnd, kDialogTimerID);
+ }
+ }
+ }
+ break;
+ }
+
+ return FilePickerHook(hwnd, msg, wParam, lParam);
+}
+
+/*
+ * Vista+ callbacks
+ */
+
+HRESULT
+nsFilePicker::OnFileOk(IFileDialog *pfd)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnFolderChanging(IFileDialog *pfd,
+ IShellItem *psiFolder)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnFolderChange(IFileDialog *pfd)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnSelectionChange(IFileDialog *pfd)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnShareViolation(IFileDialog *pfd,
+ IShellItem *psi,
+ FDE_SHAREVIOLATION_RESPONSE *pResponse)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnTypeChange(IFileDialog *pfd)
+{
+ // Failures here result in errors due to security concerns.
+ RefPtr<IOleWindow> win;
+ pfd->QueryInterface(IID_IOleWindow, getter_AddRefs(win));
+ if (!win) {
+ NS_ERROR("Could not retrieve the IOleWindow interface for IFileDialog.");
+ return S_OK;
+ }
+ HWND hwnd = nullptr;
+ win->GetWindow(&hwnd);
+ if (!hwnd) {
+ NS_ERROR("Could not retrieve the HWND for IFileDialog.");
+ return S_OK;
+ }
+
+ SetDialogHandle(hwnd);
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnOverwrite(IFileDialog *pfd,
+ IShellItem *psi,
+ FDE_OVERWRITE_RESPONSE *pResponse)
+{
+ return S_OK;
+}
+
+/*
+ * Close on parent close logic
+ */
+
+bool
+nsFilePicker::ClosePickerIfNeeded(bool aIsXPDialog)
+{
+ if (!mParentWidget || !mDlgWnd)
+ return false;
+
+ nsWindow *win = static_cast<nsWindow *>(mParentWidget.get());
+ // Note, the xp callbacks hand us an inner window, so we have to step up
+ // one to get the actual dialog.
+ HWND dlgWnd;
+ if (aIsXPDialog)
+ dlgWnd = GetParent(mDlgWnd);
+ else
+ dlgWnd = mDlgWnd;
+ if (IsWindow(dlgWnd) && IsWindowVisible(dlgWnd) && win->DestroyCalled()) {
+ wchar_t className[64];
+ // Make sure we have the right window
+ if (GetClassNameW(dlgWnd, className, mozilla::ArrayLength(className)) &&
+ !wcscmp(className, L"#32770") &&
+ DestroyWindow(dlgWnd)) {
+ mDlgWnd = nullptr;
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+nsFilePicker::PickerCallbackTimerFunc(nsITimer *aTimer, void *aCtx)
+{
+ nsFilePicker* picker = (nsFilePicker*)aCtx;
+ if (picker->ClosePickerIfNeeded(false)) {
+ aTimer->Cancel();
+ }
+}
+
+void
+nsFilePicker::SetDialogHandle(HWND aWnd)
+{
+ if (!aWnd || mDlgWnd)
+ return;
+ mDlgWnd = aWnd;
+}
+
+/*
+ * Folder picker invocation
+ */
+
+// Open the older XP style folder picker dialog. We end up in this call
+// on XP systems or when platform is built without the longhorn SDK.
+bool
+nsFilePicker::ShowXPFolderPicker(const nsString& aInitialDir)
+{
+ bool result = false;
+
+ auto dirBuffer = MakeUnique<wchar_t[]>(FILE_BUFFER_SIZE);
+ wcsncpy(dirBuffer.get(), aInitialDir.get(), FILE_BUFFER_SIZE);
+ dirBuffer[FILE_BUFFER_SIZE-1] = '\0';
+
+ AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ?
+ mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
+
+ BROWSEINFOW browserInfo = {0};
+ browserInfo.pidlRoot = nullptr;
+ browserInfo.pszDisplayName = dirBuffer.get();
+ browserInfo.lpszTitle = mTitle.get();
+ browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS;
+ browserInfo.hwndOwner = adtw.get();
+ browserInfo.iImage = 0;
+ browserInfo.lParam = reinterpret_cast<LPARAM>(this);
+
+ if (!aInitialDir.IsEmpty()) {
+ // the dialog is modal so that |initialDir.get()| will be valid in
+ // BrowserCallbackProc. Thus, we don't need to clone it.
+ browserInfo.lParam = (LPARAM) aInitialDir.get();
+ browserInfo.lpfn = &BrowseCallbackProc;
+ } else {
+ browserInfo.lParam = 0;
+ browserInfo.lpfn = nullptr;
+ }
+
+ LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo);
+ if (list) {
+ result = ::SHGetPathFromIDListW(list, dirBuffer.get());
+ if (result)
+ mUnicodeFile.Assign(static_cast<const wchar_t*>(dirBuffer.get()));
+ // free PIDL
+ CoTaskMemFree(list);
+ }
+
+ return result;
+}
+
+/*
+ * Show a folder picker post Windows XP
+ *
+ * @param aInitialDir The initial directory, the last used directory will be
+ * used if left blank.
+ * @param aWasInitError Out parameter will hold true if there was an error
+ * before the folder picker is shown.
+ * @return true if a file was selected successfully.
+*/
+bool
+nsFilePicker::ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError)
+{
+ 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,
+ IID_IFileOpenDialog,
+ getter_AddRefs(dialog)))) {
+ aWasInitError = true;
+ return false;
+ }
+ aWasInitError = false;
+
+ // hook up event callbacks
+ dialog->Advise(this, &mFDECookie);
+
+ // options
+ FILEOPENDIALOGOPTIONS fos = FOS_PICKFOLDERS;
+ dialog->SetOptions(fos);
+
+ // initial strings
+ dialog->SetTitle(mTitle.get());
+
+ if (!mOkButtonLabel.IsEmpty()) {
+ dialog->SetOkButtonLabel(mOkButtonLabel.get());
+ }
+
+ if (!aInitialDir.IsEmpty()) {
+ RefPtr<IShellItem> folder;
+ if (SUCCEEDED(
+ WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr,
+ IID_IShellItem,
+ getter_AddRefs(folder)))) {
+ dialog->SetFolder(folder);
+ }
+ }
+
+ AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ?
+ mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
+
+ // display
+ RefPtr<IShellItem> item;
+ if (FAILED(dialog->Show(adtw.get())) ||
+ FAILED(dialog->GetResult(getter_AddRefs(item))) ||
+ !item) {
+ dialog->Unadvise(mFDECookie);
+ return false;
+ }
+ dialog->Unadvise(mFDECookie);
+
+ // results
+
+ // If the user chose a Win7 Library, resolve to the library's
+ // default save folder.
+ RefPtr<IShellItem> folderPath;
+ RefPtr<IShellLibrary> shellLib;
+ CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC,
+ IID_IShellLibrary, getter_AddRefs(shellLib));
+ 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
+ */
+
+/* static */ bool
+nsFilePicker::GetFileNameWrapper(OPENFILENAMEW* ofn, PickerType aType)
+{
+ MOZ_SEH_TRY {
+ if (aType == PICKER_TYPE_OPEN)
+ return ::GetOpenFileNameW(ofn);
+ else if (aType == PICKER_TYPE_SAVE)
+ return ::GetSaveFileNameW(ofn);
+ } MOZ_SEH_EXCEPT(true) {
+ NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!");
+ }
+ return false;
+}
+
+bool
+nsFilePicker::FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType)
+{
+ if (!ofn)
+ return false;
+ AutoWidgetPickerState awps(mParentWidget);
+ return GetFileNameWrapper(ofn, aType);
+}
+
+bool
+nsFilePicker::ShowXPFilePicker(const nsString& aInitialDir)
+{
+ OPENFILENAMEW ofn = {0};
+ ofn.lStructSize = sizeof(ofn);
+ nsString filterBuffer = mFilterList;
+
+ auto fileBuffer = MakeUnique<wchar_t[]>(FILE_BUFFER_SIZE);
+ wcsncpy(fileBuffer.get(), mDefaultFilePath.get(), FILE_BUFFER_SIZE);
+ fileBuffer[FILE_BUFFER_SIZE-1] = '\0'; // null terminate in case copy truncated
+
+ if (!aInitialDir.IsEmpty()) {
+ ofn.lpstrInitialDir = aInitialDir.get();
+ }
+
+ AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ?
+ mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
+
+ ofn.lpstrTitle = (LPCWSTR)mTitle.get();
+ ofn.lpstrFilter = (LPCWSTR)filterBuffer.get();
+ ofn.nFilterIndex = mSelectedType;
+ ofn.lpstrFile = fileBuffer.get();
+ ofn.nMaxFile = FILE_BUFFER_SIZE;
+ ofn.hwndOwner = adtw.get();
+ ofn.lCustData = reinterpret_cast<LPARAM>(this);
+ ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT |
+ OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING |
+ OFN_EXPLORER;
+
+ // Windows Vista and up won't allow you to use the new looking dialogs with
+ // a hook procedure. The hook procedure fixes a problem on XP dialogs for
+ // file picker visibility. Vista and up automatically ensures the file
+ // picker is always visible.
+ if (!IsVistaOrLater()) {
+ ofn.lpfnHook = FilePickerHook;
+ ofn.Flags |= OFN_ENABLEHOOK;
+ }
+
+ // Handle add to recent docs settings
+ if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
+ ofn.Flags |= OFN_DONTADDTORECENT;
+ }
+
+ NS_NAMED_LITERAL_STRING(htmExt, "html");
+
+ if (!mDefaultExtension.IsEmpty()) {
+ ofn.lpstrDefExt = mDefaultExtension.get();
+ } else if (IsDefaultPathHtml()) {
+ // Get file extension from suggested filename to detect if we are
+ // saving an html file.
+ // This is supposed to append ".htm" if user doesn't supply an
+ // extension but the behavior is sort of weird:
+ // - Often appends ".html" even if you have an extension
+ // - It obeys your extension if you put quotes around name
+ ofn.lpstrDefExt = htmExt.get();
+ }
+
+ // When possible, instead of using OFN_NOCHANGEDIR to ensure the current
+ // working directory will not change from this call, we will retrieve the
+ // current working directory before the call and restore it after the
+ // call. This flag causes problems on Windows XP for paths that are
+ // selected like C:test.txt where the user is currently at C:\somepath
+ // In which case expected result should be C:\somepath\test.txt
+ AutoRestoreWorkingPath restoreWorkingPath;
+ // If we can't get the current working directory, the best case is to
+ // use the OFN_NOCHANGEDIR flag
+ if (!restoreWorkingPath.HasWorkingPath()) {
+ ofn.Flags |= OFN_NOCHANGEDIR;
+ }
+
+ bool result = false;
+
+ switch(mMode) {
+ case modeOpen:
+ // FILE MUST EXIST!
+ ofn.Flags |= OFN_FILEMUSTEXIST;
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN);
+ break;
+
+ case modeOpenMultiple:
+ ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT;
+
+ // The hook set here ensures that the buffer returned will always be
+ // large enough to hold all selected files. The hook may modify the
+ // value of ofn.lpstrFile and deallocate the old buffer that it pointed
+ // to (fileBuffer). The hook assumes that the passed in value is heap
+ // allocated and that the returned value should be freed by the caller.
+ // If the hook changes the buffer, it will deallocate the old buffer.
+ // This fix would be nice to have in Vista and up, but it would force
+ // the file picker to use the old style dialogs because hooks are not
+ // allowed in the new file picker UI. We need to eventually move to
+ // the new Common File Dialogs for Vista and up.
+ if (!IsVistaOrLater()) {
+ ofn.lpfnHook = MultiFilePickerHook;
+ fileBuffer.release();
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN);
+ fileBuffer.reset(ofn.lpstrFile);
+ } else {
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN);
+ }
+ break;
+
+ case modeSave:
+ {
+ ofn.Flags |= OFN_NOREADONLYRETURN;
+
+ // Don't follow shortcuts when saving a shortcut, this can be used
+ // to trick users (bug 271732)
+ if (IsDefaultPathLink())
+ ofn.Flags |= OFN_NODEREFERENCELINKS;
+
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE);
+ if (!result) {
+ // Error, find out what kind.
+ if (GetLastError() == ERROR_INVALID_PARAMETER ||
+ CommDlgExtendedError() == FNERR_INVALIDFILENAME) {
+ // Probably the default file name is too long or contains illegal
+ // characters. Try again, without a starting file name.
+ ofn.lpstrFile[0] = L'\0';
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE);
+ }
+ }
+ }
+ break;
+
+ default:
+ NS_NOTREACHED("unsupported file picker mode");
+ return false;
+ }
+
+ if (!result)
+ return false;
+
+ // Remember what filter type the user selected
+ mSelectedType = (int16_t)ofn.nFilterIndex;
+
+ // Single file selection, we're done
+ if (mMode != modeOpenMultiple) {
+ GetQualifiedPath(fileBuffer.get(), mUnicodeFile);
+ return true;
+ }
+
+ // Set user-selected location of file or directory. From msdn's "Open and
+ // Save As Dialog Boxes" section:
+ // If you specify OFN_EXPLORER, the directory and file name strings are '\0'
+ // separated, with an extra '\0' character after the last file name. This
+ // format enables the Explorer-style dialog boxes to return long file names
+ // that include spaces.
+ wchar_t *current = fileBuffer.get();
+
+ nsAutoString dirName(current);
+ // Sometimes dirName contains a trailing slash and sometimes it doesn't:
+ if (current[dirName.Length() - 1] != '\\')
+ dirName.Append((char16_t)'\\');
+
+ while (current && *current && *(current + wcslen(current) + 1)) {
+ current = current + wcslen(current) + 1;
+
+ nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
+ NS_ENSURE_TRUE(file, false);
+
+ // Only prepend the directory if the path specified is a relative path
+ nsAutoString path;
+ if (PathIsRelativeW(current)) {
+ path = dirName + nsDependentString(current);
+ } else {
+ path = current;
+ }
+
+ nsAutoString canonicalizedPath;
+ GetQualifiedPath(path.get(), canonicalizedPath);
+ if (NS_FAILED(file->InitWithPath(canonicalizedPath)) ||
+ !mFiles.AppendObject(file))
+ return false;
+ }
+
+ // Handle the case where the user selected just one file. From msdn: If you
+ // specify OFN_ALLOWMULTISELECT and the user selects only one file the
+ // lpstrFile string does not have a separator between the path and file name.
+ if (current && *current && (current == fileBuffer.get())) {
+ nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
+ NS_ENSURE_TRUE(file, false);
+
+ nsAutoString canonicalizedPath;
+ GetQualifiedPath(current, canonicalizedPath);
+ if (NS_FAILED(file->InitWithPath(canonicalizedPath)) ||
+ !mFiles.AppendObject(file))
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Show a file picker post Windows XP
+ *
+ * @param aInitialDir The initial directory, the last used directory will be
+ * used if left blank.
+ * @param aWasInitError Out parameter will hold true if there was an error
+ * before the file picker is shown.
+ * @return true if a file was selected successfully.
+*/
+bool
+nsFilePicker::ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::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,
+ IID_IFileOpenDialog,
+ getter_AddRefs(dialog)))) {
+ aWasInitError = true;
+ return false;
+ }
+ } else {
+ if (FAILED(CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC,
+ IID_IFileSaveDialog,
+ getter_AddRefs(dialog)))) {
+ aWasInitError = true;
+ return false;
+ }
+ }
+ aWasInitError = false;
+
+ // hook up event callbacks
+ dialog->Advise(this, &mFDECookie);
+
+ // options
+
+ FILEOPENDIALOGOPTIONS fos = 0;
+ fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT |
+ FOS_FORCEFILESYSTEM;
+
+ // Handle add to recent docs settings
+ if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
+ fos |= FOS_DONTADDTORECENT;
+ }
+
+ // Msdn claims FOS_NOCHANGEDIR is not needed. We'll add this
+ // just in case.
+ AutoRestoreWorkingPath arw;
+
+ // 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;
+ }
+
+ dialog->SetOptions(fos);
+
+ // initial strings
+
+ // title
+ dialog->SetTitle(mTitle.get());
+
+ // default filename
+ if (!mDefaultFilename.IsEmpty()) {
+ dialog->SetFileName(mDefaultFilename.get());
+ }
+
+ NS_NAMED_LITERAL_STRING(htmExt, "html");
+
+ // default extension to append to new files
+ if (!mDefaultExtension.IsEmpty()) {
+ dialog->SetDefaultExtension(mDefaultExtension.get());
+ } else if (IsDefaultPathHtml()) {
+ dialog->SetDefaultExtension(htmExt.get());
+ }
+
+ // initial location
+ if (!aInitialDir.IsEmpty()) {
+ RefPtr<IShellItem> folder;
+ if (SUCCEEDED(
+ WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr,
+ IID_IShellItem,
+ getter_AddRefs(folder)))) {
+ dialog->SetFolder(folder);
+ }
+ }
+
+ // filter types and the default index
+ if (!mComFilterList.IsEmpty()) {
+ dialog->SetFileTypes(mComFilterList.Length(), mComFilterList.get());
+ dialog->SetFileTypeIndex(mSelectedType);
+ }
+
+ // display
+
+ {
+ AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ?
+ mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
+ AutoTimerCallbackCancel atcc(this, PickerCallbackTimerFunc);
+ AutoWidgetPickerState awps(mParentWidget);
+
+ if (FAILED(dialog->Show(adtw.get()))) {
+ dialog->Unadvise(mFDECookie);
+ return false;
+ }
+ dialog->Unadvise(mFDECookie);
+ }
+
+ // 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 = do_CreateInstance("@mozilla.org/file/local;1");
+ if (file && NS_SUCCEEDED(file->InitWithPath(str)))
+ mFiles.AppendObject(file);
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIFilePicker impl.
+
+NS_IMETHODIMP
+nsFilePicker::ShowW(int16_t *aReturnVal)
+{
+ NS_ENSURE_ARG_POINTER(aReturnVal);
+
+ *aReturnVal = returnCancel;
+
+ AutoSuppressEvents supress(mParentWidget);
+
+ 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 = mLastUsedUnicodeDirectory;
+ }
+
+ // 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;
+
+ // Launch the XP file/folder picker on XP and as a fallback on Vista+.
+ // The CoCreateInstance call to CLSID_FileOpenDialog fails with "(0x80040111)
+ // ClassFactory cannot supply requested class" when the checkbox for
+ // Disable Visual Themes is on in the compatability tab within the shortcut
+ // properties.
+ bool result = false, wasInitError = true;
+ if (mMode == modeGetFolder) {
+ if (IsVistaOrLater())
+ result = ShowFolderPicker(initialDir, wasInitError);
+ if (!result && wasInitError)
+ result = ShowXPFolderPicker(initialDir);
+ } else {
+ if (IsVistaOrLater())
+ result = ShowFilePicker(initialDir, wasInitError);
+ if (!result && wasInitError)
+ result = ShowXPFilePicker(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(do_CreateInstance("@mozilla.org/file/local;1"));
+ bool flag = false;
+ if (file && NS_SUCCEEDED(file->InitWithPath(mUnicodeFile)) &&
+ NS_SUCCEEDED(file->Exists(&flag)) && flag) {
+ retValue = returnReplace;
+ }
+ }
+
+ *aReturnVal = retValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+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(do_CreateInstance("@mozilla.org/file/local;1"));
+
+ NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
+
+ file->InitWithPath(mUnicodeFile);
+
+ NS_ADDREF(*aFile = file);
+
+ 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);
+}
+
+// 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);
+}
+
+void
+nsFilePicker::GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath)
+{
+ // Prefer a qualified path over a non qualified path.
+ // Things like c:file.txt would be accepted in Win XP but would later
+ // fail to open from the download manager.
+ wchar_t qualifiedFileBuffer[MAX_PATH];
+ if (PathSearchAndQualifyW(aInPath, qualifiedFileBuffer, MAX_PATH)) {
+ aOutPath.Assign(qualifiedFileBuffer);
+ } else {
+ aOutPath.Assign(aInPath);
+ }
+}
+
+void
+nsFilePicker::AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter)
+{
+ mFilterList.Append(aTitle);
+ mFilterList.Append(char16_t('\0'));
+
+ if (aFilter.EqualsLiteral("..apps"))
+ mFilterList.AppendLiteral("*.exe;*.com");
+ else
+ {
+ nsAutoString filter(aFilter);
+ filter.StripWhitespace();
+ if (filter.EqualsLiteral("*"))
+ filter.AppendLiteral(".*");
+ mFilterList.Append(filter);
+ }
+
+ mFilterList.Append(char16_t('\0'));
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
+{
+ if (IsVistaOrLater()) {
+ mComFilterList.Append(aTitle, aFilter);
+ } else {
+ AppendXPFilter(aTitle, aFilter);
+ }
+ return NS_OK;
+}
+
+void
+nsFilePicker::RememberLastUsedDirectory()
+{
+ nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
+ if (!file || NS_FAILED(file->InitWithPath(mUnicodeFile))) {
+ NS_WARNING("RememberLastUsedDirectory failed to init file path.");
+ return;
+ }
+
+ nsCOMPtr<nsIFile> dir;
+ nsAutoString newDir;
+ if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) ||
+ !(mDisplayDirectory = do_QueryInterface(dir)) ||
+ NS_FAILED(mDisplayDirectory->GetPath(newDir)) ||
+ newDir.IsEmpty()) {
+ NS_WARNING("RememberLastUsedDirectory failed to get parent directory.");
+ return;
+ }
+
+ if (mLastUsedUnicodeDirectory) {
+ free(mLastUsedUnicodeDirectory);
+ mLastUsedUnicodeDirectory = nullptr;
+ }
+ mLastUsedUnicodeDirectory = 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, NS_LITERAL_CSTRING(".lnk")) ||
+ StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) ||
+ StringEndsWith(ext, NS_LITERAL_CSTRING(".url")))
+ 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 000000000..90d8c15bc
--- /dev/null
+++ b/widget/windows/nsFilePicker.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 nsFilePicker_h__
+#define nsFilePicker_h__
+
+#include <windows.h>
+
+// For Vista IFileDialog interfaces which aren't exposed
+// unless _WIN32_WINNT >= _WIN32_WINNT_LONGHORN.
+#if _WIN32_WINNT < _WIN32_WINNT_LONGHORN
+#define _WIN32_WINNT_bak _WIN32_WINNT
+#undef _WIN32_WINNT
+#define _WIN32_WINNT _WIN32_WINNT_LONGHORN
+#define _WIN32_IE_bak _WIN32_IE
+#undef _WIN32_IE
+#define _WIN32_IE _WIN32_IE_IE70
+#endif
+
+#include "nsIFile.h"
+#include "nsITimer.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);
+ NS_IMETHOD SetDefaultString(const nsAString& aDefaultString);
+ NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension);
+ NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension);
+
+protected:
+ nsString mDefaultFilePath;
+ nsString mDefaultFilename;
+ nsString mDefaultExtension;
+};
+
+/**
+ * Native Windows FileSelector wrapper
+ */
+
+class nsFilePicker :
+ public IFileDialogEvents,
+ public nsBaseWinFilePicker
+{
+ virtual ~nsFilePicker();
+public:
+ nsFilePicker();
+
+ NS_IMETHOD Init(mozIDOMWindowProxy *aParent, const nsAString& aTitle, int16_t aMode);
+
+ NS_DECL_ISUPPORTS
+
+ // IUnknown's QueryInterface
+ STDMETHODIMP QueryInterface(REFIID refiid, void** ppvResult);
+
+ // nsIFilePicker (less what's in nsBaseFilePicker and nsBaseWinFilePicker)
+ NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex);
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex);
+ NS_IMETHOD GetFile(nsIFile * *aFile);
+ NS_IMETHOD GetFileURL(nsIURI * *aFileURL);
+ NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles);
+ NS_IMETHOD Show(int16_t *aReturnVal);
+ NS_IMETHOD ShowW(int16_t *aReturnVal);
+ NS_IMETHOD AppendFilter(const nsAString& aTitle, const nsAString& aFilter);
+
+ // IFileDialogEvents
+ HRESULT STDMETHODCALLTYPE OnFileOk(IFileDialog *pfd);
+ HRESULT STDMETHODCALLTYPE OnFolderChanging(IFileDialog *pfd, IShellItem *psiFolder);
+ HRESULT STDMETHODCALLTYPE OnFolderChange(IFileDialog *pfd);
+ HRESULT STDMETHODCALLTYPE OnSelectionChange(IFileDialog *pfd);
+ HRESULT STDMETHODCALLTYPE OnShareViolation(IFileDialog *pfd, IShellItem *psi, FDE_SHAREVIOLATION_RESPONSE *pResponse);
+ HRESULT STDMETHODCALLTYPE OnTypeChange(IFileDialog *pfd);
+ HRESULT STDMETHODCALLTYPE OnOverwrite(IFileDialog *pfd, IShellItem *psi, FDE_OVERWRITE_RESPONSE *pResponse);
+
+protected:
+ enum PickerType {
+ PICKER_TYPE_OPEN,
+ PICKER_TYPE_SAVE,
+ };
+
+ /* method from nsBaseFilePicker */
+ virtual void InitNative(nsIWidget *aParent,
+ const nsAString& aTitle);
+ static void GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath);
+ void GetFilterListArray(nsString& aFilterList);
+ static bool GetFileNameWrapper(OPENFILENAMEW* ofn, PickerType aType);
+ bool FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType);
+ bool ShowXPFolderPicker(const nsString& aInitialDir);
+ bool ShowXPFilePicker(const nsString& aInitialDir);
+ bool ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError);
+ bool ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError);
+ void AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter);
+ void RememberLastUsedDirectory();
+ bool IsPrivacyModeEnabled();
+ bool IsDefaultPathLink();
+ bool IsDefaultPathHtml();
+ void SetDialogHandle(HWND aWnd);
+ bool ClosePickerIfNeeded(bool aIsXPDialog);
+ static void PickerCallbackTimerFunc(nsITimer *aTimer, void *aPicker);
+ static UINT_PTR CALLBACK MultiFilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+ static UINT_PTR CALLBACK FilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsString mTitle;
+ nsCString mFile;
+ nsString mFilterList;
+ int16_t mSelectedType;
+ nsCOMArray<nsIFile> mFiles;
+ static char mLastUsedDirectory[];
+ nsString mUnicodeFile;
+ static char16_t *mLastUsedUnicodeDirectory;
+ HWND mDlgWnd;
+
+ 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;
+ DWORD mFDECookie;
+};
+
+#if defined(_WIN32_WINNT_bak)
+#undef _WIN32_WINNT
+#define _WIN32_WINNT _WIN32_WINNT_bak
+#undef _WIN32_IE
+#define _WIN32_IE _WIN32_IE_bak
+#endif
+
+#endif // nsFilePicker_h__
diff --git a/widget/windows/nsIdleServiceWin.cpp b/widget/windows/nsIdleServiceWin.cpp
new file mode 100644
index 000000000..4bafe7253
--- /dev/null
+++ b/widget/windows/nsIdleServiceWin.cpp
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIdleServiceWin.h"
+#include <windows.h>
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceWin, nsIdleService)
+
+bool
+nsIdleServiceWin::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
+nsIdleServiceWin::UsePollMode()
+{
+ return true;
+}
diff --git a/widget/windows/nsIdleServiceWin.h b/widget/windows/nsIdleServiceWin.h
new file mode 100644
index 000000000..c3fb1243f
--- /dev/null
+++ b/widget/windows/nsIdleServiceWin.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIdleServiceWin_h__
+#define nsIdleServiceWin_h__
+
+#include "nsIdleService.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 nsIdleServiceWin : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsIdleServiceWin> GetInstance()
+ {
+ RefPtr<nsIdleServiceWin> idleService =
+ nsIdleService::GetInstance().downcast<nsIdleServiceWin>();
+ if (!idleService) {
+ idleService = new nsIdleServiceWin();
+ }
+
+ return idleService.forget();
+ }
+
+protected:
+ nsIdleServiceWin() { }
+ virtual ~nsIdleServiceWin() { }
+ bool UsePollMode() override;
+};
+
+#endif // nsIdleServiceWin_h__
diff --git a/widget/windows/nsImageClipboard.cpp b/widget/windows/nsImageClipboard.cpp
new file mode 100644
index 000000000..fab62eab5
--- /dev/null
+++ b/widget/windows/nsImageClipboard.cpp
@@ -0,0 +1,497 @@
+/* -*- 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 "nsImageClipboard.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/RefPtr.h"
+#include "nsITransferable.h"
+#include "nsGfxCIID.h"
+#include "nsMemory.h"
+#include "prmem.h"
+#include "imgIEncoder.h"
+#include "nsLiteralString.h"
+#include "nsComponentManagerUtils.h"
+
+#define BFH_LENGTH 14
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+/* Things To Do 11/8/00
+
+Check image metrics, can we support them? Do we need to?
+Any other render format? HTML?
+
+*/
+
+
+//
+// nsImageToClipboard ctor
+//
+// Given an imgIContainer, convert it to a DIB that is ready to go on the win32 clipboard
+//
+nsImageToClipboard::nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5)
+ : mImage(aInImage)
+ , mWantDIBV5(aWantDIBV5)
+{
+ // nothing to do here
+}
+
+
+//
+// nsImageToClipboard dtor
+//
+// Clean up after ourselves. We know that we have created the bitmap
+// successfully if we still have a pointer to the header.
+//
+nsImageToClipboard::~nsImageToClipboard()
+{
+}
+
+
+//
+// GetPicture
+//
+// Call to get the actual bits that go on the clipboard. If an error
+// ocurred during conversion, |outBits| will be null.
+//
+// NOTE: The caller owns the handle and must delete it with ::GlobalRelease()
+//
+nsresult
+nsImageToClipboard :: GetPicture ( HANDLE* outBits )
+{
+ NS_ASSERTION ( outBits, "Bad parameter" );
+
+ return CreateFromImage ( mImage, outBits );
+
+} // GetPicture
+
+
+//
+// CalcSize
+//
+// Computes # of bytes needed by a bitmap with the specified attributes.
+//
+int32_t
+nsImageToClipboard :: CalcSize ( int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes )
+{
+ int32_t HeaderMem = sizeof(BITMAPINFOHEADER);
+
+ // add size of pallette to header size
+ if (aBitsPerPixel < 16)
+ HeaderMem += aColors * sizeof(RGBQUAD);
+
+ if (aHeight < 0)
+ aHeight = -aHeight;
+
+ return (HeaderMem + (aHeight * aSpanBytes));
+}
+
+
+//
+// CalcSpanLength
+//
+// Computes the span bytes for determining the overall size of the image
+//
+int32_t
+nsImageToClipboard::CalcSpanLength(uint32_t aWidth, uint32_t aBitCount)
+{
+ int32_t spanBytes = (aWidth * aBitCount) >> 5;
+
+ if ((aWidth * aBitCount) & 0x1F)
+ spanBytes++;
+ spanBytes <<= 2;
+
+ return spanBytes;
+}
+
+
+//
+// CreateFromImage
+//
+// Do the work to setup the bitmap header and copy the bits out of the
+// image.
+//
+nsresult
+nsImageToClipboard::CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap )
+{
+ nsresult rv;
+ *outBitmap = nullptr;
+
+ RefPtr<SourceSurface> surface =
+ inImage->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ surface->GetFormat() == SurfaceFormat::B8G8R8X8);
+
+ RefPtr<DataSourceSurface> dataSurface;
+ if (surface->GetFormat() == SurfaceFormat::B8G8R8A8) {
+ dataSurface = surface->GetDataSurface();
+ } else {
+ // XXXjwatt Bug 995923 - get rid of this copy and handle B8G8R8X8
+ // directly below once bug 995807 is fixed.
+ dataSurface = gfxUtils::
+ CopySurfaceToDataSourceSurfaceWithFormat(surface,
+ SurfaceFormat::B8G8R8A8);
+ }
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t format;
+ nsAutoString options;
+ if (mWantDIBV5) {
+ options.AppendLiteral("version=5;bpp=");
+ } else {
+ options.AppendLiteral("version=3;bpp=");
+ }
+ switch (dataSurface->GetFormat()) {
+ case SurfaceFormat::B8G8R8A8:
+ format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+ options.AppendInt(32);
+ break;
+#if 0
+ // XXXjwatt Bug 995923 - fix |format| and reenable once bug 995807 is fixed.
+ case SurfaceFormat::B8G8R8X8:
+ format = imgIEncoder::INPUT_FORMAT_RGB;
+ options.AppendInt(24);
+ break;
+#endif
+ default:
+ NS_NOTREACHED("Unexpected surface format");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ bool mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map);
+ NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE);
+
+ rv = encoder->InitFromData(map.mData, 0,
+ dataSurface->GetSize().width,
+ dataSurface->GetSize().height,
+ map.mStride,
+ format, options);
+ dataSurface->Unmap();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t size;
+ encoder->GetImageBufferUsed(&size);
+ NS_ENSURE_TRUE(size > BFH_LENGTH, NS_ERROR_FAILURE);
+ HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT,
+ size - BFH_LENGTH);
+ if (!glob)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ char *dst = (char*) ::GlobalLock(glob);
+ char *src;
+ rv = encoder->GetImageBuffer(&src);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ::CopyMemory(dst, src + BFH_LENGTH, size - BFH_LENGTH);
+ ::GlobalUnlock(glob);
+
+ *outBitmap = (HANDLE)glob;
+ return NS_OK;
+}
+
+nsImageFromClipboard :: nsImageFromClipboard ()
+{
+ // nothing to do here
+}
+
+nsImageFromClipboard :: ~nsImageFromClipboard ( )
+{
+}
+
+//
+// GetEncodedImageStream
+//
+// Take the raw clipboard image data and convert it to aMIMEFormat in the form of a nsIInputStream
+//
+nsresult
+nsImageFromClipboard ::GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** aInputStream )
+{
+ NS_ENSURE_ARG_POINTER (aInputStream);
+ NS_ENSURE_ARG_POINTER (aMIMEFormat);
+ nsresult rv;
+ *aInputStream = nullptr;
+
+ // pull the size information out of the BITMAPINFO header and
+ // initialize the image
+ BITMAPINFO* header = (BITMAPINFO *) aClipboardData;
+ int32_t width = header->bmiHeader.biWidth;
+ int32_t height = header->bmiHeader.biHeight;
+ // neg. heights mean the Y axis is inverted and we don't handle that case
+ NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE);
+
+ unsigned char * rgbData = new unsigned char[width * height * 3 /* RGB */];
+
+ if (rgbData) {
+ BYTE * pGlobal = (BYTE *) aClipboardData;
+ // Convert the clipboard image into RGB packed pixel data
+ rv = ConvertColorBitMap((unsigned char *) (pGlobal + header->bmiHeader.biSize), header, rgbData);
+ // if that succeeded, encode the bitmap as aMIMEFormat data. Don't return early or we risk leaking rgbData
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString encoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type="));
+
+ // Map image/jpg to image/jpeg (which is how the encoder is registered).
+ if (strcmp(aMIMEFormat, kJPGImageMime) == 0)
+ encoderCID.AppendLiteral("image/jpeg");
+ else
+ encoderCID.Append(aMIMEFormat);
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get(), &rv);
+ if (NS_SUCCEEDED(rv)){
+ rv = encoder->InitFromData(rgbData, 0, width, height, 3 * width /* RGB * # pixels in a row */,
+ imgIEncoder::INPUT_FORMAT_RGB, EmptyString());
+ if (NS_SUCCEEDED(rv)) {
+ encoder.forget(aInputStream);
+ }
+ }
+ }
+ delete [] rgbData;
+ }
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+
+ return rv;
+} // GetImage
+
+//
+// InvertRows
+//
+// Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place.
+//
+void
+nsImageFromClipboard::InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow)
+{
+ if (!aNumBytesPerRow)
+ return;
+
+ uint32_t numRows = aSizeOfBuffer / aNumBytesPerRow;
+ unsigned char * row = new unsigned char[aNumBytesPerRow];
+
+ uint32_t currentRow = 0;
+ uint32_t lastRow = (numRows - 1) * aNumBytesPerRow;
+ while (currentRow < lastRow)
+ {
+ // store the current row into a temporary buffer
+ memcpy(row, &aInitialBuffer[currentRow], aNumBytesPerRow);
+ memcpy(&aInitialBuffer[currentRow], &aInitialBuffer[lastRow], aNumBytesPerRow);
+ memcpy(&aInitialBuffer[lastRow], row, aNumBytesPerRow);
+ lastRow -= aNumBytesPerRow;
+ currentRow += aNumBytesPerRow;
+ }
+
+ delete[] row;
+}
+
+//
+// ConvertColorBitMap
+//
+// Takes the clipboard bitmap and converts it into a RGB packed pixel values.
+//
+nsresult
+nsImageFromClipboard::ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer)
+{
+ uint8_t bitCount = pBitMapInfo->bmiHeader.biBitCount;
+ uint32_t imageSize = pBitMapInfo->bmiHeader.biSizeImage; // may be zero for BI_RGB bitmaps which means we need to calculate by hand
+ uint32_t bytesPerPixel = bitCount / 8;
+
+ if (bitCount <= 4)
+ bytesPerPixel = 1;
+
+ // rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't
+ // correspond to biWidth.
+ uint32_t rowSize = (bitCount * pBitMapInfo->bmiHeader.biWidth + 7) / 8; // +7 to round up
+ if (rowSize % 4)
+ rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary
+
+ // if our buffer includes a color map, skip over it
+ if (bitCount <= 8)
+ {
+ int32_t bytesToSkip = (pBitMapInfo->bmiHeader.biClrUsed ? pBitMapInfo->bmiHeader.biClrUsed : (1 << bitCount) ) * sizeof(RGBQUAD);
+ aInputBuffer += bytesToSkip;
+ }
+
+ bitFields colorMasks; // only used if biCompression == BI_BITFIELDS
+
+ if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
+ {
+ // color table consists of 3 DWORDS containing the color masks...
+ colorMasks.red = (*((uint32_t*)&(pBitMapInfo->bmiColors[0])));
+ colorMasks.green = (*((uint32_t*)&(pBitMapInfo->bmiColors[1])));
+ colorMasks.blue = (*((uint32_t*)&(pBitMapInfo->bmiColors[2])));
+ CalcBitShift(&colorMasks);
+ aInputBuffer += 3 * sizeof(DWORD);
+ }
+ else if (pBitMapInfo->bmiHeader.biCompression == BI_RGB && !imageSize) // BI_RGB can have a size of zero which means we figure it out
+ {
+ // XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each row
+ imageSize = rowSize * pBitMapInfo->bmiHeader.biHeight;
+ }
+
+ // The windows clipboard image format inverts the rows
+ InvertRows(aInputBuffer, imageSize, rowSize);
+
+ if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
+ {
+ uint32_t index = 0;
+ uint32_t writeIndex = 0;
+
+ unsigned char redValue, greenValue, blueValue;
+ uint8_t colorTableEntry = 0;
+ int8_t bit; // used for grayscale bitmaps where each bit is a pixel
+ uint32_t numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; // how many more pixels do we still need to read for the current row
+ uint32_t pos = 0;
+
+ while (index < imageSize)
+ {
+ switch (bitCount)
+ {
+ case 1:
+ for (bit = 7; bit >= 0 && numPixelsLeftInRow; bit--)
+ {
+ colorTableEntry = (aInputBuffer[index] >> bit) & 1;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
+ numPixelsLeftInRow--;
+ }
+ pos += 1;
+ break;
+ case 4:
+ {
+ // each aInputBuffer[index] entry contains data for two pixels.
+ // read the first pixel
+ colorTableEntry = aInputBuffer[index] >> 4;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
+ numPixelsLeftInRow--;
+
+ if (numPixelsLeftInRow) // now read the second pixel
+ {
+ colorTableEntry = aInputBuffer[index] & 0xF;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
+ numPixelsLeftInRow--;
+ }
+ pos += 1;
+ }
+ break;
+ case 8:
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbBlue;
+ numPixelsLeftInRow--;
+ pos += 1;
+ break;
+ case 16:
+ {
+ uint16_t num = 0;
+ num = (uint8_t) aInputBuffer[index+1];
+ num <<= 8;
+ num |= (uint8_t) aInputBuffer[index];
+
+ redValue = ((uint32_t) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16;
+ greenValue = ((uint32_t)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8;
+ blueValue = ((uint32_t)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF);
+
+ // now we have the right RGB values...
+ aOutBuffer[writeIndex++] = redValue;
+ aOutBuffer[writeIndex++] = greenValue;
+ aOutBuffer[writeIndex++] = blueValue;
+ numPixelsLeftInRow--;
+ pos += 2;
+ }
+ break;
+ case 32:
+ case 24:
+ if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
+ {
+ uint32_t val = *((uint32_t*) (aInputBuffer + index) );
+ aOutBuffer[writeIndex++] = (val & colorMasks.red) >> colorMasks.redRightShift << colorMasks.redLeftShift;
+ aOutBuffer[writeIndex++] = (val & colorMasks.green) >> colorMasks.greenRightShift << colorMasks.greenLeftShift;
+ aOutBuffer[writeIndex++] = (val & colorMasks.blue) >> colorMasks.blueRightShift << colorMasks.blueLeftShift;
+ numPixelsLeftInRow--;
+ pos += 4; // we read in 4 bytes of data in order to process this pixel
+ }
+ else
+ {
+ aOutBuffer[writeIndex++] = aInputBuffer[index+2];
+ aOutBuffer[writeIndex++] = aInputBuffer[index+1];
+ aOutBuffer[writeIndex++] = aInputBuffer[index];
+ numPixelsLeftInRow--;
+ pos += bytesPerPixel; // 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)...
+ }
+ break;
+ default:
+ // This is probably the wrong place to check this...
+ return NS_ERROR_FAILURE;
+ }
+
+ index += bytesPerPixel; // increment our loop counter
+
+ if (!numPixelsLeftInRow)
+ {
+ if (rowSize != pos)
+ {
+ // advance index to skip over remaining padding bytes
+ index += (rowSize - pos);
+ }
+ numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth;
+ pos = 0;
+ }
+
+ } // while we still have bytes to process
+ }
+
+ return NS_OK;
+}
+
+void nsImageFromClipboard::CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength)
+{
+ // find the rightmost 1
+ uint8_t pos;
+ bool started = false;
+ aBegin = aLength = 0;
+ for (pos = 0; pos <= 31; pos++)
+ {
+ if (!started && (aMask & (1 << pos)))
+ {
+ aBegin = pos;
+ started = true;
+ }
+ else if (started && !(aMask & (1 << pos)))
+ {
+ aLength = pos - aBegin;
+ break;
+ }
+ }
+}
+
+void nsImageFromClipboard::CalcBitShift(bitFields * aColorMask)
+{
+ uint8_t begin, length;
+ // red
+ CalcBitmask(aColorMask->red, begin, length);
+ aColorMask->redRightShift = begin;
+ aColorMask->redLeftShift = 8 - length;
+ // green
+ CalcBitmask(aColorMask->green, begin, length);
+ aColorMask->greenRightShift = begin;
+ aColorMask->greenLeftShift = 8 - length;
+ // blue
+ CalcBitmask(aColorMask->blue, begin, length);
+ aColorMask->blueRightShift = begin;
+ aColorMask->blueLeftShift = 8 - length;
+}
diff --git a/widget/windows/nsImageClipboard.h b/widget/windows/nsImageClipboard.h
new file mode 100644
index 000000000..25b33cc56
--- /dev/null
+++ b/widget/windows/nsImageClipboard.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImageClipboard_h
+#define nsImageClipboard_h
+
+/* Things To Do 11/8/00
+
+Check image metrics, can we support them? Do we need to?
+Any other render format? HTML?
+
+*/
+
+#include "nsError.h"
+#include <windows.h>
+
+#include "nsCOMPtr.h"
+#include "imgIContainer.h"
+#include "nsIInputStream.h"
+
+
+//
+// nsImageToClipboard
+//
+// A utility class that takes an imgIContainer and does all the bitmap magic
+// to allow us to put it on the clipboard
+//
+class nsImageToClipboard
+{
+public:
+ nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5 = true);
+ ~nsImageToClipboard();
+
+ // Call to get the actual bits that go on the clipboard. If |nullptr|, the
+ // setup operations have failed.
+ //
+ // NOTE: The caller owns the handle and must delete it with ::GlobalRelease()
+ nsresult GetPicture ( HANDLE* outBits ) ;
+
+private:
+
+ // Computes # of bytes needed by a bitmap with the specified attributes.
+ int32_t CalcSize(int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes);
+ int32_t CalcSpanLength(uint32_t aWidth, uint32_t aBitCount);
+
+ // Do the work
+ nsresult CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap );
+
+ nsCOMPtr<imgIContainer> mImage; // the image we're working with
+ bool mWantDIBV5;
+
+}; // class nsImageToClipboard
+
+
+struct bitFields {
+ uint32_t red;
+ uint32_t green;
+ uint32_t blue;
+ uint8_t redLeftShift;
+ uint8_t redRightShift;
+ uint8_t greenLeftShift;
+ uint8_t greenRightShift;
+ uint8_t blueLeftShift;
+ uint8_t blueRightShift;
+};
+
+//
+// nsImageFromClipboard
+//
+// A utility class that takes a DIB from the win32 clipboard and does
+// all the bitmap magic to convert it to a PNG or a JPEG in the form of a nsIInputStream
+//
+class nsImageFromClipboard
+{
+public:
+ nsImageFromClipboard () ;
+ ~nsImageFromClipboard ( ) ;
+
+ // Retrieve the newly created image
+ nsresult GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** outImage);
+
+private:
+
+ void InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow);
+ nsresult ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer);
+ void CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength);
+ void CalcBitShift(bitFields * aColorMask);
+
+}; // nsImageFromClipboard
+
+#endif
diff --git a/widget/windows/nsLookAndFeel.cpp b/widget/windows/nsLookAndFeel.cpp
new file mode 100644
index 000000000..7c427ac9f
--- /dev/null
+++ b/widget/windows/nsLookAndFeel.cpp
@@ -0,0 +1,701 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "gfxFont.h"
+#include "WinUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsVersion.h"
+#include "gfxFontConstants.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+//static
+LookAndFeel::OperatingSystemVersion
+nsLookAndFeel::GetOperatingSystemVersion()
+{
+ static OperatingSystemVersion version = eOperatingSystemVersion_Unknown;
+
+ if (version != eOperatingSystemVersion_Unknown) {
+ return version;
+ }
+
+ if (IsWin10OrLater()) {
+ version = eOperatingSystemVersion_Windows10;
+ } else if (IsWin8OrLater()) {
+ version = eOperatingSystemVersion_Windows8;
+ } else if (IsWin7OrLater()) {
+ version = eOperatingSystemVersion_Windows7;
+ } else if (IsVistaOrLater()) {
+ version = eOperatingSystemVersion_WindowsVista;
+ } else {
+ version = eOperatingSystemVersion_WindowsXP;
+ }
+
+ 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;
+}
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel()
+ , mUseAccessibilityTheme(0)
+{
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::TOUCH_ENABLED_DEVICE,
+ WinUtils::IsTouchDeviceSupportPresent());
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+}
+
+nsresult
+nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
+{
+ nsresult res = NS_OK;
+
+ int idx;
+ switch (aID) {
+ case eColorID_WindowBackground:
+ idx = COLOR_WINDOW;
+ break;
+ case eColorID_WindowForeground:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID_WidgetBackground:
+ idx = COLOR_BTNFACE;
+ break;
+ case eColorID_WidgetForeground:
+ idx = COLOR_BTNTEXT;
+ break;
+ case eColorID_WidgetSelectBackground:
+ idx = COLOR_HIGHLIGHT;
+ break;
+ case eColorID_WidgetSelectForeground:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case eColorID_Widget3DHighlight:
+ idx = COLOR_BTNHIGHLIGHT;
+ break;
+ case eColorID_Widget3DShadow:
+ idx = COLOR_BTNSHADOW;
+ break;
+ case eColorID_TextBackground:
+ idx = COLOR_WINDOW;
+ break;
+ case eColorID_TextForeground:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID_TextSelectBackground:
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ idx = COLOR_HIGHLIGHT;
+ break;
+ case eColorID_TextSelectForeground:
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ return NS_OK;
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ return NS_OK;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ return NS_OK;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ return NS_OK;
+ case eColorID_SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ return NS_OK;
+
+ // New CSS 2 Color definitions
+ case eColorID_activeborder:
+ idx = COLOR_ACTIVEBORDER;
+ break;
+ case eColorID_activecaption:
+ idx = COLOR_ACTIVECAPTION;
+ break;
+ case eColorID_appworkspace:
+ idx = COLOR_APPWORKSPACE;
+ break;
+ case eColorID_background:
+ idx = COLOR_BACKGROUND;
+ break;
+ case eColorID_buttonface:
+ case eColorID__moz_buttonhoverface:
+ idx = COLOR_BTNFACE;
+ break;
+ case eColorID_buttonhighlight:
+ idx = COLOR_BTNHIGHLIGHT;
+ break;
+ case eColorID_buttonshadow:
+ idx = COLOR_BTNSHADOW;
+ break;
+ case eColorID_buttontext:
+ case eColorID__moz_buttonhovertext:
+ idx = COLOR_BTNTEXT;
+ break;
+ case eColorID_captiontext:
+ idx = COLOR_CAPTIONTEXT;
+ break;
+ case eColorID_graytext:
+ idx = COLOR_GRAYTEXT;
+ break;
+ case eColorID_highlight:
+ case eColorID__moz_html_cellhighlight:
+ case eColorID__moz_menuhover:
+ idx = COLOR_HIGHLIGHT;
+ break;
+ case eColorID__moz_menubarhovertext:
+ if (!IsVistaOrLater() || !IsAppThemed())
+ {
+ idx = nsUXThemeData::sFlatMenus ?
+ COLOR_HIGHLIGHTTEXT :
+ COLOR_MENUTEXT;
+ break;
+ }
+ // Fall through
+ case eColorID__moz_menuhovertext:
+ if (IsVistaOrLater() && IsAppThemed())
+ {
+ res = ::GetColorFromTheme(eUXMenu,
+ MENU_POPUPITEM, MPI_HOT, TMT_TEXTCOLOR, aColor);
+ if (NS_SUCCEEDED(res))
+ return res;
+ // fall through to highlight case
+ }
+ case eColorID_highlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case eColorID_inactiveborder:
+ idx = COLOR_INACTIVEBORDER;
+ break;
+ case eColorID_inactivecaption:
+ idx = COLOR_INACTIVECAPTION;
+ break;
+ case eColorID_inactivecaptiontext:
+ idx = COLOR_INACTIVECAPTIONTEXT;
+ break;
+ case eColorID_infobackground:
+ idx = COLOR_INFOBK;
+ break;
+ case eColorID_infotext:
+ idx = COLOR_INFOTEXT;
+ break;
+ case eColorID_menu:
+ idx = COLOR_MENU;
+ break;
+ case eColorID_menutext:
+ case eColorID__moz_menubartext:
+ idx = COLOR_MENUTEXT;
+ break;
+ case eColorID_scrollbar:
+ idx = COLOR_SCROLLBAR;
+ break;
+ case eColorID_threeddarkshadow:
+ idx = COLOR_3DDKSHADOW;
+ break;
+ case eColorID_threedface:
+ idx = COLOR_3DFACE;
+ break;
+ case eColorID_threedhighlight:
+ idx = COLOR_3DHIGHLIGHT;
+ break;
+ case eColorID_threedlightshadow:
+ idx = COLOR_3DLIGHT;
+ break;
+ case eColorID_threedshadow:
+ idx = COLOR_3DSHADOW;
+ break;
+ case eColorID_window:
+ idx = COLOR_WINDOW;
+ break;
+ case eColorID_windowframe:
+ idx = COLOR_WINDOWFRAME;
+ break;
+ case eColorID_windowtext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_eventreerow:
+ case eColorID__moz_oddtreerow:
+ case eColorID__moz_field:
+ case eColorID__moz_combobox:
+ idx = COLOR_WINDOW;
+ break;
+ case eColorID__moz_fieldtext:
+ case eColorID__moz_comboboxtext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_dialog:
+ case eColorID__moz_cellhighlight:
+ idx = COLOR_3DFACE;
+ break;
+ case eColorID__moz_win_mediatext:
+ if (IsVistaOrLater() && IsAppThemed()) {
+ res = ::GetColorFromTheme(eUXMediaToolbar,
+ TP_BUTTON, TS_NORMAL, TMT_TEXTCOLOR, aColor);
+ if (NS_SUCCEEDED(res))
+ return res;
+ }
+ // if we've gotten here just return -moz-dialogtext instead
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_win_communicationstext:
+ if (IsVistaOrLater() && IsAppThemed())
+ {
+ res = ::GetColorFromTheme(eUXCommunicationsToolbar,
+ TP_BUTTON, TS_NORMAL, TMT_TEXTCOLOR, aColor);
+ if (NS_SUCCEEDED(res))
+ return res;
+ }
+ // if we've gotten here just return -moz-dialogtext instead
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_dialogtext:
+ case eColorID__moz_cellhighlighttext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_dragtargetzone:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case eColorID__moz_buttondefault:
+ idx = COLOR_3DDKSHADOW;
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ idx = COLOR_HOTLIGHT;
+ break;
+ default:
+ idx = COLOR_WINDOW;
+ break;
+ }
+
+ DWORD color = ::GetSysColor(idx);
+ aColor = COLOREF_2_NSRGB(color);
+
+ return res;
+}
+
+nsresult
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ aResult = (int32_t)::GetCaretBlinkTime();
+ break;
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case eIntID_SubmenuDelay:
+ // This will default to the Windows' default
+ // (400ms) on error.
+ aResult = GetSystemParam(SPI_GETMENUSHOWDELAY, 400);
+ break;
+ case eIntID_TooltipDelay:
+ aResult = 500;
+ break;
+ case eIntID_MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+ case eIntID_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 eIntID_DragThresholdY:
+ aResult = ::GetSystemMetrics(SM_CYDRAG) - 1;
+ break;
+ case eIntID_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 eIntID_ScrollArrowStyle:
+ aResult = eScrollArrowStyle_Single;
+ break;
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case eIntID_TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeCloseDelay:
+ aResult = 0;
+ break;
+ case eIntID_TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case eIntID_TreeScrollDelay:
+ aResult = 100;
+ break;
+ case eIntID_TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case eIntID_WindowsClassic:
+ aResult = !IsAppThemed();
+ break;
+ case eIntID_TouchEnabled:
+ aResult = WinUtils::IsTouchDeviceSupportPresent();
+ break;
+ case eIntID_WindowsDefaultTheme:
+ aResult = nsUXThemeData::IsDefaultWindowTheme();
+ break;
+ case eIntID_WindowsThemeIdentifier:
+ aResult = nsUXThemeData::GetNativeThemeId();
+ break;
+
+ case eIntID_OperatingSystemVersionIdentifier:
+ {
+ aResult = GetOperatingSystemVersion();
+ break;
+ }
+
+ case eIntID_MacGraphiteTheme:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_DWMCompositor:
+ aResult = nsUXThemeData::CheckForCompositor();
+ break;
+ case eIntID_WindowsGlass:
+ // Aero Glass is only available prior to Windows 8 when DWM is used.
+ aResult = (nsUXThemeData::CheckForCompositor() && !IsWin8OrLater());
+ break;
+ case eIntID_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 eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DASHED;
+ break;
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ break;
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+ case eIntID_ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+ case eIntID_SwipeAnimationEnabled:
+ aResult = 0;
+ break;
+ case eIntID_ColorPickerAvailable:
+ aResult = true;
+ break;
+ case eIntID_UseOverlayScrollbars:
+ aResult = false;
+ break;
+ case eIntID_AllowOverlayScrollbarsOverlap:
+ aResult = 0;
+ break;
+ case eIntID_ScrollbarDisplayOnMouseMove:
+ aResult = 1;
+ break;
+ case eIntID_ScrollbarFadeBeginDelay:
+ aResult = 2500;
+ break;
+ case eIntID_ScrollbarFadeDuration:
+ aResult = 350;
+ break;
+ case eIntID_ContextMenuOffsetVertical:
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+nsresult
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+static bool
+GetSysFontInfo(HDC aHDC, LookAndFeel::FontID anID,
+ nsString &aFontName,
+ gfxFontStyle &aFontStyle)
+{
+ LOGFONTW* ptrLogFont = nullptr;
+ LOGFONTW logFont;
+ NONCLIENTMETRICSW ncm;
+ char16_t name[LF_FACESIZE];
+ bool useShellDlg = false;
+
+ // Depending on which stock font we want, there are a couple of
+ // places we might have to look it up.
+ switch (anID) {
+ case LookAndFeel::eFont_Icon:
+ if (!::SystemParametersInfoW(SPI_GETICONTITLELOGFONT,
+ sizeof(logFont), (PVOID)&logFont, 0))
+ return false;
+
+ ptrLogFont = &logFont;
+ break;
+
+ default:
+ ncm.cbSize = sizeof(NONCLIENTMETRICSW);
+ if (!::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS,
+ sizeof(ncm), (PVOID)&ncm, 0))
+ return false;
+
+ switch (anID) {
+ case LookAndFeel::eFont_Menu:
+ case LookAndFeel::eFont_PullDownMenu:
+ ptrLogFont = &ncm.lfMenuFont;
+ break;
+ case LookAndFeel::eFont_Caption:
+ ptrLogFont = &ncm.lfCaptionFont;
+ break;
+ case LookAndFeel::eFont_SmallCaption:
+ ptrLogFont = &ncm.lfSmCaptionFont;
+ break;
+ case LookAndFeel::eFont_StatusBar:
+ case LookAndFeel::eFont_Tooltips:
+ ptrLogFont = &ncm.lfStatusFont;
+ break;
+ case LookAndFeel::eFont_Widget:
+ case LookAndFeel::eFont_Dialog:
+ case LookAndFeel::eFont_Button:
+ case LookAndFeel::eFont_Field:
+ case LookAndFeel::eFont_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.
+ useShellDlg = true;
+ // Fall through so that we can get size from lfMessageFont;
+ // but later we'll use the (virtual) "MS Shell Dlg 2" font name
+ // instead of the LOGFONT's.
+ default:
+ ptrLogFont = &ncm.lfMessageFont;
+ break;
+ }
+ break;
+ }
+
+ // 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 = -ptrLogFont->lfHeight;
+ if (pixelHeight < 0) {
+ HFONT hFont = ::CreateFontIndirectW(ptrLogFont);
+ if (!hFont)
+ return false;
+ HGDIOBJ hObject = ::SelectObject(aHDC, hFont);
+ TEXTMETRIC tm;
+ ::GetTextMetrics(aHDC, &tm);
+ ::SelectObject(aHDC, hObject);
+ ::DeleteObject(hFont);
+ 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;
+
+ aFontStyle.size = pixelHeight;
+
+ // FIXME: What about oblique?
+ aFontStyle.style =
+ (ptrLogFont->lfItalic) ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL;
+
+ // FIXME: Other weights?
+ aFontStyle.weight =
+ (ptrLogFont->lfWeight == FW_BOLD ?
+ NS_FONT_WEIGHT_BOLD : NS_FONT_WEIGHT_NORMAL);
+
+ // FIXME: Set aFontStyle->stretch correctly!
+ aFontStyle.stretch = NS_FONT_STRETCH_NORMAL;
+
+ aFontStyle.systemFont = true;
+
+ if (useShellDlg) {
+ aFontName = NS_LITERAL_STRING("MS Shell Dlg 2");
+ } else {
+ memcpy(name, ptrLogFont->lfFaceName, LF_FACESIZE*sizeof(char16_t));
+ aFontName = name;
+ }
+
+ return true;
+}
+
+bool
+nsLookAndFeel::GetFontImpl(FontID anID, nsString &aFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ HDC tdc = GetDC(nullptr);
+ bool status = GetSysFontInfo(tdc, anID, aFontName, aFontStyle);
+ ReleaseDC(nullptr, tdc);
+ // now convert the logical font size from GetSysFontInfo into device pixels for layout
+ aFontStyle.size *= aDevPixPerCSSPixel;
+ return status;
+}
+
+/* virtual */
+char16_t
+nsLookAndFeel::GetPasswordCharacterImpl()
+{
+#define UNICODE_BLACK_CIRCLE_CHAR 0x25cf
+ return UNICODE_BLACK_CIRCLE_CHAR;
+}
+
+nsTArray<LookAndFeelInt>
+nsLookAndFeel::GetIntCacheImpl()
+{
+ nsTArray<LookAndFeelInt> lookAndFeelIntCache =
+ nsXPLookAndFeel::GetIntCacheImpl();
+
+ LookAndFeelInt useAccessibilityTheme;
+ useAccessibilityTheme.id = eIntID_UseAccessibilityTheme;
+ useAccessibilityTheme.value = GetInt(eIntID_UseAccessibilityTheme);
+ lookAndFeelIntCache.AppendElement(useAccessibilityTheme);
+
+ return lookAndFeelIntCache;
+}
+
+void
+nsLookAndFeel::SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache)
+{
+ for (auto entry : aLookAndFeelIntCache) {
+ if (entry.id == eIntID_UseAccessibilityTheme) {
+ mUseAccessibilityTheme = entry.value;
+ break;
+ }
+ }
+}
+
diff --git a/widget/windows/nsLookAndFeel.h b/widget/windows/nsLookAndFeel.h
new file mode 100644
index 000000000..bc2d158b6
--- /dev/null
+++ b/widget/windows/nsLookAndFeel.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+/*
+ * 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
+typedef enum _AR_STATE
+{
+ AR_ENABLED = 0x0,
+ AR_DISABLED = 0x1,
+ AR_SUPPRESSED = 0x2,
+ AR_REMOTESESSION = 0x4,
+ AR_MULTIMON = 0x8,
+ AR_NOSENSOR = 0x10,
+ AR_NOT_SUPPORTED = 0x20,
+ AR_DOCKED = 0x40,
+ AR_LAPTOP = 0x80
+} AR_STATE, *PAR_STATE;
+#endif
+
+class nsLookAndFeel: public nsXPLookAndFeel {
+ static OperatingSystemVersion GetOperatingSystemVersion();
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel);
+ virtual char16_t GetPasswordCharacterImpl();
+
+ virtual nsTArray<LookAndFeelInt> GetIntCacheImpl();
+ virtual void SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache);
+
+private:
+ int32_t mUseAccessibilityTheme;
+};
+
+#endif
diff --git a/widget/windows/nsNativeDragSource.cpp b/widget/windows/nsNativeDragSource.cpp
new file mode 100644
index 000000000..857907076
--- /dev/null
+++ b/widget/windows/nsNativeDragSource.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 "nsNativeDragSource.h"
+#include <stdio.h>
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsIServiceManager.h"
+#include "nsToolkit.h"
+#include "nsWidgetsCID.h"
+#include "nsIDragService.h"
+
+/*
+ * class nsNativeDragSource
+ */
+nsNativeDragSource::nsNativeDragSource(nsIDOMDataTransfer* aDataTransfer) :
+ m_cRef(0),
+ m_hCursor(nullptr),
+ mUserCancelled(false)
+{
+ mDataTransfer = do_QueryInterface(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 000000000..19d127599
--- /dev/null
+++ b/widget/windows/nsNativeDragSource.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 _nsNativeDragSource_h_
+#define _nsNativeDragSource_h_
+
+#include "nscore.h"
+#include "nsIDOMDataTransfer.h"
+#include "nsCOMPtr.h"
+#include <ole2.h>
+#include <oleidl.h>
+#include "mozilla/Attributes.h"
+
+//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);
+ nsNativeDragSource(nsIDOMDataTransfer* 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
+ nsCOMPtr<nsIDOMDataTransfer> 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 000000000..1686642a3
--- /dev/null
+++ b/widget/windows/nsNativeDragTarget.cpp
@@ -0,0 +1,486 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsIServiceManager.h"
+#include "nsIDOMNode.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;
+
+/*
+ * 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)
+{
+ nsEventStatus status;
+ 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.inputSource = static_cast<nsBaseDragService*>(mDragService)->GetInputSource();
+
+ mWidget->DispatchEvent(&event, status);
+}
+
+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()) {
+ 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;
+ }
+
+ // 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()) {
+ POINT pt = { ptl.x, ptl.y };
+ GetDropTargetHelper()->DragOver(&pt, *pdwEffect);
+ }
+
+ mDragService->FireDragEventAtSource(eDrag);
+ // 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<nsIDOMNode> 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).
+ mDragService->EndDragSession(false);
+ }
+ }
+
+ // 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) {
+ mDragService->EndDragSession(false);
+ }
+ 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));
+ serv->EndDragSession(true);
+
+ // 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 000000000..fbc28fc92
--- /dev/null
+++ b/widget/windows/nsNativeDragTarget.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 _nsNativeDragTarget_h_
+#define _nsNativeDragTarget_h_
+
+#include "nsCOMPtr.h"
+#include "nsIDragSession.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:
+ 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.
+ 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.
+ 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.
+ STDMETHODIMP Drop(LPDATAOBJECT pSource, DWORD grfKeyState,
+ POINTL point, DWORD* pEffect);
+ /**
+ * Cancel the current drag session, if any.
+ */
+ void DragCancel();
+
+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;
+};
+
+#endif // _nsNativeDragTarget_h_
+
+
diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp
new file mode 100644
index 000000000..4ff6b0af9
--- /dev/null
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -0,0 +1,4168 @@
+/* -*- 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/EventStates.h"
+#include "mozilla/Logging.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsDeviceContext.h"
+#include "nsRenderingContext.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsTransform2D.h"
+#include "nsThemeConstants.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsLookAndFeel.h"
+#include "nsMenuFrame.h"
+#include "nsGkAtoms.h"
+#include <malloc.h>
+#include "nsWindow.h"
+#include "nsIComboboxControlFrame.h"
+#include "prinrval.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 mozilla::IsVistaOrLater;
+using namespace mozilla;
+using namespace mozilla::widget;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin, nsNativeTheme, nsITheme)
+
+nsNativeThemeWin::nsNativeThemeWin() :
+ mProgressDeterminateTimeStamp(TimeStamp::Now()),
+ mProgressIndeterminateTimeStamp(TimeStamp::Now())
+{
+ // 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;
+ }
+ // 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
+ double scaleFactor = nsIWidget::DefaultScaleOverride();
+ if (scaleFactor <= 0.0) {
+ scaleFactor = WinUtils::LogToPhysFactor(hdc);
+ }
+ 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;
+}
+
+/* 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
+ *
+ * xp theme min 0 2 0 2
+ * xp theme max 0 2 1 2
+ * xp theme close 1 2 2 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
+ *
+ * xp theme min 0 0 1 0
+ * xp theme max 1 0 0 0
+ * xp theme close 0 0 0 0
+ */
+
+enum CaptionDesktopTheme {
+ CAPTION_CLASSIC = 0,
+ CAPTION_BASIC,
+ CAPTION_XPTHEME,
+};
+
+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 (!IsAppThemed())
+ offset = buttonData[CAPTION_CLASSIC].hotPadding[button];
+ else if (!IsVistaOrLater())
+ offset = buttonData[CAPTION_XPTHEME].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 (!IsAppThemed())
+ offset = buttonData[CAPTION_CLASSIC].hotPadding[button];
+ else if (!IsVistaOrLater())
+ offset = buttonData[CAPTION_XPTHEME].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 kProgressHorizontalVistaOverlaySize = 120;
+// The width of the overlay used for the horizontal indeterminate progress bars on XP.
+static const int32_t kProgressHorizontalXPOverlaySize = 55;
+// 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)
+{
+ if (aIsVertical) {
+ if (IsVistaOrLater()) {
+ return PP_MOVEOVERLAYVERT;
+ }
+ return PP_CHUNKVERT;
+ } else {
+ if (IsVistaOrLater()) {
+ return PP_MOVEOVERLAY;
+ }
+ return PP_CHUNK;
+ }
+}
+
+/*
+ * 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 (IsVistaOrLater()) {
+ if (aIsVertical) {
+ return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize
+ : kProgressVerticalOverlaySize;
+ }
+ return kProgressHorizontalVistaOverlaySize;
+ }
+ return kProgressHorizontalXPOverlaySize;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * DrawChunkProgressMeter - renders an xp style chunked progress meter. Called
+ * by DrawProgressMeter.
+ *
+ * @param aTheme progress theme handle
+ * @param aHdc hdc returned by gfxWindowsNativeDrawing
+ * @param aPart the PP_X progress part
+ * @param aState the theme state
+ * @param aFrame the elements frame
+ * @param aWidgetRect bounding rect for the widget
+ * @param aClipRect dirty rect that needs drawing.
+ * @param aAppUnits app units per device pixel
+ * @param aIsIndeterm is an indeterminate progress?
+ * @param aIsVertical render a vertical progress?
+ * @param aIsRtl direction is rtl
+ */
+static void
+DrawChunkProgressMeter(HTHEME aTheme, HDC aHdc, int aPart,
+ int aState, nsIFrame* aFrame, RECT* aWidgetRect,
+ RECT* aClipRect, gfxFloat aAppUnits, bool aIsIndeterm,
+ bool aIsVertical, bool aIsRtl)
+{
+ NS_ASSERTION(aTheme, "Bad theme.");
+ NS_ASSERTION(aHdc, "Bad hdc.");
+ NS_ASSERTION(aWidgetRect, "Bad rect.");
+ NS_ASSERTION(aClipRect, "Bad clip rect.");
+ NS_ASSERTION(aFrame, "Bad frame.");
+
+ // For horizontal meters, the theme lib paints the right graphic but doesn't
+ // paint the chunks, so we do that manually. For vertical meters, the theme
+ // library draws everything correctly.
+ if (aIsVertical) {
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect, aClipRect);
+ return;
+ }
+
+ // query for the proper chunk metrics
+ int chunkSize, spaceSize;
+ if (FAILED(GetThemeMetric(aTheme, aHdc, aPart, aState,
+ TMT_PROGRESSCHUNKSIZE, &chunkSize)) ||
+ FAILED(GetThemeMetric(aTheme, aHdc, aPart, aState,
+ TMT_PROGRESSSPACESIZE, &spaceSize))) {
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect, aClipRect);
+ return;
+ }
+
+ // render chunks
+ if (!aIsRtl || aIsIndeterm) {
+ for (int chunk = aWidgetRect->left; chunk <= aWidgetRect->right;
+ chunk += (chunkSize+spaceSize)) {
+ if (!aIsIndeterm && ((chunk + chunkSize) > aWidgetRect->right)) {
+ // aWidgetRect->right represents the end of the meter. Partial blocks
+ // don't get rendered with one exception, so exit here if we don't have
+ // a full chunk to draw.
+ // The above is true *except* when the meter is at 100% fill, in which
+ // case Windows renders any remaining partial block. Query the parent
+ // frame to find out if we're at 100%.
+ if (!IsProgressMeterFilled(aFrame)) {
+ break;
+ }
+ }
+ RECT bounds =
+ { chunk, aWidgetRect->top, chunk + chunkSize, aWidgetRect->bottom };
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, &bounds, aClipRect);
+ }
+ } else {
+ // rtl needs to grow in the opposite direction to look right.
+ for (int chunk = aWidgetRect->right; chunk >= aWidgetRect->left;
+ chunk -= (chunkSize+spaceSize)) {
+ if ((chunk - chunkSize) < aWidgetRect->left) {
+ if (!IsProgressMeterFilled(aFrame)) {
+ break;
+ }
+ }
+ RECT bounds =
+ { chunk - chunkSize, aWidgetRect->top, chunk, aWidgetRect->bottom };
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, &bounds, aClipRect);
+ }
+ }
+}
+
+/*
+ * 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 aWidgetType 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, int aWidgetType,
+ HANDLE aTheme, HDC aHdc,
+ int aPart, int aState,
+ RECT* aWidgetRect, RECT* aClipRect,
+ gfxFloat aAppUnits)
+{
+ 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;
+ if (!IsVistaOrLater()) {
+ // Adjust clipping out by one pixel. XP progress meters are inset,
+ // Vista+ are not.
+ InflateRect(&adjWidgetRect, 1, 1);
+ InflateRect(&adjClipRect, 1, 1);
+ }
+
+ 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, aWidgetType);
+ bool vertical = IsVerticalProgress(parentFrame) ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL;
+ bool indeterminate = IsIndeterminateProgress(parentFrame, eventStates);
+ bool animate = indeterminate;
+
+ if (IsVistaOrLater()) {
+ // 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;
+ }
+ } else if (!indeterminate) {
+ // XP progress meters are 'chunk' style.
+ DrawChunkProgressMeter(aTheme, aHdc, aPart, aState, aFrame,
+ &adjWidgetRect, &adjClipRect, aAppUnits,
+ indeterminate, vertical, IsFrameRTL(aFrame));
+ }
+
+ if (animate) {
+ // Indeterminate rendering
+ int32_t overlayPart = GetProgressOverlayStyle(vertical);
+ RECT overlayRect =
+ CalculateProgressOverlayRect(aFrame, &adjWidgetRect, vertical,
+ indeterminate, false);
+ if (IsVistaOrLater()) {
+ DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect,
+ &adjClipRect);
+ } else {
+ DrawChunkProgressMeter(aTheme, aHdc, overlayPart, aState, aFrame,
+ &overlayRect, &adjClipRect, aAppUnits,
+ indeterminate, vertical, IsFrameRTL(aFrame));
+ }
+
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
+ NS_WARNING("unable to animate progress widget!");
+ }
+ }
+}
+
+HANDLE
+nsNativeThemeWin::GetTheme(uint8_t aWidgetType)
+{
+ if (!IsVistaOrLater()) {
+ // On XP or earlier, render dropdowns as textfields;
+ // doing it the right way works fine with the MS themes,
+ // but breaks on a lot of custom themes (presumably because MS
+ // apps do the textfield border business as well).
+ if (aWidgetType == NS_THEME_MENULIST)
+ aWidgetType = NS_THEME_TEXTFIELD;
+ }
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ case NS_THEME_RADIO:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_GROUPBOX:
+ return nsUXThemeData::GetTheme(eUXButton);
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_FOCUS_OUTLINE:
+ return nsUXThemeData::GetTheme(eUXEdit);
+ case NS_THEME_TOOLTIP:
+ // XP/2K3 should force a classic treatment of tooltips
+ return !IsVistaOrLater() ?
+ nullptr : nsUXThemeData::GetTheme(eUXTooltip);
+ case NS_THEME_TOOLBOX:
+ return nsUXThemeData::GetTheme(eUXRebar);
+ case NS_THEME_WIN_MEDIA_TOOLBOX:
+ return nsUXThemeData::GetTheme(eUXMediaRebar);
+ case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
+ return nsUXThemeData::GetTheme(eUXCommunicationsRebar);
+ case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
+ return nsUXThemeData::GetTheme(eUXBrowserTabBarRebar);
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_SEPARATOR:
+ return nsUXThemeData::GetTheme(eUXToolbar);
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ return nsUXThemeData::GetTheme(eUXProgress);
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ return nsUXThemeData::GetTheme(eUXTab);
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ return nsUXThemeData::GetTheme(eUXScrollbar);
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ return nsUXThemeData::GetTheme(eUXTrackbar);
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ return nsUXThemeData::GetTheme(eUXSpin);
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_RESIZER:
+ return nsUXThemeData::GetTheme(eUXStatus);
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ return nsUXThemeData::GetTheme(eUXCombobox);
+ case NS_THEME_TREEHEADERCELL:
+ case NS_THEME_TREEHEADERSORTARROW:
+ return nsUXThemeData::GetTheme(eUXHeader);
+ case NS_THEME_LISTBOX:
+ case NS_THEME_LISTITEM:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TREETWISTYOPEN:
+ case NS_THEME_TREEITEM:
+ return nsUXThemeData::GetTheme(eUXListview);
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUIMAGE:
+ case NS_THEME_MENUITEMTEXT:
+ return nsUXThemeData::GetTheme(eUXMenu);
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ return nsUXThemeData::GetTheme(eUXWindowFrame);
+ }
+ return nullptr;
+}
+
+int32_t
+nsNativeThemeWin::StandardGetState(nsIFrame* aFrame, uint8_t aWidgetType,
+ bool wantFocused)
+{
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ 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 && eventState.HasState(NS_EVENT_STATE_FOCUS))
+ return TS_FOCUSED;
+
+ return TS_NORMAL;
+}
+
+bool
+nsNativeThemeWin::IsMenuActive(nsIFrame *aFrame, uint8_t aWidgetType)
+{
+ 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, uint8_t aWidgetType,
+ int32_t& aPart, int32_t& aState)
+{
+ if (!IsVistaOrLater()) {
+ // See GetTheme
+ if (aWidgetType == NS_THEME_MENULIST)
+ aWidgetType = NS_THEME_TEXTFIELD;
+ }
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ 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, aWidgetType, 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 NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO: {
+ bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
+ aPart = isCheckbox ? BP_CHECKBOX : BP_RADIO;
+
+ enum InputState {
+ UNCHECKED = 0, CHECKED, INDETERMINATE
+ };
+ InputState inputState = UNCHECKED;
+ bool isXULCheckboxRadio = false;
+
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ } else {
+ if (GetCheckedOrSelected(aFrame, !isCheckbox)) {
+ inputState = CHECKED;
+ } if (isCheckbox && GetIndeterminate(aFrame)) {
+ inputState = INDETERMINATE;
+ }
+
+ EventStates eventState =
+ GetContentState(isXULCheckboxRadio ? aFrame->GetParent() : aFrame,
+ aWidgetType);
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ } else {
+ aState = StandardGetState(aFrame, aWidgetType, false);
+ }
+ }
+
+ // 4 unchecked states, 4 checked states, 4 indeterminate states.
+ aState += inputState * 4;
+ return NS_OK;
+ }
+ case NS_THEME_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 NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE: {
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ if (IsVistaOrLater()) {
+ /* 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_FOCUS))
+ aState = TFS_EDITBORDER_FOCUSED;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TFS_EDITBORDER_HOVER;
+ else
+ aState = TFS_EDITBORDER_NORMAL;
+ }
+ } else {
+ aPart = TFP_TEXTFIELD;
+
+ if (!aFrame)
+ aState = TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState = TS_DISABLED;
+ else if (IsReadOnly(aFrame))
+ aState = TFS_READONLY;
+ else
+ aState = StandardGetState(aFrame, aWidgetType, true);
+ }
+
+ return NS_OK;
+ }
+ case NS_THEME_FOCUS_OUTLINE: {
+ if (IsVistaOrLater()) {
+ // XXX the EDITBORDER values don't respect DTBG_OMITCONTENT
+ aPart = TFP_TEXTFIELD; //TFP_EDITBORDER_NOSCROLL;
+ aState = TS_FOCUSED; //TFS_EDITBORDER_FOCUSED;
+ } else {
+ aPart = TFP_TEXTFIELD;
+ aState = TS_FOCUSED;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_TOOLTIP: {
+ aPart = TTP_STANDARD;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL: {
+ // Note IsVerticalProgress only tests for orient css attrribute,
+ // NS_THEME_PROGRESSBAR_VERTICAL is dedicated to -moz-appearance:
+ // progressbar-vertical.
+ bool vertical = IsVerticalProgress(aFrame) ||
+ aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL;
+ aPart = vertical ? PP_BARVERT : PP_BAR;
+ aState = PBBS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL: {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
+ IsVerticalProgress(parentFrame)) {
+ aPart = IsVistaOrLater() ?
+ PP_FILLVERT : PP_CHUNKVERT;
+ } else {
+ aPart = IsVistaOrLater() ?
+ PP_FILL : PP_CHUNK;
+ }
+
+ aState = PBBVS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TOOLBARBUTTON: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ 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 NS_THEME_SEPARATOR: {
+ aPart = TP_SEPARATOR;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT: {
+ aPart = SP_BUTTON;
+ aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP)*4;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (!aFrame)
+ aState += TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState += TS_DISABLED;
+ else {
+ nsIFrame *parent = aFrame->GetParent();
+ EventStates parentState =
+ GetContentState(parent, parent->StyleDisplay()->mAppearance);
+ 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 (IsVistaOrLater() &&
+ parentState.HasState(NS_EVENT_STATE_HOVER))
+ aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP) + SP_BUTTON_IMPLICIT_HOVER_BASE;
+ else
+ aState += TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL: {
+ aPart = (aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL) ?
+ SP_TRACKSTARTHOR : SP_TRACKSTARTVERT;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL: {
+ aPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) ?
+ SP_THUMBHOR : SP_THUMBVERT;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ 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 NS_THEME_RANGE:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL: {
+ if (aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ (aWidgetType == NS_THEME_RANGE &&
+ IsRangeHorizontal(aFrame))) {
+ aPart = TKP_TRACK;
+ aState = TRS_NORMAL;
+ } else {
+ aPart = TKP_TRACKVERT;
+ aState = TRVS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL: {
+ if (aWidgetType == NS_THEME_RANGE_THUMB) {
+ if (IsRangeHorizontal(aFrame)) {
+ aPart = TKP_THUMBBOTTOM;
+ } else {
+ aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT;
+ }
+ } else {
+ aPart = (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL) ?
+ TKP_THUMB : TKP_THUMBVERT;
+ }
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ 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_FOCUS))
+ aState = TKP_FOCUSED;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TS_HOVER;
+ else
+ aState = TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON: {
+ aPart = (aWidgetType == NS_THEME_SPINNER_UPBUTTON) ?
+ SPNP_UP : SPNP_DOWN;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (!aFrame)
+ aState = TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState = TS_DISABLED;
+ else
+ aState = StandardGetState(aFrame, aWidgetType, false);
+ return NS_OK;
+ }
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_WIN_MEDIA_TOOLBOX:
+ case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
+ case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL: {
+ aState = 0;
+ if (IsVistaOrLater()) {
+ // On vista, they have a part
+ aPart = RP_BACKGROUND;
+ } else {
+ // Otherwise, they don't. (But I bet
+ // RP_BACKGROUND would work here, too);
+ aPart = 0;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_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 NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_RESIZER: {
+ aPart = (aWidgetType - NS_THEME_STATUSBARPANEL) + 1;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_LISTBOX: {
+ aPart = TREEVIEW_BODY;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TABPANELS: {
+ aPart = TABP_PANELS;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TABPANEL: {
+ aPart = TABP_PANEL;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TAB: {
+ aPart = TABP_TAB;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ 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, aWidgetType, true);
+
+ return NS_OK;
+ }
+ case NS_THEME_TREEHEADERSORTARROW: {
+ // XXX Probably will never work due to a bug in the Luna theme.
+ aPart = 4;
+ aState = 1;
+ return NS_OK;
+ }
+ case NS_THEME_TREEHEADERCELL: {
+ aPart = 1;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ aState = StandardGetState(aFrame, aWidgetType, true);
+
+ return NS_OK;
+ }
+ case NS_THEME_MENULIST: {
+ nsIContent* content = aFrame->GetContent();
+ bool isHTML = content && content->IsHTMLElement();
+ bool useDropBorder = isHTML || IsMenuListEditable(aFrame);
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ /* 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_FOCUS) || 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 NS_THEME_MENULIST_BUTTON: {
+ bool isHTML = IsHTMLContent(aFrame);
+ nsIFrame* parentFrame = aFrame->GetParent();
+ bool isMenulist = !isHTML && parentFrame->GetType() == nsGkAtoms::menuFrame;
+ bool isOpen = false;
+
+ // HTML select and XUL menulist dropdown buttons get state from the parent.
+ if (isHTML || isMenulist)
+ aFrame = parentFrame;
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ aPart = IsVistaOrLater() ?
+ CBP_DROPMARKER_VISTA : CBP_DROPMARKER;
+
+ // 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, NS_THEME_MENULIST))
+ aPart = CBP_DROPMARKER;
+
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+
+ if (isHTML) {
+ nsIComboboxControlFrame* ccf = do_QueryFrame(aFrame);
+ isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup());
+ }
+ else
+ isOpen = IsOpenButton(aFrame);
+
+ if (IsVistaOrLater()) {
+ if (isHTML || IsMenuListEditable(aFrame)) {
+ 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 {
+ /* On Vista, 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 NS_THEME_MENUPOPUP: {
+ aPart = MENU_POPUPBACKGROUND;
+ aState = MB_ACTIVE;
+ return NS_OK;
+ }
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM: {
+ bool isTopLevel = false;
+ bool isOpen = false;
+ bool isHover = false;
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ isTopLevel = IsTopLevelMenu(aFrame);
+
+ if (menuFrame)
+ isOpen = menuFrame->IsOpen();
+
+ isHover = IsMenuActive(aFrame, aWidgetType);
+
+ 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 NS_THEME_MENUSEPARATOR:
+ aPart = MENU_POPUPSEPARATOR;
+ aState = 0;
+ return NS_OK;
+ case NS_THEME_MENUARROW:
+ {
+ aPart = MENU_POPUPSUBMENU;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ aState = IsDisabled(aFrame, eventState) ? MSM_DISABLED : MSM_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ {
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ aPart = MENU_POPUPCHECK;
+ aState = MC_CHECKMARKNORMAL;
+
+ // Radio states are offset by 2
+ if (aWidgetType == NS_THEME_MENURADIO)
+ aState += 2;
+
+ // the disabled states are offset by 1
+ if (IsDisabled(aFrame, eventState))
+ aState += 1;
+
+ return NS_OK;
+ }
+ case NS_THEME_MENUITEMTEXT:
+ case NS_THEME_MENUIMAGE:
+ aPart = -1;
+ aState = 0;
+ return NS_OK;
+
+ case NS_THEME_WINDOW_TITLEBAR:
+ aPart = mozilla::widget::themeconst::WP_CAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ aPart = mozilla::widget::themeconst::WP_FRAMELEFT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ aPart = mozilla::widget::themeconst::WP_FRAMERIGHT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ aPart = mozilla::widget::themeconst::WP_CLOSEBUTTON;
+ aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ aPart = mozilla::widget::themeconst::WP_MINBUTTON;
+ aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ aPart = mozilla::widget::themeconst::WP_MAXBUTTON;
+ aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ aPart = mozilla::widget::themeconst::WP_RESTOREBUTTON;
+ aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ aPart = -1;
+ aState = 0;
+ return NS_OK;
+ }
+
+ aPart = 0;
+ aState = 0;
+ return NS_ERROR_FAILURE;
+}
+
+static bool
+AssumeThemePartAndStateAreTransparent(int32_t aPart, int32_t aState)
+{
+ if (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()) {
+ nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget();
+ if (rootWidget) {
+ double systemScale = WinUtils::SystemScaleFactor();
+ return rootWidget->GetDefaultScale().scale / systemScale;
+ }
+ }
+ return 1.0;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ HANDLE theme = GetTheme(aWidgetType);
+ if (!theme)
+ return ClassicDrawWidgetBackground(aContext, aFrame, aWidgetType, aRect, aDirtyRect);
+
+ // ^^ without the right sdk, assume xp theming and fall through.
+ if (nsUXThemeData::CheckForCompositor()) {
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ // Nothing to draw, these areas are glass. Minimum dimensions
+ // should be set, so xul content should be layed out correctly.
+ return NS_OK;
+ break;
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ // 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;
+ break;
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ // Nothing to draw, this is the glass background.
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ // We handle these through nsIWidget::UpdateThemeGeometries
+ return NS_OK;
+ break;
+ }
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (AssumeThemePartAndStateAreTransparent(part, state)) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxContext> ctx = aContext->ThebesContext();
+ gfxContextMatrixAutoSaveRestore save(ctx);
+
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ ctx->SetMatrix(ctx->CurrentMatrix().Scale(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.ScaleInverse(p2a * themeScale);
+ dr.ScaleInverse(p2a * themeScale);
+
+ gfxWindowsNativeDrawing nativeDrawing(ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType));
+
+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 (aWidgetType == NS_THEME_WINDOW_TITLEBAR) {
+ // 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 (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) {
+ // 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 (aWidgetType == NS_THEME_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 (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) {
+ 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 (aWidgetType == NS_THEME_RANGE ||
+ aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ aWidgetType == NS_THEME_SCALE_VERTICAL) {
+ 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 (aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ (aWidgetType == NS_THEME_RANGE && 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 (aWidgetType == NS_THEME_MENUCHECKBOX || aWidgetType == NS_THEME_MENURADIO)
+ {
+ bool isChecked = false;
+ isChecked = CheckBooleanAttr(aFrame, nsGkAtoms::checked);
+
+ if (isChecked)
+ {
+ int bgState = MCB_NORMAL;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ // 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 (aWidgetType == NS_THEME_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 (aWidgetType == NS_THEME_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 (aWidgetType == NS_THEME_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 NS_THEME_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 (aWidgetType == NS_THEME_RESIZER ||
+ aWidgetType == NS_THEME_MENULIST_BUTTON)
+ {
+ DrawThemeBGRTLAware(theme, hdc, part, state,
+ &widgetRect, &clipRect, IsFrameRTL(aFrame));
+ }
+ else if (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) {
+ 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 (aWidgetType == NS_THEME_PROGRESSBAR ||
+ aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL) {
+ // 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 (aWidgetType == NS_THEME_PROGRESSCHUNK ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL) {
+ DrawThemedProgressMeter(aFrame, aWidgetType, theme, hdc, part, state,
+ &widgetRect, &clipRect, p2a);
+ }
+ else if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
+ // Inflate 'widgetRect' with the focus outline size.
+ nsIntMargin border;
+ if (NS_SUCCEEDED(GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, aWidgetType, &border))) {
+ 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 and scale elements
+ // XXX it'd be nice to draw these outside of the frame
+ if (aWidgetType == NS_THEME_RANGE ||
+ aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ aWidgetType == NS_THEME_SCALE_VERTICAL) {
+ EventStates contentState = GetContentState(aFrame, aWidgetType);
+
+ if (contentState.HasState(NS_EVENT_STATE_FOCUS)) {
+ 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 (aWidgetType == NS_THEME_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 NS_THEME_TOOLBOX.
+ theme = GetTheme(NS_THEME_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 (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL ||
+ aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL)
+ {
+ // Draw the decorative gripper for the scrollbar thumb button, if it fits
+
+ SIZE gripSize;
+ MARGINS thumbMgns;
+ int gripPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) ?
+ 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(nsIntMargin* 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);
+ }
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ HANDLE theme = GetTheme(aWidgetType);
+ nsresult rv = NS_OK;
+ if (!theme) {
+ rv = ClassicGetWidgetBorder(aContext, aFrame, aWidgetType, aResult);
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+
+ aResult->top = aResult->bottom = aResult->left = aResult->right = 0;
+
+ if (!WidgetIsContainer(aWidgetType) ||
+ aWidgetType == NS_THEME_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX ||
+ aWidgetType == NS_THEME_STATUSBAR ||
+ aWidgetType == NS_THEME_RESIZER || aWidgetType == NS_THEME_TABPANEL ||
+ aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL ||
+ aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ||
+ aWidgetType == NS_THEME_MENUITEM || aWidgetType == NS_THEME_CHECKMENUITEM ||
+ aWidgetType == NS_THEME_RADIOMENUITEM || aWidgetType == NS_THEME_MENUPOPUP ||
+ aWidgetType == NS_THEME_MENUIMAGE || aWidgetType == NS_THEME_MENUITEMTEXT ||
+ aWidgetType == NS_THEME_SEPARATOR ||
+ aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
+ aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED ||
+ aWidgetType == NS_THEME_WIN_GLASS || aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS)
+ return NS_OK; // Don't worry about it.
+
+ int32_t part, state;
+ rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (aWidgetType == NS_THEME_TOOLBAR) {
+ // make space for the separator line above all toolbars but the first
+ if (state == 0)
+ aResult->top = TB_SEPARATOR_HEIGHT;
+ return NS_OK;
+ }
+
+ // 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(theme, nullptr, part, state, &outerRect, &contentRect);
+
+ if (FAILED(res))
+ return NS_ERROR_FAILURE;
+
+ // Now compute the delta in each direction and place it in our
+ // nsIntMargin struct.
+ aResult->top = contentRect.top - outerRect.top;
+ aResult->bottom = outerRect.bottom - contentRect.bottom;
+ aResult->left = contentRect.left - outerRect.left;
+ aResult->right = outerRect.right - contentRect.right;
+
+ // Remove the edges for tabs that are before or after the selected tab,
+ if (aWidgetType == NS_THEME_TAB) {
+ if (IsLeftToSelectedTab(aFrame))
+ // Remove the right edge, since we won't be drawing it.
+ aResult->right = 0;
+ else if (IsRightToSelectedTab(aFrame))
+ // Remove the left edge, since we won't be drawing it.
+ aResult->left = 0;
+ }
+
+ if (aFrame && (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE)) {
+ 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.
+ aResult->top++;
+ aResult->left++;
+ aResult->bottom++;
+ aResult->right++;
+ }
+ }
+
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+}
+
+bool
+nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ switch (aWidgetType) {
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ }
+
+ bool ok = true;
+
+ if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) {
+ aResult->SizeTo(0, 0, 0, 0);
+
+ // aero glass doesn't display custom buttons
+ if (nsUXThemeData::CheckForCompositor())
+ return true;
+
+ // button padding for standard windows
+ if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX) {
+ aResult->top = GetSystemMetrics(SM_CXFRAME);
+ }
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ // Content padding
+ if (aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
+ aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) {
+ 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 (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED)
+ aResult->top = GetSystemMetrics(SM_CXFRAME)
+ + GetSystemMetrics(SM_CXPADDEDBORDER);
+ return ok;
+ }
+
+ HANDLE theme = GetTheme(aWidgetType);
+ if (!theme) {
+ ok = ClassicGetWidgetPadding(aContext, aFrame, aWidgetType, aResult);
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ if (aWidgetType == NS_THEME_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 (IsVistaOrLater()) {
+ if (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
+ aWidgetType == NS_THEME_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 (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) {
+ aResult->top = aResult->bottom = 2;
+ aResult->left = aResult->right = 2;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ } else if (IsHTMLContent(aFrame) && aWidgetType == NS_THEME_MENULIST) {
+ /* 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 (aWidgetType)
+ {
+ case NS_THEME_MENUIMAGE:
+ right = 8;
+ left = 3;
+ break;
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ right = 8;
+ left = 0;
+ break;
+ case NS_THEME_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 NS_THEME_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,
+ uint8_t aWidgetType,
+ 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.
+ */
+#if 0
+ if (IsVistaOrLater()) {
+ /* 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 (aWidgetType == NS_THEME_MENULIST_BUTTON &&
+ IsHTMLContent(aFrame) &&
+ !IsWidgetStyled(aFrame->GetParent()->PresContext(),
+ aFrame->GetParent(),
+ NS_THEME_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 (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
+ nsIntMargin border;
+ nsresult rv = GetWidgetBorder(aContext, aFrame, aWidgetType, &border);
+ if (NS_SUCCEEDED(rv)) {
+ 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,
+ uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult, bool* aIsOverridable)
+{
+ aResult->width = aResult->height = 0;
+ *aIsOverridable = true;
+ nsresult rv = NS_OK;
+
+ HANDLE theme = GetTheme(aWidgetType);
+ if (!theme) {
+ rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable);
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ switch (aWidgetType) {
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_WIN_MEDIA_TOOLBOX:
+ case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
+ case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_MENUITEMTEXT:
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ return NS_OK; // Don't worry about it.
+ }
+
+ if (aWidgetType == NS_THEME_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 (aWidgetType) {
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_MENULIST_BUTTON: {
+ rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable);
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ if(!IsTopLevelMenu(aFrame))
+ {
+ SIZE gutterSize(GetGutterSize(theme, nullptr));
+ aResult->width = gutterSize.cx;
+ aResult->height = gutterSize.cy;
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ break;
+
+ case NS_THEME_MENUIMAGE:
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ {
+ SIZE boxSize(GetGutterSize(theme, nullptr));
+ aResult->width = boxSize.cx+2;
+ aResult->height = boxSize.cy;
+ *aIsOverridable = false;
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+
+ case NS_THEME_MENUITEMTEXT:
+ return NS_OK;
+
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ // 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 NS_THEME_RESIZER:
+ *aIsOverridable = false;
+ break;
+
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ {
+ *aIsOverridable = false;
+ // on Vista, GetThemePartAndState returns odd values for
+ // scale thumbs, so use a hardcoded size instead.
+ if (IsVistaOrLater()) {
+ if (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL ||
+ (aWidgetType == NS_THEME_RANGE_THUMB && IsRangeHorizontal(aFrame))) {
+ aResult->width = 12;
+ aResult->height = 20;
+ }
+ else {
+ aResult->width = 20;
+ aResult->height = 12;
+ }
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR:
+ {
+ if (nsLookAndFeel::GetInt(
+ nsLookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
+ aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL),
+ ::GetSystemMetrics(SM_CYVSCROLL));
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ break;
+ }
+
+ case NS_THEME_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 NS_THEME_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 NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ // 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::sCommandButtons[CMDBUTTONIDX_RESTORE].cx;
+ aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_RESTORE].cy;
+ // For XP, subtract 4 from system metrics dimensions.
+ if (!IsVistaOrLater()) {
+ aResult->width -= 4;
+ aResult->height -= 4;
+ }
+ AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE);
+ *aIsOverridable = false;
+ return rv;
+
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_MINIMIZE].cx;
+ aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_MINIMIZE].cy;
+ if (!IsVistaOrLater()) {
+ aResult->width -= 4;
+ aResult->height -= 4;
+ }
+ AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE);
+ *aIsOverridable = false;
+ return rv;
+
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_CLOSE].cx;
+ aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_CLOSE].cy;
+ if (!IsVistaOrLater()) {
+ aResult->width -= 4;
+ aResult->height -= 4;
+ }
+ AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE);
+ *aIsOverridable = false;
+ return rv;
+
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ 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 NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ if (nsUXThemeData::CheckForCompositor()) {
+ aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cx;
+ aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cy
+ - GetSystemMetrics(SM_CYFRAME)
+ - GetSystemMetrics(SM_CXPADDEDBORDER);
+ if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) {
+ aResult->width += 1;
+ aResult->height -= 2;
+ }
+ *aIsOverridable = false;
+ return rv;
+ }
+ break;
+
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ aResult->width = GetSystemMetrics(SM_CXFRAME);
+ aResult->height = GetSystemMetrics(SM_CYFRAME);
+ *aIsOverridable = false;
+ return rv;
+ }
+
+ int32_t part, state;
+ rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
+ if (NS_FAILED(rv))
+ return rv;
+
+ HDC hdc = ::GetDC(NULL);
+ if (!hdc)
+ return NS_ERROR_FAILURE;
+
+ SIZE sz;
+ GetThemePartSize(theme, hdc, part, state, nullptr, sizeReq, &sz);
+ aResult->width = sz.cx;
+ aResult->height = sz.cy;
+
+ switch(aWidgetType) {
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ aResult->width++;
+ aResult->height = aResult->height / 2 + 1;
+ break;
+
+ case NS_THEME_MENUSEPARATOR:
+ {
+ SIZE gutterSize(GetGutterSize(theme, hdc));
+ aResult->width += gutterSize.cx;
+ break;
+ }
+
+ case NS_THEME_MENUARROW:
+ {
+ // Use the width of the arrow glyph as padding. See the drawing
+ // code for details.
+ aResult->width *= 2;
+ break;
+ }
+ }
+
+ ::ReleaseDC(nullptr, hdc);
+
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue)
+{
+ // Some widget types just never change state.
+ if (aWidgetType == NS_THEME_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX ||
+ aWidgetType == NS_THEME_TOOLBAR ||
+ aWidgetType == NS_THEME_STATUSBAR || aWidgetType == NS_THEME_STATUSBARPANEL ||
+ aWidgetType == NS_THEME_RESIZERPANEL ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
+ aWidgetType == NS_THEME_PROGRESSBAR ||
+ aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL ||
+ aWidgetType == NS_THEME_TOOLTIP ||
+ aWidgetType == NS_THEME_TABPANELS ||
+ aWidgetType == NS_THEME_TABPANEL ||
+ aWidgetType == NS_THEME_SEPARATOR ||
+ aWidgetType == NS_THEME_WIN_GLASS ||
+ aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS) {
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ if (aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
+ aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED ||
+ aWidgetType == NS_THEME_WINDOW_FRAME_LEFT ||
+ aWidgetType == NS_THEME_WINDOW_FRAME_RIGHT ||
+ aWidgetType == NS_THEME_WINDOW_FRAME_BOTTOM ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ // On Vista, the scrollbar buttons need to change state when the track has/doesn't have hover
+ if (!IsVistaOrLater() &&
+ (aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ||
+ aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL)) {
+ *aShouldRepaint = false;
+ 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 (IsVistaOrLater() &&
+ (aWidgetType == NS_THEME_MENULIST || aWidgetType == NS_THEME_MENULIST_BUTTON) &&
+ 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();
+ return NS_OK;
+}
+
+bool
+nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ // XXXdwh We can go even further and call the API to ask if support exists for
+ // specific widgets.
+
+ if (aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
+ return false;
+
+ if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
+ return true;
+ }
+
+ HANDLE theme = nullptr;
+ if (aWidgetType == NS_THEME_CHECKBOX_CONTAINER)
+ theme = GetTheme(NS_THEME_CHECKBOX);
+ else if (aWidgetType == NS_THEME_RADIO_CONTAINER)
+ theme = GetTheme(NS_THEME_RADIO);
+ else
+ theme = GetTheme(aWidgetType);
+
+ if (theme && aWidgetType == NS_THEME_RESIZER)
+ return true;
+
+ if ((theme) || (!theme && ClassicThemeSupportsWidget(aFrame, aWidgetType)))
+ // turn off theming for some HTML widgets styled by the page
+ return (!IsWidgetStyled(aPresContext, aFrame, aWidgetType));
+
+ return false;
+}
+
+bool
+nsNativeThemeWin::WidgetIsContainer(uint8_t aWidgetType)
+{
+ // XXXdwh At some point flesh all of this out.
+ if (aWidgetType == NS_THEME_MENULIST_BUTTON ||
+ aWidgetType == NS_THEME_RADIO ||
+ aWidgetType == NS_THEME_CHECKBOX)
+ return false;
+ return true;
+}
+
+bool
+nsNativeThemeWin::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
+{
+ return false;
+}
+
+bool
+nsNativeThemeWin::ThemeNeedsComboboxDropmarker()
+{
+ return true;
+}
+
+bool
+nsNativeThemeWin::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+nsITheme::ThemeGeometryType
+nsNativeThemeWin::ThemeGeometryTypeForWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ return eThemeGeometryTypeWindowButtons;
+ default:
+ return eThemeGeometryTypeUnknown;
+ }
+}
+
+bool
+nsNativeThemeWin::ShouldHideScrollbars()
+{
+ return WinUtils::ShouldHideScrollbars();
+}
+
+nsITheme::Transparency
+nsNativeThemeWin::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_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 eOpaque;
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_RANGE:
+ return eTransparent;
+ }
+
+ HANDLE theme = GetTheme(aWidgetType);
+ // 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 (aWidgetType == NS_THEME_MENUPOPUP || aWidgetType == NS_THEME_TOOLTIP) {
+ return eOpaque;
+ }
+ return eUnknownTransparency;
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aWidgetType, 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,
+ uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_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->GetType() != nsGkAtoms::scrollFrame);
+ }
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ // Classic non-flat menus are handled almost entirely through CSS.
+ if (!nsUXThemeData::sFlatMenus)
+ return false;
+ case NS_THEME_BUTTON:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_MENULIST:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_MENUITEMTEXT:
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ return true;
+ }
+ return false;
+}
+
+nsresult
+nsNativeThemeWin::ClassicGetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ switch (aWidgetType) {
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_BUTTON:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 2;
+ break;
+ case NS_THEME_STATUSBAR:
+ (*aResult).bottom = (*aResult).left = (*aResult).right = 0;
+ (*aResult).top = 2;
+ break;
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_TAB:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_FOCUS_OUTLINE:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 2;
+ break;
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL: {
+ (*aResult).top = 1;
+ (*aResult).left = 1;
+ (*aResult).bottom = 1;
+ (*aResult).right = aFrame->GetNextSibling() ? 3 : 1;
+ break;
+ }
+ case NS_THEME_TOOLTIP:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1;
+ break;
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1;
+ break;
+ case NS_THEME_MENUBAR:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 0;
+ break;
+ case NS_THEME_MENUPOPUP:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 3;
+ break;
+ default:
+ (*aResult).top = (*aResult).bottom = (*aResult).left = (*aResult).right = 0;
+ break;
+ }
+ return NS_OK;
+}
+
+bool
+nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM: {
+ int32_t part, state;
+ bool focused;
+
+ if (NS_FAILED(ClassicGetThemePartAndState(aFrame, aWidgetType, part, state, focused)))
+ return false;
+
+ if (part == 1) { // top-level menu
+ if (nsUXThemeData::sFlatMenus || !(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 NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1;
+ return true;
+ default:
+ return false;
+ }
+}
+
+nsresult
+nsNativeThemeWin::ClassicGetMinimumWidgetSize(nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable)
+{
+ (*aResult).width = (*aResult).height = 0;
+ *aIsOverridable = true;
+ switch (aWidgetType) {
+ case NS_THEME_RADIO:
+ case NS_THEME_CHECKBOX:
+ (*aResult).width = (*aResult).height = 13;
+ break;
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUARROW:
+ (*aResult).width = ::GetSystemMetrics(SM_CXMENUCHECK);
+ (*aResult).height = ::GetSystemMetrics(SM_CYMENUCHECK);
+ break;
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ (*aResult).height = 8; // No good metrics available for this
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ (*aResult).height = ::GetSystemMetrics(SM_CYVSCROLL);
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ (*aResult).width = ::GetSystemMetrics(SM_CXHSCROLL);
+ (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL);
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ // 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 NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ {
+ aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL),
+ ::GetSystemMetrics(SM_CYVSCROLL));
+ break;
+ }
+ case NS_THEME_RANGE_THUMB: {
+ if (IsRangeHorizontal(aFrame)) {
+ (*aResult).width = 12;
+ (*aResult).height = 20;
+ } else {
+ (*aResult).width = 20;
+ (*aResult).height = 12;
+ }
+ *aIsOverridable = false;
+ break;
+ }
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ (*aResult).width = 12;
+ (*aResult).height = 20;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ (*aResult).width = 20;
+ (*aResult).height = 12;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_MENULIST_BUTTON:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ break;
+ case NS_THEME_MENULIST:
+ case NS_THEME_BUTTON:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ // no minimum widget size
+ break;
+ case NS_THEME_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 NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ (*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(aWidgetType))
+ (*aResult).height >>= 1;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ (*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(aWidgetType))
+ (*aResult).width >>= 1;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB) << 1;
+ break;
+ }
+ case NS_THEME_MENUSEPARATOR:
+ {
+ aResult->width = 0;
+ aResult->height = 10;
+ break;
+ }
+
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_TITLEBAR:
+ aResult->height = GetSystemMetrics(SM_CYCAPTION);
+ aResult->height += GetSystemMetrics(SM_CYFRAME);
+ aResult->width = 0;
+ break;
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ aResult->width = GetSystemMetrics(SM_CXFRAME);
+ aResult->height = 0;
+ break;
+
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ aResult->height = GetSystemMetrics(SM_CYFRAME);
+ aResult->width = 0;
+ break;
+
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ 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 (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) {
+ AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
+ AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) {
+ AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE);
+ }
+ break;
+
+ default:
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+
+nsresult nsNativeThemeWin::ClassicGetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType,
+ int32_t& aPart, int32_t& aState, bool& aFocused)
+{
+ aFocused = false;
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON: {
+ EventStates contentState;
+
+ aPart = DFC_BUTTON;
+ aState = DFCS_BUTTONPUSH;
+ aFocused = false;
+
+ contentState = GetContentState(aFrame, aWidgetType);
+ 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 nsStyleUserInterface *uiData = aFrame->StyleUserInterface();
+ // The down state is flat if the button is focusable
+ if (uiData->mUserFocus == StyleUserFocus::Normal) {
+ if (!aFrame->GetContent()->IsHTMLElement())
+ aState |= DFCS_FLAT;
+
+ aFocused = true;
+ }
+ }
+ if (contentState.HasState(NS_EVENT_STATE_FOCUS) ||
+ (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) {
+ aFocused = true;
+ }
+
+ }
+
+ return NS_OK;
+ }
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO: {
+ EventStates contentState;
+ aFocused = false;
+
+ aPart = DFC_BUTTON;
+ aState = 0;
+ nsIContent* content = aFrame->GetContent();
+ bool isCheckbox = (aWidgetType == NS_THEME_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, aWidgetType);
+ if (!content->IsXULElement() &&
+ contentState.HasState(NS_EVENT_STATE_FOCUS)) {
+ 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 NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM: {
+ bool isTopLevel = false;
+ bool isOpen = false;
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ // 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, aWidgetType))
+ aState |= DFCS_HOT;
+
+ return NS_OK;
+ }
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUARROW: {
+ aState = 0;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ if (IsDisabled(aFrame, eventState))
+ aState |= DFCS_INACTIVE;
+ if (IsMenuActive(aFrame, aWidgetType))
+ aState |= DFCS_HOT;
+
+ if (aWidgetType == NS_THEME_MENUCHECKBOX || aWidgetType == NS_THEME_MENURADIO) {
+ if (IsCheckedButton(aFrame))
+ aState |= DFCS_CHECKED;
+ } else if (IsFrameRTL(aFrame)) {
+ aState |= DFCS_RTL;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_FOCUS_OUTLINE:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_GROUPBOX:
+ // these don't use DrawFrameControl
+ return NS_OK;
+ case NS_THEME_MENULIST_BUTTON: {
+
+ aPart = DFC_SCROLL;
+ aState = DFCS_SCROLLCOMBOBOX;
+
+ nsIFrame* parentFrame = aFrame->GetParent();
+ bool isHTML = IsHTMLContent(aFrame);
+ bool isMenulist = !isHTML && parentFrame->GetType() == nsGkAtoms::menuFrame;
+ bool isOpen = false;
+
+ // HTML select and XUL menulist dropdown buttons get state from the parent.
+ if (isHTML || isMenulist)
+ aFrame = parentFrame;
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ if (IsDisabled(aFrame, eventState)) {
+ aState |= DFCS_INACTIVE;
+ return NS_OK;
+ }
+
+ if (isHTML) {
+ nsIComboboxControlFrame* 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 NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT: {
+ EventStates contentState = GetContentState(aFrame, aWidgetType);
+
+ aPart = DFC_SCROLL;
+ switch (aWidgetType) {
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ aState = DFCS_SCROLLUP;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ aState = DFCS_SCROLLDOWN;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ aState = DFCS_SCROLLLEFT;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ aState = DFCS_SCROLLRIGHT;
+ 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 NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON: {
+ EventStates contentState = GetContentState(aFrame, aWidgetType);
+
+ aPart = DFC_SCROLL;
+ switch (aWidgetType) {
+ case NS_THEME_SPINNER_UPBUTTON:
+ aState = DFCS_SCROLLUP;
+ break;
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ aState = DFCS_SCROLLDOWN;
+ 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 NS_THEME_RESIZER:
+ aPart = DFC_SCROLL;
+ aState = (IsFrameRTL(aFrame)) ?
+ DFCS_SCROLLSIZEGRIPRIGHT : DFCS_SCROLLSIZEGRIP;
+ return NS_OK;
+ case NS_THEME_MENUSEPARATOR:
+ aPart = 0;
+ aState = 0;
+ return NS_OK;
+ case NS_THEME_WINDOW_TITLEBAR:
+ aPart = mozilla::widget::themeconst::WP_CAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ aPart = mozilla::widget::themeconst::WP_FRAMELEFT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ aPart = mozilla::widget::themeconst::WP_FRAMERIGHT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONCLOSE |
+ GetClassicWindowFrameButtonState(GetContentState(aFrame,
+ aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONMIN |
+ GetClassicWindowFrameButtonState(GetContentState(aFrame,
+ aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONMAX |
+ GetClassicWindowFrameButtonState(GetContentState(aFrame,
+ aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONRESTORE |
+ GetClassicWindowFrameButtonState(GetContentState(aFrame,
+ aWidgetType));
+ return NS_OK;
+ }
+ 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(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ int32_t part, state;
+ bool focused;
+ nsresult rv;
+ rv = ClassicGetThemePartAndState(aFrame, aWidgetType, 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.ScaleInverse(p2a);
+ dr.ScaleInverse(p2a);
+
+ RefPtr<gfxContext> ctx = aContext->ThebesContext();
+
+ gfxWindowsNativeDrawing nativeDrawing(ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType));
+
+RENDER_AGAIN:
+
+ HDC hdc = nativeDrawing.BeginNativeDrawing();
+ if (!hdc)
+ return NS_ERROR_FAILURE;
+
+ nativeDrawing.TransformToNativeRect(tr, widgetRect);
+
+ rv = NS_OK;
+ switch (aWidgetType) {
+ // Draw button
+ case NS_THEME_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 NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_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 NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD: {
+ // Draw inset edge
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ // 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 NS_THEME_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 NS_THEME_TOOLTIP:
+ ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_WINDOWFRAME));
+ InflateRect(&widgetRect, -1, -1);
+ ::FillRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_INFOBK));
+
+ break;
+ case NS_THEME_GROUPBOX:
+ ::DrawEdge(hdc, &widgetRect, EDGE_ETCHED, BF_RECT | BF_ADJUST);
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1));
+ break;
+ // Draw 3D face background controls
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ // Draw 3D border
+ ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
+ InflateRect(&widgetRect, -1, -1);
+ // fall through
+ case NS_THEME_TABPANEL:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_RESIZERPANEL: {
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1));
+
+ break;
+ }
+ // Draw 3D inset statusbar panel
+ case NS_THEME_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 NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_MIDDLE);
+
+ break;
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL: {
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ ::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 NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL: {
+
+ // 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;
+ }
+ // Draw scale track background
+ case NS_THEME_RANGE:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALE_HORIZONTAL: {
+ 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 (aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ (aWidgetType == NS_THEME_RANGE && 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 NS_THEME_PROGRESSCHUNK_VERTICAL:
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_HIGHLIGHT+1));
+ break;
+
+ case NS_THEME_PROGRESSCHUNK: {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ EventStates eventStates = GetContentState(stateFrame, aWidgetType);
+
+ bool indeterminate = IsIndeterminateProgress(stateFrame, eventStates);
+ bool vertical = IsVerticalProgress(stateFrame) ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL;
+
+ 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 NS_THEME_TAB: {
+ DrawTab(hdc, widgetRect,
+ IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP,
+ IsSelectedTab(aFrame),
+ !IsRightToSelectedTab(aFrame),
+ !IsLeftToSelectedTab(aFrame));
+
+ break;
+ }
+ case NS_THEME_TABPANELS:
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_SOFT | BF_MIDDLE |
+ BF_LEFT | BF_RIGHT | BF_BOTTOM);
+
+ break;
+ case NS_THEME_MENUBAR:
+ break;
+ case NS_THEME_MENUPOPUP:
+ NS_ASSERTION(nsUXThemeData::sFlatMenus, "Classic menus are styled entirely through CSS");
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_MENU+1));
+ ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_BTNSHADOW));
+ break;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ // part == 0 for normal items
+ // part == 1 for top-level menu items
+ if (nsUXThemeData::sFlatMenus) {
+ // 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 NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ if (!(state & DFCS_CHECKED))
+ break; // nothin' to do
+ case NS_THEME_MENUARROW: {
+ uint32_t color = COLOR_MENUTEXT;
+ if ((state & DFCS_INACTIVE))
+ color = COLOR_GRAYTEXT;
+ else if ((state & DFCS_HOT))
+ color = COLOR_HIGHLIGHTTEXT;
+
+ if (aWidgetType == NS_THEME_MENUCHECKBOX)
+ DrawMenuImage(hdc, widgetRect, DFCS_MENUCHECK, color);
+ else if (aWidgetType == NS_THEME_MENURADIO)
+ DrawMenuImage(hdc, widgetRect, DFCS_MENUBULLET, color);
+ else if (aWidgetType == NS_THEME_MENUARROW)
+ DrawMenuImage(hdc, widgetRect,
+ (state & DFCS_RTL) ? DFCS_MENUARROWRIGHT : DFCS_MENUARROW,
+ color);
+ break;
+ }
+ case NS_THEME_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 NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ {
+ 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 (aWidgetType == NS_THEME_WINDOW_TITLEBAR) {
+ // frame things up with a top raised border.
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_TOP);
+ }
+ break;
+ }
+
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_LEFT);
+ break;
+
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RIGHT);
+ break;
+
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_BOTTOM);
+ break;
+
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ {
+ if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) {
+ 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(uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_FOCUS_OUTLINE:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ return
+ gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+
+ // need to check these others
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_RESIZER:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ break;
+
+ // 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 NS_THEME_MENULIST_BUTTON:
+ // these are definitely no; they're all graphics that don't get scaled up
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUARROW:
+ return
+ gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+ }
+
+ return
+ gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+}
+
+///////////////////////////////////////////
+// Creation Routine
+///////////////////////////////////////////
+
+// from nsWindow.cpp
+extern bool gDisableNativeTheme;
+
+nsresult NS_NewNativeTheme(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ if (gDisableNativeTheme)
+ return NS_ERROR_NO_INTERFACE;
+
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsNativeThemeWin* theme = new nsNativeThemeWin();
+ if (!theme)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return theme->QueryInterface(aIID, aResult);
+}
diff --git a/widget/windows/nsNativeThemeWin.h b/widget/windows/nsNativeThemeWin.h
new file mode 100644
index 000000000..f20649444
--- /dev/null
+++ b/widget/windows/nsNativeThemeWin.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNativeThemeWin_h
+#define nsNativeThemeWin_h
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsNativeTheme.h"
+#include "gfxTypes.h"
+#include <windows.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(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+
+ NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) override;
+
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+
+ NS_IMETHOD ThemeChanged() override;
+
+ bool ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+
+ bool WidgetIsContainer(uint8_t aWidgetType) override;
+
+ bool ThemeDrawsFocusForWidget(uint8_t aWidgetType) override;
+
+ bool ThemeNeedsComboboxDropmarker() override;
+
+ virtual bool WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) override;
+
+ enum {
+ eThemeGeometryTypeWindowButtons = eThemeGeometryTypeUnknown + 1
+ };
+ virtual ThemeGeometryType ThemeGeometryTypeForWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+
+ virtual bool ShouldHideScrollbars() override;
+
+ nsNativeThemeWin();
+
+protected:
+ HANDLE GetTheme(uint8_t aWidgetType);
+ nsresult GetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType,
+ int32_t& aPart, int32_t& aState);
+ nsresult ClassicGetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType,
+ int32_t& aPart, int32_t& aState, bool& aFocused);
+ nsresult ClassicDrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aClipRect);
+ nsresult ClassicGetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult);
+ bool ClassicGetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult);
+ nsresult ClassicGetMinimumWidgetSize(nsIFrame* aFrame, uint8_t aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable);
+ bool ClassicThemeSupportsWidget(nsIFrame* aFrame, uint8_t aWidgetType);
+ void DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore, int32_t back,
+ HBRUSH defaultBack);
+ uint32_t GetWidgetNativeDrawingFlags(uint8_t aWidgetType);
+ int32_t StandardGetState(nsIFrame* aFrame, uint8_t aWidgetType, bool wantFocused);
+ bool IsMenuActive(nsIFrame* aFrame, uint8_t aWidgetType);
+ RECT CalculateProgressOverlayRect(nsIFrame* aFrame, RECT* aWidgetRect,
+ bool aIsVertical, bool aIsIndeterminate,
+ bool aIsClassic);
+ void DrawThemedProgressMeter(nsIFrame* aFrame, int aWidgetType,
+ HANDLE aTheme, HDC aHdc,
+ int aPart, int aState,
+ RECT* aWidgetRect, RECT* aClipRect,
+ gfxFloat aAppUnits);
+
+private:
+ TimeStamp mProgressDeterminateTimeStamp;
+ TimeStamp mProgressIndeterminateTimeStamp;
+};
+
+#endif
diff --git a/widget/windows/nsPrintOptionsWin.cpp b/widget/windows/nsPrintOptionsWin.cpp
new file mode 100644
index 000000000..97b15fa3f
--- /dev/null
+++ b/widget/windows/nsPrintOptionsWin.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsPrintOptionsWin.h"
+#include "nsPrintSettingsWin.h"
+#include "nsPrintDialogUtil.h"
+
+#include "nsGfxCIID.h"
+#include "nsIServiceManager.h"
+#include "nsIWebBrowserPrint.h"
+#include "nsWindowsHelpers.h"
+#include "ipc/IPCMessageUtils.h"
+
+const char kPrinterEnumeratorContractID[] = "@mozilla.org/gfx/printerenumerator;1";
+
+using namespace mozilla::embedding;
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsWin.h
+ * @update 6/21/00 dwc
+ */
+nsPrintOptionsWin::nsPrintOptionsWin()
+{
+
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsImpl.h
+ * @update 6/21/00 dwc
+ */
+nsPrintOptionsWin::~nsPrintOptionsWin()
+{
+}
+
+NS_IMETHODIMP
+nsPrintOptionsWin::SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ PrintData* data)
+{
+ nsresult rv = nsPrintOptions::SerializeToPrintData(aSettings, aWBP, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Windows wants this information for its print dialogs
+ if (aWBP) {
+ aWBP->GetIsFramesetDocument(&data->isFramesetDocument());
+ aWBP->GetIsFramesetFrameSelected(&data->isFramesetFrameSelected());
+ aWBP->GetIsIFrameSelected(&data->isIFrameSelected());
+ aWBP->GetIsRangeSelection(&data->isRangeSelection());
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aSettings);
+ if (!psWin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char16_t* deviceName;
+ char16_t* driverName;
+
+ psWin->GetDeviceName(&deviceName);
+ psWin->GetDriverName(&driverName);
+
+ data->deviceName().Assign(deviceName);
+ data->driverName().Assign(driverName);
+
+ free(deviceName);
+ free(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()) {
+ psWin->GetPrintableWidthInInches(&data->printableWidthInInches());
+ psWin->GetPrintableHeightInInches(&data->printableHeightInInches());
+
+ // 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::MAX_MESSAGE_SIZE) {
+ 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().SwapElements(arrayBuf);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintOptionsWin::DeserializeToPrintSettings(const PrintData& data,
+ nsIPrintSettings* settings)
+{
+ nsresult rv = nsPrintOptions::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().get());
+ psWin->SetDriverName(data.driverName().get());
+
+ psWin->SetPrintableWidthInInches(data.printableWidthInInches());
+ psWin->SetPrintableHeightInInches(data.printableHeightInInches());
+
+ 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 nsPrintOptionsWin::_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/nsPrintOptionsWin.h b/widget/windows/nsPrintOptionsWin.h
new file mode 100644
index 000000000..7a8a89fc9
--- /dev/null
+++ b/widget/windows/nsPrintOptionsWin.h
@@ -0,0 +1,36 @@
+/* -*- 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 nsPrintOptionsWin_h__
+#define nsPrintOptionsWin_h__
+
+#include "mozilla/embedding/PPrinting.h"
+#include "nsPrintOptionsImpl.h"
+
+class nsIPrintSettings;
+class nsIWebBrowserPrint;
+
+//*****************************************************************************
+//*** nsPrintOptions
+//*****************************************************************************
+class nsPrintOptionsWin : public nsPrintOptions
+{
+public:
+ nsPrintOptionsWin();
+ virtual ~nsPrintOptionsWin();
+
+ NS_IMETHODIMP SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ mozilla::embedding::PrintData* data);
+ NS_IMETHODIMP DeserializeToPrintSettings(const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings);
+
+ virtual nsresult _CreatePrintSettings(nsIPrintSettings **_retval);
+};
+
+
+
+#endif /* nsPrintOptions_h__ */
diff --git a/widget/windows/nsPrintSettingsWin.cpp b/widget/windows/nsPrintSettingsWin.cpp
new file mode 100644
index 000000000..80e73e530
--- /dev/null
+++ b/widget/windows/nsPrintSettingsWin.cpp
@@ -0,0 +1,528 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+// 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) :
+ mDeviceName(nullptr),
+ mDriverName(nullptr),
+ mDevMode(nullptr)
+{
+ *this = aPS;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsWin.h
+ * @update
+ */
+nsPrintSettingsWin::~nsPrintSettingsWin()
+{
+ if (mDeviceName) free(mDeviceName);
+ if (mDriverName) free(mDriverName);
+ if (mDevMode) ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDeviceName(const char16_t * aDeviceName)
+{
+ if (mDeviceName) {
+ free(mDeviceName);
+ }
+ mDeviceName = aDeviceName?wcsdup(char16ptr_t(aDeviceName)):nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsWin::GetDeviceName(char16_t **aDeviceName)
+{
+ NS_ENSURE_ARG_POINTER(aDeviceName);
+ *aDeviceName = mDeviceName?reinterpret_cast<char16_t*>(wcsdup(mDeviceName)):nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDriverName(const char16_t * aDriverName)
+{
+ if (mDriverName) {
+ free(mDriverName);
+ }
+ mDriverName = aDriverName?wcsdup(char16ptr_t(aDriverName)):nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsWin::GetDriverName(char16_t **aDriverName)
+{
+ NS_ENSURE_ARG_POINTER(aDriverName);
+ *aDriverName = mDriverName?reinterpret_cast<char16_t*>(wcsdup(mDriverName)):nullptr;
+ 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;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::GetPrintableWidthInInches(double* aPrintableWidthInInches)
+{
+ MOZ_ASSERT(aPrintableWidthInInches);
+ *aPrintableWidthInInches = mPrintableWidthInInches;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::SetPrintableWidthInInches(double aPrintableWidthInInches)
+{
+ mPrintableWidthInInches = aPrintableWidthInInches;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::GetPrintableHeightInInches(double* aPrintableHeightInInches)
+{
+ MOZ_ASSERT(aPrintableHeightInInches);
+ *aPrintableHeightInInches = mPrintableHeightInInches;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::SetPrintableHeightInInches(double aPrintableHeightInInches)
+{
+ mPrintableHeightInInches = aPrintableHeightInInches;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::GetEffectivePageSize(double *aWidth, double *aHeight)
+{
+ // If printable page size not set, fall back to nsPrintSettings.
+ if (mPrintableWidthInInches == 0l || mPrintableHeightInInches == 0l) {
+ return nsPrintSettings::GetEffectivePageSize(aWidth, aHeight);
+ }
+
+ if (mOrientation == kPortraitOrientation) {
+ *aWidth = NS_INCHES_TO_TWIPS(mPrintableWidthInInches);
+ *aHeight = NS_INCHES_TO_TWIPS(mPrintableHeightInInches);
+ } else {
+ *aHeight = NS_INCHES_TO_TWIPS(mPrintableWidthInInches);
+ *aWidth = NS_INCHES_TO_TWIPS(mPrintableHeightInInches);
+ }
+ return NS_OK;
+}
+
+void
+nsPrintSettingsWin::CopyFromNative(HDC aHdc, DEVMODEW* aDevMode)
+{
+ MOZ_ASSERT(aHdc);
+ MOZ_ASSERT(aDevMode);
+
+ mIsInitedFromPrinter = true;
+ if (aDevMode->dmFields & DM_ORIENTATION) {
+ mOrientation = int32_t(aDevMode->dmOrientation == DMORIENT_PORTRAIT
+ ? kPortraitOrientation : kLandscapeOrientation);
+ }
+
+ if (aDevMode->dmFields & DM_COPIES) {
+ mNumCopies = aDevMode->dmCopies;
+ }
+
+ // 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) {
+ mPaperData = aDevMode->dmPaperSize;
+ // If not a paper size we know about, the unit will be the last one saved.
+ if (mPaperData > 0 &&
+ mPaperData < int32_t(mozilla::ArrayLength(kPaperSizeUnits))) {
+ mPaperSizeUnit = kPaperSizeUnits[mPaperData];
+ }
+ } else {
+ mPaperData = -1;
+ }
+
+ // 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 {
+ mPaperHeight = -1l;
+ }
+
+ if (aDevMode->dmFields & DM_PAPERWIDTH) {
+ mPaperWidth = aDevMode->dmPaperWidth / sizeUnitToTenthsOfAmm;
+ } else {
+ mPaperWidth = -1l;
+ }
+
+ // On Windows we currently create a surface using the printable area of the
+ // page and don't set the unwriteable [sic] margins. Using the unwriteable
+ // margins doesn't appear to work on Windows, but I am not sure if this is a
+ // bug elsewhere in our code or a Windows quirk.
+ // Note: we only scale the printing using the LOGPIXELSY, so we use that
+ // when calculating the surface width as well as the height.
+ int32_t printableWidthInDots = GetDeviceCaps(aHdc, HORZRES);
+ int32_t printableHeightInDots = GetDeviceCaps(aHdc, VERTRES);
+ int32_t heightDPI = GetDeviceCaps(aHdc, LOGPIXELSY);
+
+ // Keep these values in portrait format, so we can reflect our own changes
+ // to mOrientation.
+ if (mOrientation == kPortraitOrientation) {
+ mPrintableWidthInInches = double(printableWidthInDots) / heightDPI;
+ mPrintableHeightInInches = double(printableHeightInDots) / heightDPI;
+ } else {
+ mPrintableHeightInInches = double(printableWidthInDots) / heightDPI;
+ mPrintableWidthInInches = double(printableHeightInDots) / heightDPI;
+ }
+
+ // Using Y to match existing code for print scaling calculations.
+ mResolution = heightDPI;
+}
+
+void
+nsPrintSettingsWin::CopyToNative(DEVMODEW* aDevMode)
+{
+ MOZ_ASSERT(aDevMode);
+
+ if (mPaperData >= 0) {
+ aDevMode->dmPaperSize = mPaperData;
+ aDevMode->dmFields |= DM_PAPERSIZE;
+ } else {
+ aDevMode->dmPaperSize = 0;
+ aDevMode->dmFields &= ~DM_PAPERSIZE;
+ }
+
+ // 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 = mOrientation == kPortraitOrientation
+ ? DMORIENT_PORTRAIT : DMORIENT_LANDSCAPE;
+ aDevMode->dmFields |= DM_ORIENTATION;
+
+ // Setup Number of Copies
+ aDevMode->dmCopies = mNumCopies;
+ aDevMode->dmFields |= DM_COPIES;
+}
+
+//-------------------------------------------
+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;
+
+ if (mDeviceName) {
+ free(mDeviceName);
+ }
+
+ if (mDriverName) {
+ free(mDriverName);
+ }
+
+ // Use free because we used the native malloc to create the memory
+ if (mDevMode) {
+ ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+ }
+
+ mDeviceName = rhs.mDeviceName?wcsdup(rhs.mDeviceName):nullptr;
+ mDriverName = rhs.mDriverName?wcsdup(rhs.mDriverName):nullptr;
+
+ 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;
+}
+
+//----------------------------------------------------------------------
+// Testing of assign and clone
+// This define turns on the testing module below
+// so at start up it writes and reads the prefs.
+#ifdef DEBUG_rodsX
+#include "nsIPrintOptions.h"
+#include "nsIServiceManager.h"
+class Tester {
+public:
+ Tester();
+};
+Tester::Tester()
+{
+ nsCOMPtr<nsIPrintSettings> ps;
+ nsresult rv;
+ nsCOMPtr<nsIPrintOptions> printService = do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = printService->CreatePrintSettings(getter_AddRefs(ps));
+ }
+
+ if (ps) {
+ ps->SetPrintOptions(nsIPrintSettings::kPrintOddPages, true);
+ ps->SetPrintOptions(nsIPrintSettings::kPrintEvenPages, false);
+ ps->SetMarginTop(1.0);
+ ps->SetMarginLeft(1.0);
+ ps->SetMarginBottom(1.0);
+ ps->SetMarginRight(1.0);
+ ps->SetScaling(0.5);
+ ps->SetPrintBGColors(true);
+ ps->SetPrintBGImages(true);
+ ps->SetPrintRange(15);
+ ps->SetHeaderStrLeft(NS_ConvertUTF8toUTF16("Left").get());
+ ps->SetHeaderStrCenter(NS_ConvertUTF8toUTF16("Center").get());
+ ps->SetHeaderStrRight(NS_ConvertUTF8toUTF16("Right").get());
+ ps->SetFooterStrLeft(NS_ConvertUTF8toUTF16("Left").get());
+ ps->SetFooterStrCenter(NS_ConvertUTF8toUTF16("Center").get());
+ ps->SetFooterStrRight(NS_ConvertUTF8toUTF16("Right").get());
+ ps->SetPaperName(NS_ConvertUTF8toUTF16("Paper Name").get());
+ ps->SetPaperData(1);
+ ps->SetPaperWidth(100.0);
+ ps->SetPaperHeight(50.0);
+ ps->SetPaperSizeUnit(nsIPrintSettings::kPaperSizeMillimeters);
+ ps->SetPrintReversed(true);
+ ps->SetPrintInColor(true);
+ ps->SetOrientation(nsIPrintSettings::kLandscapeOrientation);
+ ps->SetPrintCommand(NS_ConvertUTF8toUTF16("Command").get());
+ ps->SetNumCopies(2);
+ ps->SetPrinterName(NS_ConvertUTF8toUTF16("Printer Name").get());
+ ps->SetPrintToFile(true);
+ ps->SetToFileName(NS_ConvertUTF8toUTF16("File Name").get());
+ ps->SetPrintPageDelay(1000);
+
+ nsCOMPtr<nsIPrintSettings> ps2;
+ if (NS_SUCCEEDED(rv)) {
+ rv = printService->CreatePrintSettings(getter_AddRefs(ps2));
+ }
+
+ ps2->Assign(ps);
+
+ nsCOMPtr<nsIPrintSettings> psClone;
+ ps2->Clone(getter_AddRefs(psClone));
+
+ }
+
+}
+Tester gTester;
+#endif
diff --git a/widget/windows/nsPrintSettingsWin.h b/widget/windows/nsPrintSettingsWin.h
new file mode 100644
index 000000000..d4a31b41e
--- /dev/null
+++ b/widget/windows/nsPrintSettingsWin.h
@@ -0,0 +1,59 @@
+/* -*- 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);
+
+ /**
+ * Makes a new copy
+ */
+ virtual nsresult _Clone(nsIPrintSettings **_retval);
+
+ /**
+ * Assigns values
+ */
+ virtual nsresult _Assign(nsIPrintSettings* aPS);
+
+ /**
+ * Assignment
+ */
+ nsPrintSettingsWin& operator=(const nsPrintSettingsWin& rhs);
+
+ NS_IMETHOD GetEffectivePageSize(double *aWidth, double *aHeight) override;
+
+protected:
+ void CopyDevMode(DEVMODEW* aInDevMode, DEVMODEW *& aOutDevMode);
+
+ wchar_t* mDeviceName;
+ wchar_t* mDriverName;
+ LPDEVMODEW mDevMode;
+ double mPrintableWidthInInches = 0l;
+ double mPrintableHeightInInches = 0l;
+};
+
+
+
+#endif /* nsPrintSettingsWin_h__ */
diff --git a/widget/windows/nsScreenManagerWin.cpp b/widget/windows/nsScreenManagerWin.cpp
new file mode 100644
index 000000000..5440be7dd
--- /dev/null
+++ b/widget/windows/nsScreenManagerWin.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 "nsScreenManagerWin.h"
+#include "mozilla/gfx/2D.h"
+#include "nsScreenWin.h"
+#include "gfxWindowsPlatform.h"
+#include "nsIWidget.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+
+BOOL CALLBACK CountMonitors(HMONITOR, HDC, LPRECT, LPARAM ioCount);
+
+nsScreenManagerWin::nsScreenManagerWin()
+ : mNumberOfScreens(0)
+{
+ // nothing to do. I guess we could cache a bunch of information
+ // here, but we want to ask the device at runtime in case anything
+ // has changed.
+}
+
+
+nsScreenManagerWin::~nsScreenManagerWin()
+{
+}
+
+
+// addref, release, QI
+NS_IMPL_ISUPPORTS(nsScreenManagerWin, nsIScreenManager)
+
+
+//
+// CreateNewScreenObject
+//
+// Utility routine. Creates a new screen object from the given device handle
+//
+// NOTE: For this "single-monitor" impl, we just always return the cached primary
+// screen. This should change when a multi-monitor impl is done.
+//
+nsIScreen*
+nsScreenManagerWin::CreateNewScreenObject(HMONITOR inScreen)
+{
+ nsIScreen* retScreen = nullptr;
+
+ // look through our screen list, hoping to find it. If it's not there,
+ // add it and return the new one.
+ for (unsigned i = 0; i < mScreenList.Length(); ++i) {
+ ScreenListItem& curr = mScreenList[i];
+ if (inScreen == curr.mMon) {
+ NS_IF_ADDREF(retScreen = curr.mScreen.get());
+ return retScreen;
+ }
+ } // for each screen.
+
+ retScreen = new nsScreenWin(inScreen);
+ mScreenList.AppendElement(ScreenListItem(inScreen, retScreen));
+
+ NS_IF_ADDREF(retScreen);
+ return retScreen;
+}
+
+NS_IMETHODIMP
+nsScreenManagerWin::ScreenForId(uint32_t aId, nsIScreen **outScreen)
+{
+ *outScreen = nullptr;
+
+ for (unsigned i = 0; i < mScreenList.Length(); ++i) {
+ ScreenListItem& curr = mScreenList[i];
+ uint32_t id;
+ nsresult rv = curr.mScreen->GetId(&id);
+ if (NS_SUCCEEDED(rv) && id == aId) {
+ NS_IF_ADDREF(*outScreen = curr.mScreen.get());
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+//
+// ScreenForRect
+//
+// 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 logical screen coordinates.
+//
+NS_IMETHODIMP
+nsScreenManagerWin::ScreenForRect(int32_t inLeft, int32_t inTop,
+ int32_t inWidth, int32_t inHeight,
+ nsIScreen **outScreen)
+{
+ if (!(inWidth || inHeight)) {
+ NS_WARNING("trying to find screen for sizeless window, using primary monitor");
+ *outScreen = CreateNewScreenObject(nullptr); // addrefs
+ return NS_OK;
+ }
+
+ gfx::Rect logicalBounds(inLeft, inTop, inWidth, inHeight);
+ HMONITOR genScreen = widget::WinUtils::MonitorFromRect(logicalBounds);
+
+ *outScreen = CreateNewScreenObject(genScreen); // addrefs
+
+ return NS_OK;
+
+} // ScreenForRect
+
+
+//
+// GetPrimaryScreen
+//
+// The screen with the menubar/taskbar. This shouldn't be needed very
+// often.
+//
+NS_IMETHODIMP
+nsScreenManagerWin::GetPrimaryScreen(nsIScreen** aPrimaryScreen)
+{
+ *aPrimaryScreen = CreateNewScreenObject(nullptr); // addrefs
+ return NS_OK;
+
+} // GetPrimaryScreen
+
+
+//
+// CountMonitors
+//
+// Will be called once for every monitor in the system. Just
+// increments the parameter, which holds a ptr to a PRUin32 holding the
+// count up to this point.
+//
+BOOL CALLBACK
+CountMonitors(HMONITOR, HDC, LPRECT, LPARAM ioParam)
+{
+ uint32_t* countPtr = reinterpret_cast<uint32_t*>(ioParam);
+ ++(*countPtr);
+
+ return TRUE; // continue the enumeration
+
+} // CountMonitors
+
+
+//
+// GetNumberOfScreens
+//
+// Returns how many physical screens are available.
+//
+NS_IMETHODIMP
+nsScreenManagerWin::GetNumberOfScreens(uint32_t *aNumberOfScreens)
+{
+ if (mNumberOfScreens)
+ *aNumberOfScreens = mNumberOfScreens;
+ else {
+ uint32_t count = 0;
+ BOOL result = ::EnumDisplayMonitors(nullptr, nullptr, (MONITORENUMPROC)CountMonitors, (LPARAM)&count);
+ if (!result)
+ return NS_ERROR_FAILURE;
+ *aNumberOfScreens = mNumberOfScreens = count;
+ }
+
+ return NS_OK;
+
+} // GetNumberOfScreens
+
+NS_IMETHODIMP
+nsScreenManagerWin::GetSystemDefaultScale(float *aDefaultScale)
+{
+ HMONITOR primary = widget::WinUtils::GetPrimaryMonitor();
+ *aDefaultScale = float(widget::WinUtils::LogToPhysFactor(primary));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerWin::ScreenForNativeWidget(void *aWidget, nsIScreen **outScreen)
+{
+ HMONITOR mon = MonitorFromWindow((HWND) aWidget, MONITOR_DEFAULTTOPRIMARY);
+ *outScreen = CreateNewScreenObject(mon);
+ return NS_OK;
+}
diff --git a/widget/windows/nsScreenManagerWin.h b/widget/windows/nsScreenManagerWin.h
new file mode 100644
index 000000000..c4757418a
--- /dev/null
+++ b/widget/windows/nsScreenManagerWin.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 nsScreenManagerWin_h___
+#define nsScreenManagerWin_h___
+
+#include "nsIScreenManager.h"
+
+#include <windows.h>
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+class nsIScreen;
+
+//------------------------------------------------------------------------
+
+class ScreenListItem
+{
+public:
+ ScreenListItem ( HMONITOR inMon, nsIScreen* inScreen )
+ : mMon(inMon), mScreen(inScreen) { } ;
+
+ HMONITOR mMon;
+ nsCOMPtr<nsIScreen> mScreen;
+};
+
+class nsScreenManagerWin final : public nsIScreenManager
+{
+public:
+ nsScreenManagerWin ( );
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+private:
+ ~nsScreenManagerWin();
+
+ nsIScreen* CreateNewScreenObject ( HMONITOR inScreen ) ;
+
+ uint32_t mNumberOfScreens;
+
+ // cache the screens to avoid the memory allocations
+ AutoTArray<ScreenListItem, 8> mScreenList;
+
+};
+
+#endif // nsScreenManagerWin_h___
diff --git a/widget/windows/nsScreenWin.cpp b/widget/windows/nsScreenWin.cpp
new file mode 100644
index 000000000..beacbf05f
--- /dev/null
+++ b/widget/windows/nsScreenWin.cpp
@@ -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/. */
+
+#include "nsScreenWin.h"
+#include "nsCoord.h"
+#include "nsIWidget.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+
+static uint32_t sScreenId;
+
+nsScreenWin::nsScreenWin(HMONITOR inScreen)
+ : mScreen(inScreen)
+ , mId(++sScreenId)
+{
+#ifdef DEBUG
+ HDC hDCScreen = ::GetDC(nullptr);
+ NS_ASSERTION(hDCScreen,"GetDC Failure");
+ NS_ASSERTION(::GetDeviceCaps(hDCScreen, TECHNOLOGY) == DT_RASDISPLAY, "Not a display screen");
+ ::ReleaseDC(nullptr,hDCScreen);
+#endif
+
+ // nothing else to do. I guess we could cache a bunch of information
+ // here, but we want to ask the device at runtime in case anything
+ // has changed.
+}
+
+
+nsScreenWin::~nsScreenWin()
+{
+ // nothing to see here.
+}
+
+
+NS_IMETHODIMP
+nsScreenWin::GetId(uint32_t *outId)
+{
+ *outId = mId;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsScreenWin::GetRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ BOOL success = FALSE;
+ if (mScreen) {
+ MONITORINFO info;
+ info.cbSize = sizeof(MONITORINFO);
+ success = ::GetMonitorInfoW(mScreen, &info);
+ if (success) {
+ *outLeft = info.rcMonitor.left;
+ *outTop = info.rcMonitor.top;
+ *outWidth = info.rcMonitor.right - info.rcMonitor.left;
+ *outHeight = info.rcMonitor.bottom - info.rcMonitor.top;
+ }
+ }
+ if (!success) {
+ HDC hDCScreen = ::GetDC(nullptr);
+ NS_ASSERTION(hDCScreen,"GetDC Failure");
+
+ *outTop = *outLeft = 0;
+ *outWidth = ::GetDeviceCaps(hDCScreen, HORZRES);
+ *outHeight = ::GetDeviceCaps(hDCScreen, VERTRES);
+
+ ::ReleaseDC(nullptr, hDCScreen);
+ }
+ return NS_OK;
+
+} // GetRect
+
+
+NS_IMETHODIMP
+nsScreenWin::GetAvailRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ BOOL success = FALSE;
+
+ if (mScreen) {
+ MONITORINFO info;
+ info.cbSize = sizeof(MONITORINFO);
+ success = ::GetMonitorInfoW(mScreen, &info);
+ if (success) {
+ *outLeft = info.rcWork.left;
+ *outTop = info.rcWork.top;
+ *outWidth = info.rcWork.right - info.rcWork.left;
+ *outHeight = info.rcWork.bottom - info.rcWork.top;
+ }
+ }
+ if (!success) {
+ RECT workArea;
+ ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
+ *outLeft = workArea.left;
+ *outTop = workArea.top;
+ *outWidth = workArea.right - workArea.left;
+ *outHeight = workArea.bottom - workArea.top;
+ }
+
+ return NS_OK;
+
+} // GetAvailRect
+
+NS_IMETHODIMP
+nsScreenWin::GetRectDisplayPix(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight)
+{
+ if (widget::WinUtils::IsPerMonitorDPIAware()) {
+ // on per-monitor-dpi config, display pixels are device pixels
+ return GetRect(outLeft, outTop, outWidth, outHeight);
+ }
+ int32_t left, top, width, height;
+ nsresult rv = GetRect(&left, &top, &width, &height);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ double scaleFactor = 1.0 / widget::WinUtils::LogToPhysFactor(mScreen);
+ *outLeft = NSToIntRound(left * scaleFactor);
+ *outTop = NSToIntRound(top * scaleFactor);
+ *outWidth = NSToIntRound(width * scaleFactor);
+ *outHeight = NSToIntRound(height * scaleFactor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenWin::GetAvailRectDisplayPix(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight)
+{
+ if (widget::WinUtils::IsPerMonitorDPIAware()) {
+ // on per-monitor-dpi config, display pixels are device pixels
+ return GetAvailRect(outLeft, outTop, outWidth, outHeight);
+ }
+ int32_t left, top, width, height;
+ nsresult rv = GetAvailRect(&left, &top, &width, &height);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ double scaleFactor = 1.0 / widget::WinUtils::LogToPhysFactor(mScreen);
+ *outLeft = NSToIntRound(left * scaleFactor);
+ *outTop = NSToIntRound(top * scaleFactor);
+ *outWidth = NSToIntRound(width * scaleFactor);
+ *outHeight = NSToIntRound(height * scaleFactor);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsScreenWin :: GetPixelDepth(int32_t *aPixelDepth)
+{
+ //XXX not sure how to get this info for multiple monitors, this might be ok...
+ HDC hDCScreen = ::GetDC(nullptr);
+ NS_ASSERTION(hDCScreen,"GetDC Failure");
+
+ int32_t depth = ::GetDeviceCaps(hDCScreen, BITSPIXEL);
+ if (depth == 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.)
+ depth = 24;
+ }
+ *aPixelDepth = depth;
+
+ ::ReleaseDC(nullptr, hDCScreen);
+ return NS_OK;
+
+} // GetPixelDepth
+
+
+NS_IMETHODIMP
+nsScreenWin::GetColorDepth(int32_t *aColorDepth)
+{
+ return GetPixelDepth(aColorDepth);
+
+} // GetColorDepth
+
+
+NS_IMETHODIMP
+nsScreenWin::GetContentsScaleFactor(double *aContentsScaleFactor)
+{
+ if (widget::WinUtils::IsPerMonitorDPIAware()) {
+ *aContentsScaleFactor = 1.0;
+ } else {
+ *aContentsScaleFactor = widget::WinUtils::LogToPhysFactor(mScreen);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenWin::GetDefaultCSSScaleFactor(double* aScaleFactor)
+{
+ double scale = nsIWidget::DefaultScaleOverride();
+ if (scale > 0.0) {
+ *aScaleFactor = scale;
+ } else {
+ *aScaleFactor = widget::WinUtils::LogToPhysFactor(mScreen);
+ }
+ return NS_OK;
+}
diff --git a/widget/windows/nsScreenWin.h b/widget/windows/nsScreenWin.h
new file mode 100644
index 000000000..0560c5534
--- /dev/null
+++ b/widget/windows/nsScreenWin.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 nsScreenWin_h___
+#define nsScreenWin_h___
+
+#include <windows.h>
+#include "nsBaseScreen.h"
+
+//------------------------------------------------------------------------
+
+class nsScreenWin final : public nsBaseScreen
+{
+public:
+ nsScreenWin ( HMONITOR inScreen );
+ ~nsScreenWin();
+
+ NS_IMETHOD GetId(uint32_t* aId);
+
+ // These methods return the size in device (physical) pixels
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+
+ // And these methods get the screen size in 'desktop pixels' (AKA 'logical pixels')
+ // that are dependent on the logical DPI setting in windows
+ NS_IMETHOD GetRectDisplayPix(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight);
+ NS_IMETHOD GetAvailRectDisplayPix(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight);
+
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
+
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor) override;
+
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor) override;
+
+private:
+ HMONITOR mScreen;
+ uint32_t mId;
+};
+
+#endif // nsScreenWin_h___
diff --git a/widget/windows/nsSound.cpp b/widget/windows/nsSound.cpp
new file mode 100644
index 000000000..a7e3f8e7c
--- /dev/null
+++ b/widget/windows/nsSound.cpp
@@ -0,0 +1,305 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsSound.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "prprf.h"
+#include "prmem.h"
+
+#include "nsNativeCharsetUtils.h"
+#include "nsThreadUtils.h"
+
+using mozilla::LogLevel;
+
+PRLogModuleInfo* gWin32SoundLog = nullptr;
+
+class nsSoundPlayer: public mozilla::Runnable {
+public:
+ nsSoundPlayer(nsSound *aSound, const wchar_t* aSoundName) :
+ mSoundName(aSoundName), mSound(aSound)
+ {
+ Init();
+ }
+
+ nsSoundPlayer(nsSound *aSound, const nsAString& aSoundName) :
+ mSoundName(aSoundName), mSound(aSound)
+ {
+ Init();
+ }
+
+ NS_DECL_NSIRUNNABLE
+
+protected:
+ nsString mSoundName;
+ nsSound *mSound; // Strong, but this will be released from SoundReleaser.
+ nsCOMPtr<nsIThread> mThread;
+
+ void Init()
+ {
+ NS_GetCurrentThread(getter_AddRefs(mThread));
+ NS_ASSERTION(mThread, "failed to get current thread");
+ NS_IF_ADDREF(mSound);
+ }
+
+ class SoundReleaser: public mozilla::Runnable {
+ public:
+ SoundReleaser(nsSound* aSound) :
+ mSound(aSound)
+ {
+ }
+
+ NS_DECL_NSIRUNNABLE
+
+ protected:
+ nsSound *mSound;
+ };
+};
+
+NS_IMETHODIMP
+nsSoundPlayer::Run()
+{
+ PR_SetCurrentThreadName("Play Sound");
+
+ NS_PRECONDITION(!mSoundName.IsEmpty(), "Sound name should not be empty");
+ ::PlaySoundW(mSoundName.get(), nullptr,
+ SND_NODEFAULT | SND_ALIAS | SND_ASYNC);
+ nsCOMPtr<nsIRunnable> releaser = new SoundReleaser(mSound);
+ // Don't release nsSound from here, because here is not an owning thread of
+ // the nsSound. nsSound must be released in its owning thread.
+ mThread->Dispatch(releaser, NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSoundPlayer::SoundReleaser::Run()
+{
+ mSound->ShutdownOldPlayerThread();
+ NS_IF_RELEASE(mSound);
+ return NS_OK;
+}
+
+
+#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)
+
+
+nsSound::nsSound()
+{
+ if (!gWin32SoundLog) {
+ gWin32SoundLog = PR_NewLogModule("nsSound");
+ }
+
+ mLastSound = nullptr;
+}
+
+nsSound::~nsSound()
+{
+ NS_ASSERTION(!mPlayerThread, "player thread is not null but should be");
+ PurgeLastSound();
+}
+
+void nsSound::ShutdownOldPlayerThread()
+{
+ nsCOMPtr<nsIThread> playerThread(mPlayerThread.forget());
+ if (playerThread)
+ playerThread->Shutdown();
+}
+
+void nsSound::PurgeLastSound()
+{
+ if (mLastSound) {
+ // Halt any currently playing sound.
+ ::PlaySound(nullptr, nullptr, SND_PURGE);
+
+ // Now delete the buffer.
+ free(mLastSound);
+ mLastSound = nullptr;
+ }
+}
+
+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)
+{
+ // 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;
+ }
+
+ ShutdownOldPlayerThread();
+ PurgeLastSound();
+
+ if (data && dataLen > 0) {
+ DWORD flags = SND_MEMORY | SND_NODEFAULT;
+ // We try to make a copy so we can play it async.
+ mLastSound = (uint8_t *) malloc(dataLen);
+ if (mLastSound) {
+ memcpy(mLastSound, data, dataLen);
+ data = mLastSound;
+ flags |= SND_ASYNC;
+ }
+ ::PlaySoundW(reinterpret_cast<LPCWSTR>(data), 0, flags);
+ }
+
+ 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_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ return rv;
+}
+
+
+NS_IMETHODIMP nsSound::Init()
+{
+ // 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.
+ ::PlaySound(nullptr, nullptr, SND_PURGE);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias)
+{
+ ShutdownOldPlayerThread();
+ PurgeLastSound();
+
+ if (!NS_IsMozAliasSound(aSoundAlias)) {
+ if (aSoundAlias.IsEmpty())
+ return NS_OK;
+ nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(this, aSoundAlias);
+ NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead");
+
+ uint32_t eventId;
+ if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP))
+ eventId = EVENT_NEW_MAIL_RECEIVED;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG))
+ eventId = EVENT_CONFIRM_DIALOG_OPEN;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG))
+ eventId = EVENT_ALERT_DIALOG_OPEN;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE))
+ eventId = EVENT_MENU_EXECUTE;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP))
+ eventId = EVENT_MENU_POPUP;
+ else
+ return NS_OK;
+
+ return PlayEventSound(eventId);
+}
+
+NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId)
+{
+ ShutdownOldPlayerThread();
+ 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");
+
+ nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(this, sound);
+ NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
diff --git a/widget/windows/nsSound.h b/widget/windows/nsSound.h
new file mode 100644
index 000000000..fc3ea30ca
--- /dev/null
+++ b/widget/windows/nsSound.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 __nsSound_h__
+#define __nsSound_h__
+
+#include "nsISound.h"
+#include "nsIStreamLoader.h"
+#include "nsCOMPtr.h"
+
+class nsIThread;
+
+class nsSound : public nsISound,
+ public nsIStreamLoaderObserver
+
+{
+public:
+ nsSound();
+ void ShutdownOldPlayerThread();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+private:
+ virtual ~nsSound();
+ void PurgeLastSound();
+
+private:
+ uint8_t* mLastSound;
+ nsCOMPtr<nsIThread> mPlayerThread;
+};
+
+#endif /* __nsSound_h__ */
diff --git a/widget/windows/nsToolkit.cpp b/widget/windows/nsToolkit.cpp
new file mode 100644
index 000000000..6c3cc0508
--- /dev/null
+++ b/widget/windows/nsToolkit.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 "nsToolkit.h"
+#include "nsAppShell.h"
+#include "nsWindow.h"
+#include "nsWidgetsCID.h"
+#include "prmon.h"
+#include "prtime.h"
+#include "nsIServiceManager.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();
+ nsUXThemeData::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 000000000..14bb22c18
--- /dev/null
+++ b/widget/windows/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 "nsdefs.h"
+
+#include "nsITimer.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 000000000..731dcedf2
--- /dev/null
+++ b/widget/windows/nsUXThemeConstants.h
@@ -0,0 +1,251 @@
+/* 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
+
+// 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
+
+// Vista only; implict 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
+};
+
+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 */
+};
+
+}}} // mozilla::widget::themeconst
+
+#endif
diff --git a/widget/windows/nsUXThemeData.cpp b/widget/windows/nsUXThemeData.cpp
new file mode 100644
index 000000000..bcbd32484
--- /dev/null
+++ b/widget/windows/nsUXThemeData.cpp
@@ -0,0 +1,405 @@
+/* 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"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+const wchar_t
+nsUXThemeData::kThemeLibraryName[] = L"uxtheme.dll";
+
+HANDLE
+nsUXThemeData::sThemes[eUXNumClasses];
+
+HMODULE
+nsUXThemeData::sThemeDLL = nullptr;
+
+bool
+nsUXThemeData::sFlatMenus = false;
+
+bool nsUXThemeData::sTitlebarInfoPopulatedAero = false;
+bool nsUXThemeData::sTitlebarInfoPopulatedThemed = false;
+const int NUM_COMMAND_BUTTONS = 4;
+SIZE nsUXThemeData::sCommandButtons[NUM_COMMAND_BUTTONS];
+
+void
+nsUXThemeData::Teardown() {
+ Invalidate();
+ if(sThemeDLL)
+ FreeLibrary(sThemeDLL);
+}
+
+void
+nsUXThemeData::Initialize()
+{
+ ::ZeroMemory(sThemes, sizeof(sThemes));
+ NS_ASSERTION(!sThemeDLL, "nsUXThemeData being initialized twice!");
+
+ CheckForCompositor(true);
+ Invalidate();
+}
+
+void
+nsUXThemeData::Invalidate() {
+ for(int i = 0; i < eUXNumClasses; i++) {
+ if(sThemes[i]) {
+ CloseThemeData(sThemes[i]);
+ sThemes[i] = nullptr;
+ }
+ }
+ BOOL useFlat = FALSE;
+ sFlatMenus = ::SystemParametersInfo(SPI_GETFLATMENU, 0, &useFlat, 0) ?
+ useFlat : false;
+}
+
+HANDLE
+nsUXThemeData::GetTheme(nsUXThemeClass cls) {
+ NS_ASSERTION(cls < eUXNumClasses, "Invalid theme class!");
+ if(!sThemes[cls])
+ {
+ sThemes[cls] = OpenThemeData(nullptr, GetClassName(cls));
+ }
+ return sThemes[cls];
+}
+
+HMODULE
+nsUXThemeData::GetThemeDLL() {
+ if (!sThemeDLL)
+ sThemeDLL = ::LoadLibraryW(kThemeLibraryName);
+ return sThemeDLL;
+}
+
+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:
+ NS_NOTREACHED("unknown uxtheme class");
+ return L"";
+ }
+}
+
+// static
+void
+nsUXThemeData::InitTitlebarInfo()
+{
+ // Pre-populate with generic metrics. These likley will not match
+ // the current theme, but they insure the buttons at least show up.
+ sCommandButtons[0].cx = GetSystemMetrics(SM_CXSIZE);
+ sCommandButtons[0].cy = GetSystemMetrics(SM_CYSIZE);
+ sCommandButtons[1].cx = sCommandButtons[2].cx = sCommandButtons[0].cx;
+ sCommandButtons[1].cy = sCommandButtons[2].cy = sCommandButtons[0].cy;
+ sCommandButtons[3].cx = sCommandButtons[0].cx * 3;
+ sCommandButtons[3].cy = sCommandButtons[0].cy;
+
+ // Use system metrics for pre-vista, otherwise trigger a
+ // refresh on the next layout.
+ sTitlebarInfoPopulatedAero = sTitlebarInfoPopulatedThemed =
+ !IsVistaOrLater();
+}
+
+// static
+void
+nsUXThemeData::UpdateTitlebarInfo(HWND aWnd)
+{
+ if (!aWnd)
+ return;
+
+ if (!sTitlebarInfoPopulatedAero && nsUXThemeData::CheckForCompositor()) {
+ RECT captionButtons;
+ if (SUCCEEDED(WinUtils::dwmGetWindowAttributePtr(aWnd,
+ DWMWA_CAPTION_BUTTON_BOUNDS,
+ &captionButtons,
+ sizeof(captionButtons)))) {
+ sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cx = captionButtons.right - captionButtons.left - 3;
+ sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cy = (captionButtons.bottom - captionButtons.top) - 1;
+ 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 && !nsUXThemeData::CheckForCompositor())) {
+ 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
+ sCommandButtons[0].cx = info.rgrect[2].right - info.rgrect[2].left;
+ sCommandButtons[0].cy = info.rgrect[2].bottom - info.rgrect[2].top;
+ // maximize/restore
+ sCommandButtons[1].cx = info.rgrect[3].right - info.rgrect[3].left;
+ sCommandButtons[1].cy = info.rgrect[3].bottom - info.rgrect[3].top;
+ // close
+ sCommandButtons[2].cx = info.rgrect[5].right - info.rgrect[5].left;
+ sCommandButtons[2].cy = info.rgrect[5].bottom - info.rgrect[5].top;
+
+#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(sCommandButtons[i].cx > 0);
+ MOZ_ASSERT(sCommandButtons[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
+bool nsUXThemeData::CheckForCompositor(bool aUpdateCache)
+{
+ static BOOL sCachedValue = FALSE;
+ if (aUpdateCache && WinUtils::dwmIsCompositionEnabledPtr) {
+ WinUtils::dwmIsCompositionEnabledPtr(&sCachedValue);
+ }
+ return sCachedValue;
+}
+
+// static
+void
+nsUXThemeData::UpdateNativeThemeInfo()
+{
+ // Trigger a refresh of themed button metrics if needed
+ sTitlebarInfoPopulatedThemed = !IsVistaOrLater();
+
+ 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 (!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;
+ }
+}
diff --git a/widget/windows/nsUXThemeData.h b/widget/windows/nsUXThemeData.h
new file mode 100644
index 000000000..2688ec659
--- /dev/null
+++ b/widget/windows/nsUXThemeData.h
@@ -0,0 +1,120 @@
+/* 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 "WinUtils.h"
+
+#include <dwmapi.h>
+
+#include "nsWindowDefs.h"
+
+// These window messages are not defined in dwmapi.h
+#ifndef WM_DWMCOMPOSITIONCHANGED
+#define WM_DWMCOMPOSITIONCHANGED 0x031E
+#endif
+
+// Windows 7 additions
+#ifndef WM_DWMSENDICONICTHUMBNAIL
+#define WM_DWMSENDICONICTHUMBNAIL 0x0323
+#define WM_DWMSENDICONICLIVEPREVIEWBITMAP 0x0326
+#endif
+
+#define DWMWA_FORCE_ICONIC_REPRESENTATION 7
+#define DWMWA_HAS_ICONIC_BITMAP 10
+
+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
+};
+
+#define CMDBUTTONIDX_MINIMIZE 0
+#define CMDBUTTONIDX_RESTORE 1
+#define CMDBUTTONIDX_CLOSE 2
+#define CMDBUTTONIDX_BUTTONBOX 3
+
+class nsUXThemeData {
+ static HMODULE sThemeDLL;
+ static HANDLE sThemes[eUXNumClasses];
+
+ static const wchar_t *GetClassName(nsUXThemeClass);
+
+public:
+ static const wchar_t kThemeLibraryName[];
+ static bool sFlatMenus;
+ static bool sTitlebarInfoPopulatedAero;
+ static bool sTitlebarInfoPopulatedThemed;
+ static SIZE sCommandButtons[4];
+ static mozilla::LookAndFeel::WindowsTheme sThemeId;
+ static bool sIsDefaultWindowsTheme;
+ static bool sIsHighContrastOn;
+
+ static void Initialize();
+ static void Teardown();
+ static void Invalidate();
+ static HANDLE GetTheme(nsUXThemeClass cls);
+ static HMODULE GetThemeDLL();
+
+ // nsWindow calls this to update desktop settings info
+ static void InitTitlebarInfo();
+ static void UpdateTitlebarInfo(HWND aWnd);
+
+ static void UpdateNativeThemeInfo();
+ static mozilla::LookAndFeel::WindowsTheme GetNativeThemeId();
+ static bool IsDefaultWindowTheme();
+ static bool IsHighContrastOn();
+
+ // This method returns the cached compositor state. Most
+ // callers should call without the argument. The cache
+ // should be modified only when the application receives
+ // WM_DWMCOMPOSITIONCHANGED. This rule prevents inconsistent
+ // results for two or more calls which check the state during
+ // composition transition.
+ static bool CheckForCompositor(bool aUpdateCache = false);
+};
+#endif // __UXThemeData_h__
diff --git a/widget/windows/nsWidgetFactory.cpp b/widget/windows/nsWidgetFactory.cpp
new file mode 100644
index 000000000..7e6ffd638
--- /dev/null
+++ b/widget/windows/nsWidgetFactory.cpp
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsIFactory.h"
+#include "nsISupports.h"
+#include "nsdefs.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIdleServiceWin.h"
+#include "nsLookAndFeel.h"
+#include "nsScreenManagerWin.h"
+#include "nsSound.h"
+#include "WinMouseScrollHandler.h"
+#include "KeyboardLayout.h"
+#include "GfxInfo.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"
+#include "nsNativeThemeWin.h"
+#include "nsWindow.h"
+// Content processes
+#include "nsFilePickerProxy.h"
+
+// Drag & Drop, Clipboard
+#include "nsClipboardHelper.h"
+#include "nsClipboard.h"
+#include "nsBidiKeyboard.h"
+#include "nsDragService.h"
+#include "nsTransferable.h"
+#include "nsHTMLFormatConverter.h"
+
+#include "WinTaskbar.h"
+#include "JumpListBuilder.h"
+#include "JumpListItem.h"
+
+#include "WindowsUIUtils.h"
+
+#ifdef NS_PRINTING
+#include "nsDeviceContextSpecWin.h"
+#include "nsPrintOptionsWin.h"
+#include "nsPrintSession.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static nsresult
+WindowConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIWidget> widget = new nsWindow;
+ return widget->QueryInterface(aIID, aResult);
+}
+
+static nsresult
+ChildWindowConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIWidget> widget = new ChildWindow;
+ return widget->QueryInterface(aIID, aResult);
+}
+
+static nsresult
+FilePickerConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIFilePicker> picker = new nsFilePicker;
+ return picker->QueryInterface(aIID, aResult);
+}
+
+static nsresult
+ColorPickerConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIColorPicker> picker = new nsColorPicker;
+ return picker->QueryInterface(aIID, aResult);
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerWin)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceWin, nsIdleServiceWin::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound)
+NS_GENERIC_FACTORY_CONSTRUCTOR(WinTaskbar)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListBuilder)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListItem)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListSeparator)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListLink)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListShortcut)
+NS_GENERIC_FACTORY_CONSTRUCTOR(WindowsUIUtils)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(TaskbarPreviewCallback)
+#ifdef NS_PRINTING
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsWin, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrinterEnumeratorWin)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecWin)
+#endif
+
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+}
+}
+
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID);
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_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_WIN_TASKBAR_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTBUILDER_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTITEM_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTSEPARATOR_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTLINK_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTSHORTCUT_CID);
+NS_DEFINE_NAMED_CID(NS_WINDOWS_UIUTILS_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_TASKBARPREVIEWCALLBACK_CID);
+#ifdef NS_PRINTING
+NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTER_ENUMERATOR_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
+#endif
+
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, nullptr, WindowConstructor },
+ { &kNS_CHILD_CID, false, nullptr, ChildWindowConstructor },
+ { &kNS_FILEPICKER_CID, false, nullptr, FilePickerConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_COLORPICKER_CID, false, nullptr, ColorPickerConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor, Module::ALLOW_IN_GPU_PROCESS },
+ { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerWinConstructor,
+ Module::MAIN_PROCESS_ONLY },
+ { &kNS_GFXINFO_CID, false, nullptr, GfxInfoConstructor },
+ { &kNS_THEMERENDERER_CID, false, nullptr, NS_NewNativeTheme },
+ { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceWinConstructor },
+ { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
+ { &kNS_SOUND_CID, false, nullptr, nsSoundConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
+ { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
+ { &kNS_WIN_TASKBAR_CID, false, nullptr, WinTaskbarConstructor },
+ { &kNS_WIN_JUMPLISTBUILDER_CID, false, nullptr, JumpListBuilderConstructor },
+ { &kNS_WIN_JUMPLISTITEM_CID, false, nullptr, JumpListItemConstructor },
+ { &kNS_WIN_JUMPLISTSEPARATOR_CID, false, nullptr, JumpListSeparatorConstructor },
+ { &kNS_WIN_JUMPLISTLINK_CID, false, nullptr, JumpListLinkConstructor },
+ { &kNS_WIN_JUMPLISTSHORTCUT_CID, false, nullptr, JumpListShortcutConstructor },
+ { &kNS_WINDOWS_UIUTILS_CID, false, nullptr, WindowsUIUtilsConstructor },
+ { &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_BIDIKEYBOARD_CID, false, nullptr, nsBidiKeyboardConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_TASKBARPREVIEWCALLBACK_CID, false, nullptr, TaskbarPreviewCallbackConstructor },
+#ifdef NS_PRINTING
+ { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsWinConstructor },
+ { &kNS_PRINTER_ENUMERATOR_CID, false, nullptr, nsPrinterEnumeratorWinConstructor },
+ { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor },
+ { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecWinConstructor },
+#endif
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/win;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/child_window/win;1", &kNS_CHILD_CID },
+ { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/appshell/win;1", &kNS_APPSHELL_CID, Module::ALLOW_IN_GPU_PROCESS },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID },
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { "@mozilla.org/sound;1", &kNS_SOUND_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/windows-taskbar;1", &kNS_WIN_TASKBAR_CID },
+ { "@mozilla.org/windows-jumplistbuilder;1", &kNS_WIN_JUMPLISTBUILDER_CID },
+ { "@mozilla.org/windows-jumplistitem;1", &kNS_WIN_JUMPLISTITEM_CID },
+ { "@mozilla.org/windows-jumplistseparator;1", &kNS_WIN_JUMPLISTSEPARATOR_CID },
+ { "@mozilla.org/windows-jumplistlink;1", &kNS_WIN_JUMPLISTLINK_CID },
+ { "@mozilla.org/windows-jumplistshortcut;1", &kNS_WIN_JUMPLISTSHORTCUT_CID },
+ { "@mozilla.org/windows-ui-utils;1", &kNS_WINDOWS_UIUTILS_CID },
+ { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/taskbar-preview-callback;1", &kNS_TASKBARPREVIEWCALLBACK_CID },
+#ifdef NS_PRINTING
+ { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
+ { "@mozilla.org/gfx/printerenumerator;1", &kNS_PRINTER_ENUMERATOR_CID },
+ { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
+ { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
+#endif
+ { nullptr }
+};
+
+static void
+nsWidgetWindowsModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ KeyboardLayout::Shutdown();
+ MouseScrollHandler::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsToolkit::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ nullptr,
+ nullptr,
+ nsAppShellInit,
+ nsWidgetWindowsModuleDtor,
+ Module::ALLOW_IN_GPU_PROCESS
+};
+
+NSMODULE_DEFN(nsWidgetModule) = &kWidgetModule;
diff --git a/widget/windows/nsWinGesture.cpp b/widget/windows/nsWinGesture.cpp
new file mode 100644
index 000000000..faec5ec22
--- /dev/null
+++ b/widget/windows/nsWinGesture.cpp
@@ -0,0 +1,590 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDOMSimpleGestureEvent.h"
+#include "nsIDOMWheelEvent.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TouchEvents.h"
+
+#include <cmath>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+const wchar_t nsWinGesture::kGestureLibraryName[] = L"user32.dll";
+HMODULE nsWinGesture::sLibraryHandle = nullptr;
+nsWinGesture::GetGestureInfoPtr nsWinGesture::getGestureInfo = nullptr;
+nsWinGesture::CloseGestureInfoHandlePtr nsWinGesture::closeGestureInfoHandle = nullptr;
+nsWinGesture::GetGestureExtraArgsPtr nsWinGesture::getGestureExtraArgs = nullptr;
+nsWinGesture::SetGestureConfigPtr nsWinGesture::setGestureConfig = nullptr;
+nsWinGesture::GetGestureConfigPtr nsWinGesture::getGestureConfig = nullptr;
+nsWinGesture::BeginPanningFeedbackPtr nsWinGesture::beginPanningFeedback = nullptr;
+nsWinGesture::EndPanningFeedbackPtr nsWinGesture::endPanningFeedback = nullptr;
+nsWinGesture::UpdatePanningFeedbackPtr nsWinGesture::updatePanningFeedback = nullptr;
+
+nsWinGesture::RegisterTouchWindowPtr nsWinGesture::registerTouchWindow = nullptr;
+nsWinGesture::UnregisterTouchWindowPtr nsWinGesture::unregisterTouchWindow = nullptr;
+nsWinGesture::GetTouchInputInfoPtr nsWinGesture::getTouchInputInfo = nullptr;
+nsWinGesture::CloseTouchInputHandlePtr nsWinGesture::closeTouchInputHandle = nullptr;
+
+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()
+{
+ if (getGestureInfo) {
+ return true;
+ } else if (sLibraryHandle) {
+ return false;
+ }
+
+ sLibraryHandle = ::LoadLibraryW(kGestureLibraryName);
+ HMODULE hTheme = nsUXThemeData::GetThemeDLL();
+
+ // gesture interfaces
+ if (sLibraryHandle) {
+ getGestureInfo = (GetGestureInfoPtr)GetProcAddress(sLibraryHandle, "GetGestureInfo");
+ closeGestureInfoHandle = (CloseGestureInfoHandlePtr)GetProcAddress(sLibraryHandle, "CloseGestureInfoHandle");
+ getGestureExtraArgs = (GetGestureExtraArgsPtr)GetProcAddress(sLibraryHandle, "GetGestureExtraArgs");
+ setGestureConfig = (SetGestureConfigPtr)GetProcAddress(sLibraryHandle, "SetGestureConfig");
+ getGestureConfig = (GetGestureConfigPtr)GetProcAddress(sLibraryHandle, "GetGestureConfig");
+ registerTouchWindow = (RegisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "RegisterTouchWindow");
+ unregisterTouchWindow = (UnregisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "UnregisterTouchWindow");
+ getTouchInputInfo = (GetTouchInputInfoPtr)GetProcAddress(sLibraryHandle, "GetTouchInputInfo");
+ closeTouchInputHandle = (CloseTouchInputHandlePtr)GetProcAddress(sLibraryHandle, "CloseTouchInputHandle");
+ }
+
+ if (!getGestureInfo || !closeGestureInfoHandle || !getGestureExtraArgs ||
+ !setGestureConfig || !getGestureConfig) {
+ getGestureInfo = nullptr;
+ closeGestureInfoHandle = nullptr;
+ getGestureExtraArgs = nullptr;
+ setGestureConfig = nullptr;
+ getGestureConfig = nullptr;
+ return false;
+ }
+
+ if (!registerTouchWindow || !unregisterTouchWindow || !getTouchInputInfo || !closeTouchInputHandle) {
+ registerTouchWindow = nullptr;
+ unregisterTouchWindow = nullptr;
+ getTouchInputInfo = nullptr;
+ closeTouchInputHandle = nullptr;
+ }
+
+ // panning feedback interfaces
+ if (hTheme) {
+ beginPanningFeedback = (BeginPanningFeedbackPtr)GetProcAddress(hTheme, "BeginPanningFeedback");
+ endPanningFeedback = (EndPanningFeedbackPtr)GetProcAddress(hTheme, "EndPanningFeedback");
+ updatePanningFeedback = (UpdatePanningFeedbackPtr)GetProcAddress(hTheme, "UpdatePanningFeedback");
+ }
+
+ if (!beginPanningFeedback || !endPanningFeedback || !updatePanningFeedback) {
+ beginPanningFeedback = nullptr;
+ endPanningFeedback = nullptr;
+ updatePanningFeedback = nullptr;
+ }
+
+ // 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)
+{
+ if (!getGestureInfo)
+ return false;
+
+ 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, GCOUNT, (PGESTURECONFIG)&config);
+}
+
+/* Helpers */
+
+bool nsWinGesture::IsAvailable()
+{
+ return getGestureInfo != nullptr;
+}
+
+bool nsWinGesture::RegisterTouchWindow(HWND hWnd)
+{
+ if (!registerTouchWindow)
+ return false;
+
+ return registerTouchWindow(hWnd, TWF_WANTPALM);
+}
+
+bool nsWinGesture::UnregisterTouchWindow(HWND hWnd)
+{
+ if (!unregisterTouchWindow)
+ return false;
+
+ return unregisterTouchWindow(hWnd);
+}
+
+bool nsWinGesture::GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs)
+{
+ if (!getTouchInputInfo)
+ return false;
+
+ return getTouchInputInfo(hTouchInput, cInputs, pInputs, sizeof(TOUCHINPUT));
+}
+
+bool nsWinGesture::CloseTouchInputHandle(HTOUCHINPUT hTouchInput)
+{
+ if (!closeTouchInputHandle)
+ return false;
+
+ return closeTouchInputHandle(hTouchInput);
+}
+
+bool nsWinGesture::GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo)
+{
+ if (!getGestureInfo || !hGestureInfo || !pGestureInfo)
+ return false;
+
+ ZeroMemory(pGestureInfo, sizeof(GESTUREINFO));
+ pGestureInfo->cbSize = sizeof(GESTUREINFO);
+
+ return getGestureInfo(hGestureInfo, pGestureInfo);
+}
+
+bool nsWinGesture::CloseGestureInfoHandle(HGESTUREINFO hGestureInfo)
+{
+ if (!getGestureInfo || !hGestureInfo)
+ return false;
+
+ return closeGestureInfoHandle(hGestureInfo);
+}
+
+bool nsWinGesture::GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs)
+{
+ if (!getGestureInfo || !hGestureInfo || !pExtraArgs)
+ return false;
+
+ return getGestureExtraArgs(hGestureInfo, cbExtraArgs, pExtraArgs);
+}
+
+bool nsWinGesture::SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig)
+{
+ if (!getGestureInfo || !pGestureConfig)
+ return false;
+
+ return setGestureConfig(hWnd, 0, cIDs, pGestureConfig, sizeof(GESTURECONFIG));
+}
+
+bool nsWinGesture::GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig)
+{
+ if (!getGestureInfo || !pGestureConfig)
+ return false;
+
+ return getGestureConfig(hWnd, 0, dwFlags, pcIDs, pGestureConfig, sizeof(GESTURECONFIG));
+}
+
+bool nsWinGesture::BeginPanningFeedback(HWND hWnd)
+{
+ if (!beginPanningFeedback)
+ return false;
+
+ return beginPanningFeedback(hWnd);
+}
+
+bool nsWinGesture::EndPanningFeedback(HWND hWnd)
+{
+ if (!beginPanningFeedback)
+ return false;
+
+ return endPanningFeedback(hWnd, TRUE);
+}
+
+bool nsWinGesture::UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia)
+{
+ if (!beginPanningFeedback)
+ return false;
+
+ return updatePanningFeedback(hWnd, offsetX, offsetY, fInInertia);
+}
+
+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 = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
+ else if (evt.mDelta < 0)
+ evt.mDirection = nsIDOMSimpleGestureEvent::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);
+ 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 = nsIDOMWheelEvent::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 000000000..24c1f6b2d
--- /dev/null
+++ b/widget/windows/nsWinGesture.h
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+// Desktop builds target apis for 502. Win8 Metro builds target 602.
+#if WINVER < 0x0602
+
+DECLARE_HANDLE(HGESTUREINFO);
+
+/*
+ * Gesture flags - GESTUREINFO.dwFlags
+ */
+#define GF_BEGIN 0x00000001
+#define GF_INERTIA 0x00000002
+#define GF_END 0x00000004
+
+/*
+ * Gesture configuration structure
+ * - Used in SetGestureConfig and GetGestureConfig
+ * - Note that any setting not included in either GESTURECONFIG.dwWant or
+ * GESTURECONFIG.dwBlock will use the parent window's preferences or
+ * system defaults.
+ */
+typedef struct tagGESTURECONFIG {
+ DWORD dwID; // gesture ID
+ DWORD dwWant; // settings related to gesture ID that are to be turned on
+ DWORD dwBlock; // settings related to gesture ID that are to be turned off
+} GESTURECONFIG, *PGESTURECONFIG;
+
+/*
+ * Gesture information structure
+ * - Pass the HGESTUREINFO received in the WM_GESTURE message lParam into the
+ * GetGestureInfo function to retrieve this information.
+ * - If cbExtraArgs is non-zero, pass the HGESTUREINFO received in the WM_GESTURE
+ * message lParam into the GetGestureExtraArgs function to retrieve extended
+ * argument information.
+ */
+typedef struct tagGESTUREINFO {
+ UINT cbSize; // size, in bytes, of this structure (including variable length Args field)
+ DWORD dwFlags; // see GF_* flags
+ DWORD dwID; // gesture ID, see GID_* defines
+ HWND hwndTarget; // handle to window targeted by this gesture
+ POINTS ptsLocation; // current location of this gesture
+ DWORD dwInstanceID; // internally used
+ DWORD dwSequenceID; // internally used
+ ULONGLONG ullArguments; // arguments for gestures whose arguments fit in 8 BYTES
+ UINT cbExtraArgs; // size, in bytes, of extra arguments, if any, that accompany this gesture
+} GESTUREINFO, *PGESTUREINFO;
+typedef GESTUREINFO const * PCGESTUREINFO;
+
+/*
+ * Gesture notification structure
+ * - The WM_GESTURENOTIFY message lParam contains a pointer to this structure.
+ * - The WM_GESTURENOTIFY message notifies a window that gesture recognition is
+ * in progress and a gesture will be generated if one is recognized under the
+ * current gesture settings.
+ */
+typedef struct tagGESTURENOTIFYSTRUCT {
+ UINT cbSize; // size, in bytes, of this structure
+ DWORD dwFlags; // unused
+ HWND hwndTarget; // handle to window targeted by the gesture
+ POINTS ptsLocation; // starting location
+ DWORD dwInstanceID; // internally used
+} GESTURENOTIFYSTRUCT, *PGESTURENOTIFYSTRUCT;
+
+
+/*
+ * Gesture argument helpers
+ * - Angle should be a double in the range of -2pi to +2pi
+ * - Argument should be an unsigned 16-bit value
+ */
+#define GID_ROTATE_ANGLE_TO_ARGUMENT(_arg_) ((USHORT)((((_arg_) + 2.0 * 3.14159265) / (4.0 * 3.14159265)) * 65535.0))
+#define GID_ROTATE_ANGLE_FROM_ARGUMENT(_arg_) ((((double)(_arg_) / 65535.0) * 4.0 * 3.14159265) - 2.0 * 3.14159265)
+
+/*
+ * Gesture configuration flags
+ */
+#define GC_ALLGESTURES 0x00000001
+
+#define GC_ZOOM 0x00000001
+
+#define GC_PAN 0x00000001
+#define GC_PAN_WITH_SINGLE_FINGER_VERTICALLY 0x00000002
+#define GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY 0x00000004
+#define GC_PAN_WITH_GUTTER 0x00000008
+#define GC_PAN_WITH_INERTIA 0x00000010
+
+#define GC_ROTATE 0x00000001
+
+#define GC_TWOFINGERTAP 0x00000001
+
+#define GC_PRESSANDTAP 0x00000001
+
+/*
+ * Gesture IDs
+ */
+#define GID_BEGIN 1
+#define GID_END 2
+#define GID_ZOOM 3
+#define GID_PAN 4
+#define GID_ROTATE 5
+#define GID_TWOFINGERTAP 6
+#define GID_PRESSANDTAP 7
+
+// Maximum number of gestures that can be included
+// in a single call to SetGestureConfig / GetGestureConfig
+#define GESTURECONFIGMAXCOUNT 256
+
+// If specified, GetGestureConfig returns consolidated configuration
+// for the specified window and it's parent window chain
+#define GCF_INCLUDE_ANCESTORS 0x00000001
+
+// Window events we need to respond to or receive
+#define WM_GESTURE 0x0119
+#define WM_GESTURENOTIFY 0x011A
+
+typedef struct _TOUCHINPUT {
+ LONG x;
+ LONG y;
+ HANDLE hSource;
+ DWORD dwID;
+ DWORD dwFlags;
+ DWORD dwMask;
+ DWORD dwTime;
+ ULONG_PTR dwExtraInfo;
+ DWORD cxContact;
+ DWORD cyContact;
+} TOUCHINPUT, *PTOUCHINPUT;
+
+typedef HANDLE HTOUCHINPUT;
+
+#define WM_TOUCH 0x0240
+
+#define TOUCHEVENTF_MOVE 0x0001
+#define TOUCHEVENTF_DOWN 0x0002
+#define TOUCHEVENTF_UP 0x0004
+#define TOUCHEVENTF_INRANGE 0x0008
+#define TOUCHEVENTF_PRIMARY 0x0010
+#define TOUCHEVENTF_NOCOALESCE 0x0020
+#define TOUCHEVENTF_PEN 0x0040
+#define TOUCHEVENTF_PALM 0x0080
+
+#define TOUCHINPUTMASKF_TIMEFROMSYSTEM 0x0001
+#define TOUCHINPUTMASKF_EXTRAINFO 0x0002
+#define TOUCHINPUTMASKF_CONTACTAREA 0x0004
+
+#define TOUCH_COORD_TO_PIXEL(C) (C/100)
+
+#define TWF_FINETOUCH 0x0001
+#define TWF_WANTPALM 0x0002
+
+#endif // WINVER < 0x0602
+
+// 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();
+ bool RegisterTouchWindow(HWND hWnd);
+ bool UnregisterTouchWindow(HWND hWnd);
+ bool GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs);
+ bool CloseTouchInputHandle(HTOUCHINPUT hTouchInput);
+ bool IsAvailable();
+
+ // 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);
+
+public:
+ // Helpers
+ bool GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo);
+ bool CloseGestureInfoHandle(HGESTUREINFO hGestureInfo);
+ bool GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs);
+ bool SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig);
+ bool GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig);
+ bool BeginPanningFeedback(HWND hWnd);
+ bool EndPanningFeedback(HWND hWnd);
+ bool UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia);
+
+protected:
+
+private:
+ // Function prototypes
+ typedef BOOL (WINAPI * GetGestureInfoPtr)(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo);
+ typedef BOOL (WINAPI * CloseGestureInfoHandlePtr)(HGESTUREINFO hGestureInfo);
+ typedef BOOL (WINAPI * GetGestureExtraArgsPtr)(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs);
+ typedef BOOL (WINAPI * SetGestureConfigPtr)(HWND hwnd, DWORD dwReserved, UINT cIDs, PGESTURECONFIG pGestureConfig, UINT cbSize);
+ typedef BOOL (WINAPI * GetGestureConfigPtr)(HWND hwnd, DWORD dwReserved, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig, UINT cbSize);
+ typedef BOOL (WINAPI * BeginPanningFeedbackPtr)(HWND hWnd);
+ typedef BOOL (WINAPI * EndPanningFeedbackPtr)(HWND hWnd, BOOL fAnimateBack);
+ typedef BOOL (WINAPI * UpdatePanningFeedbackPtr)(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia);
+ typedef BOOL (WINAPI * RegisterTouchWindowPtr)(HWND hWnd, ULONG flags);
+ typedef BOOL (WINAPI * UnregisterTouchWindowPtr)(HWND hWnd);
+ typedef BOOL (WINAPI * GetTouchInputInfoPtr)(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs, int32_t cbSize);
+ typedef BOOL (WINAPI * CloseTouchInputHandlePtr)(HTOUCHINPUT hTouchInput);
+
+ // Static function pointers
+ static GetGestureInfoPtr getGestureInfo;
+ static CloseGestureInfoHandlePtr closeGestureInfoHandle;
+ static GetGestureExtraArgsPtr getGestureExtraArgs;
+ static SetGestureConfigPtr setGestureConfig;
+ static GetGestureConfigPtr getGestureConfig;
+ static BeginPanningFeedbackPtr beginPanningFeedback;
+ static EndPanningFeedbackPtr endPanningFeedback;
+ static UpdatePanningFeedbackPtr updatePanningFeedback;
+ static RegisterTouchWindowPtr registerTouchWindow;
+ static UnregisterTouchWindowPtr unregisterTouchWindow;
+ static GetTouchInputInfoPtr getTouchInputInfo;
+ static CloseTouchInputHandlePtr closeTouchInputHandle;
+
+ // Delay load info
+ bool InitLibrary();
+
+ static HMODULE sLibraryHandle;
+ static const wchar_t kGestureLibraryName[];
+
+ // 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 000000000..2172f2aa0
--- /dev/null
+++ b/widget/windows/nsWindow.cpp
@@ -0,0 +1,8139 @@
+/* -*- 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 "gfxPrefs.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TouchEvents.h"
+
+#include "mozilla/ipc/MessageChannel.h"
+#include <algorithm>
+#include <limits>
+
+#include "nsWindow.h"
+
+#include <shellapi.h>
+#include <windows.h>
+#include <wtsapi32.h>
+#include <process.h>
+#include <commctrl.h>
+#include <unknwn.h>
+#include <psapi.h>
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "prenv.h"
+
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsIAppShell.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIKeyEventInPluginCallback.h"
+#include "nsITheme.h"
+#include "nsIObserverService.h"
+#include "nsIScreenManager.h"
+#include "imgIContainer.h"
+#include "nsIFile.h"
+#include "nsIRollupListener.h"
+#include "nsIServiceManager.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 "nsXPIDLString.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 "nsIWindowMediator.h"
+#include "nsIServiceManager.h"
+#include "nsWindowGfx.h"
+#include "gfxWindowsPlatform.h"
+#include "Layers.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Preferences.h"
+#include "nsISound.h"
+#include "SystemTimeConverter.h"
+#include "WinTaskbar.h"
+#include "WidgetUtils.h"
+#include "nsIWidgetListener.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/gfx/2D.h"
+#include "nsToolkitCompsCID.h"
+#include "nsIAppStartup.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/widget/WinNativeEventData.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsThemeConstants.h"
+#include "nsBidiKeyboard.h"
+#include "nsThemeConstants.h"
+#include "gfxConfig.h"
+#include "InProcessWinCompositorWidget.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/a11y/DocAccessible.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 "nsIXULRuntime.h"
+
+#include "nsIContent.h"
+
+#include "mozilla/HangMonitor.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/APZCTreeManager.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/ScrollInputMethods.h"
+#include "ClientLayerManager.h"
+#include "InputData.h"
+
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+
+/**************************************************************
+ **************************************************************
+ **
+ ** 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;
+
+// Trim heap on minimize. (initialized, but still true.)
+int nsWindow::sTrimOnMinimize = 2;
+
+TriStateBool nsWindow::sHasBogusPopupsDropShadowOnMultiMonitor = TRI_UNKNOWN;
+
+WPARAM nsWindow::sMouseExitwParam = 0;
+LPARAM nsWindow::sMouseExitlParamScreen = 0;
+
+static SystemTimeConverter<DWORD>&
+TimeConverter() {
+ static SystemTimeConverter<DWORD> timeConverterSingleton;
+ return timeConverterSingleton;
+}
+
+namespace mozilla {
+
+class CurrentWindowsTimeGetter {
+public:
+ 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;
+
+// Global used in Show window enumerations.
+static bool gWindowsVisible = false;
+
+// 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;
+
+// Cached pointer events enabler value, True if pointer events are enabled.
+static bool gIsPointerEventsEnabled = false;
+
+// 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) && defined(_M_IX86)
+
+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 = sTipTsfInterceptor.AddHook("ProcessCaretEvents",
+ reinterpret_cast<intptr_t>(&ProcessCaretEventsHook),
+ (void**) &sProcessCaretEventsStub);
+ MOZ_ASSERT(ok);
+ }
+
+ if (!sSendMessageTimeoutWStub) {
+ sUser32Intercept.Init("user32.dll");
+ DebugOnly<bool> hooked = sUser32Intercept.AddHook("SendMessageTimeoutW",
+ reinterpret_cast<intptr_t>(&SendMessageTimeoutWHook),
+ (void**) &sSendMessageTimeoutWStub);
+ 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 || 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 WINEVENTPROC sProcessCaretEventsStub;
+ static decltype(&SendMessageTimeoutW) sSendMessageTimeoutWStub;
+ static StaticAutoPtr<TIPMessageHandler> sInstance;
+
+ HHOOK mHook;
+ UINT mMessages[7];
+ uint32_t mA11yBlockCount;
+};
+
+WindowsDllInterceptor TIPMessageHandler::sTipTsfInterceptor;
+WINEVENTPROC TIPMessageHandler::sProcessCaretEventsStub;
+decltype(&SendMessageTimeoutW) TIPMessageHandler::sSendMessageTimeoutWStub;
+StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
+
+} // namespace mozilla
+
+#endif // defined(ACCESSIBILITY) && defined(_M_IX86)
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: nsIWidget impl.
+ **
+ ** nsIWidget interface implementation, broken down into
+ ** sections.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow construction and destruction
+ *
+ **************************************************************/
+
+nsWindow::nsWindow()
+ : nsWindowBase()
+ , mResizeState(NOT_RESIZING)
+{
+ mIconSmall = nullptr;
+ mIconBig = nullptr;
+ mWnd = nullptr;
+ mTransitionWnd = nullptr;
+ mPaintDC = nullptr;
+ mPrevWndProc = nullptr;
+ mNativeDragTarget = nullptr;
+ mInDtor = false;
+ mIsVisible = false;
+ mIsTopWidgetWindow = false;
+ mUnicodeWidget = true;
+ mDisplayPanFeedback = false;
+ mTouchWindow = false;
+ mFutureMarginsToUse = false;
+ mCustomNonClient = false;
+ mHideChrome = false;
+ mFullscreenMode = false;
+ mMousePresent = false;
+ mDestroyCalled = 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;
+ mBlurSuppressLevel = 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
+
+ mTaskbarPreview = 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) && defined(_M_IX86)
+ mozilla::TIPMessageHandler::Initialize();
+#endif // defined(ACCESSIBILITY) && defined(_M_IX86)
+ IMEHandler::Initialize();
+ if (SUCCEEDED(::OleInitialize(nullptr))) {
+ sIsOleInitialized = TRUE;
+ }
+ NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
+ MouseScrollHandler::Initialize();
+ // Init titlebar button info for custom frames.
+ nsUXThemeData::InitTitlebarInfo();
+ // Init theme data
+ nsUXThemeData::UpdateNativeThemeInfo();
+ RedirectedKeyDownMessageManager::Forget();
+ InkCollector::sInkCollector = new InkCollector();
+
+ Preferences::AddBoolVarCache(&gIsPointerEventsEnabled,
+ "dom.w3c_pointer_events.enabled",
+ gIsPointerEventsEnabled);
+ } // !sInstanceCount
+
+ mIdleService = nullptr;
+
+ mSizeConstraintsScale = GetDefaultScale().scale;
+
+ 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) {
+ InkCollector::sInkCollector->Shutdown();
+ InkCollector::sInkCollector = nullptr;
+ IMEHandler::Terminate();
+ NS_IF_RELEASE(sCursorImgContainer);
+ if (sIsOleInitialized) {
+ ::OleFlushClipboard();
+ ::OleUninitialize();
+ sIsOleInitialized = FALSE;
+ }
+ }
+
+ NS_IF_RELEASE(mNativeDragTarget);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
+
+/**************************************************************
+ *
+ * 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);
+}
+
+// Create the proper widget
+nsresult
+nsWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ nsWidgetInitData defaultInitData;
+ if (!aInitData)
+ aInitData = &defaultInitData;
+
+ mUnicodeWidget = aInitData->mUnicode;
+
+ 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;
+
+ DWORD style = WindowStyle();
+ DWORD extendedStyle = WindowExStyle();
+
+ if (mWindowType == eWindowType_popup) {
+ if (!aParent) {
+ parent = nullptr;
+ }
+
+ if (IsVistaOrLater() && !IsWin8OrLater() &&
+ HasBogusPopupsDropShadowOnMultiMonitor()) {
+ 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;
+ }
+ 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;
+ }
+
+ if (mIsRTL && WinUtils::dwmSetWindowAttributePtr) {
+ DWORD dwAttribute = TRUE;
+ WinUtils::dwmSetWindowAttributePtr(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute, sizeof dwAttribute);
+ }
+
+ 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;
+ if (mUnicodeWidget)
+ oldWndProc = (WNDPROC)::SetWindowLongPtrW(scrollableWnd, GWLP_WNDPROC,
+ (LONG_PTR)nsWindow::WindowProc);
+ else
+ oldWndProc = (WNDPROC)::SetWindowLongPtrA(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);
+
+ // If the internal variable set by the config.trim_on_minimize pref has not
+ // been initialized, and if this is the hidden window (conveniently created
+ // before any visible windows, and after the profile has been initialized),
+ // do some initialization work.
+ if (sTrimOnMinimize == 2 && mWindowType == eWindowType_invisible) {
+ // Our internal trim prevention logic is effective on 2K/XP at maintaining
+ // the working set when windows are minimized, but on Vista and up it has
+ // little to no effect. Since this feature has been the source of numerous
+ // bugs over the years, disable it (sTrimOnMinimize=1) on Vista and up.
+ sTrimOnMinimize =
+ Preferences::GetBool("config.trim_on_minimize",
+ IsVistaOrLater() ? 1 : 0);
+ sSwitchKeyboardLayout =
+ Preferences::GetBool("intl.keyboard.per_window_layout", false);
+ }
+
+ // 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);
+ }
+
+ 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);
+
+ /**
+ * 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();
+
+ // 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;
+ }
+ }
+ }
+
+ 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");
+ }
+
+ if (mUnicodeWidget) {
+ mPrevWndProc =
+ reinterpret_cast<WNDPROC>(
+ SetWindowLongPtrW(mWnd,
+ GWLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
+ } else {
+ mPrevWndProc =
+ reinterpret_cast<WNDPROC>(
+ SetWindowLongPtrA(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)) {
+ if (mUnicodeWidget) {
+ SetWindowLongPtrW(mWnd,
+ GWLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(mPrevWndProc));
+ } else {
+ SetWindowLongPtrA(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
+NS_IMETHODIMP 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 NS_OK;
+ }
+ if (mWnd) {
+ // If we have no parent, SetParent should return the desktop.
+ VERIFY(::SetParent(mWnd, nullptr));
+ }
+ return NS_OK;
+}
+
+void
+nsWindow::ReparentNativeWidget(nsIWidget* aNewParent)
+{
+ NS_PRECONDITION(aNewParent, "");
+
+ 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);
+ }
+}
+
+nsIWidget* nsWindow::GetParent(void)
+{
+ return GetParentWindow(false);
+}
+
+static int32_t RoundDown(double aDouble)
+{
+ return aDouble > 0 ? static_cast<int32_t>(floor(aDouble)) :
+ static_cast<int32_t>(ceil(aDouble));
+}
+
+float nsWindow::GetDPI()
+{
+ HDC dc = ::GetDC(mWnd);
+ if (!dc)
+ return 96.0f;
+
+ double heightInches = ::GetDeviceCaps(dc, VERTSIZE)/MM_PER_INCH_FLOAT;
+ int heightPx = ::GetDeviceCaps(dc, VERTRES);
+ ::ReleaseDC(mWnd, dc);
+ if (heightInches < 0.25) {
+ // Something's broken
+ return 96.0f;
+ }
+ return float(heightPx/heightInches);
+}
+
+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) {
+ ((nsWindow::WindowEnumCallback*)aParam)(wnd);
+ }
+ return TRUE;
+}
+
+BOOL CALLBACK
+nsWindow::EnumAllThreadWindowProc(HWND aWnd, LPARAM aParam)
+{
+ nsWindow *wnd = WinUtils::GetNSWindowPtr(aWnd);
+ if (wnd) {
+ ((nsWindow::WindowEnumCallback*)aParam)(wnd);
+ }
+ EnumChildWindows(aWnd, EnumAllChildWindProc, aParam);
+ return TRUE;
+}
+
+void
+nsWindow::EnumAllWindows(WindowEnumCallback aCallback)
+{
+ EnumThreadWindows(GetCurrentThreadId(),
+ EnumAllThreadWindowProc,
+ (LPARAM)aCallback);
+}
+
+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.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP nsWindow::Show(bool bState)
+{
+ 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 &&
+ !nsUXThemeData::CheckForCompositor())
+ {
+ 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;
+ 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()) {
+ ::ShowWindow(mWnd, SW_SHOWNORMAL);
+ } else {
+ ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
+ GetAttention(2);
+ }
+ break;
+ }
+ } else {
+ DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
+ if (wasVisible)
+ flags |= SWP_NOZORDER;
+
+ 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
+
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * 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.
+void nsWindow::ClearThemeRegion()
+{
+ if (IsVistaOrLater() && !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 (IsVistaOrLater() && !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;
+ mGesture.RegisterTouchWindow(mWnd);
+ ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
+}
+
+BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(aWnd);
+ if (win)
+ win->mGesture.RegisterTouchWindow(aWnd);
+ return TRUE;
+}
+
+/**************************************************************
+ *
+ * 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) {
+ 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);
+ }
+ ClientLayerManager *clientLayerManager = GetLayerManager()->AsClientLayerManager();
+
+ if (clientLayerManager) {
+ int32_t maxSize = clientLayerManager->GetMaxTextureSize();
+ // 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, maxSize);
+ c.mMaxSize.height = std::min(c.mMaxSize.height, maxSize);
+ }
+
+ 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
+NS_IMETHODIMP 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.x == x) && (mBounds.y == y))
+ {
+ // Nothing to do, since it is already positioned correctly.
+ return NS_OK;
+ }
+
+ mBounds.x = x;
+ mBounds.y = 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
+ 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 || mLayerManager->GetBackendType() == LayersBackend::LAYERS_D3D9) &&
+ 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();
+ }
+ NotifyRollupGeometryChange();
+ return NS_OK;
+}
+
+// Resize this component
+NS_IMETHODIMP 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.width == width && mBounds.height == height) {
+ if (aRepaint) {
+ Invalidate();
+ }
+ return NS_OK;
+ }
+
+ // Set cached value for lightweight and printing
+ mBounds.width = width;
+ mBounds.height = height;
+
+ if (mWnd) {
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE;
+
+ if (!aRepaint) {
+ flags |= SWP_NOREDRAW;
+ }
+
+ ClearThemeRegion();
+ double oldScale = mDefaultScale;
+ VERIFY(::SetWindowPos(mWnd, nullptr, 0, 0,
+ width, GetHeight(height), flags));
+ if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
+ ChangedDPI();
+ }
+ SetThemeRegion();
+ }
+
+ if (aRepaint)
+ Invalidate();
+
+ NotifyRollupGeometryChange();
+ return NS_OK;
+}
+
+// Resize this component
+NS_IMETHODIMP 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.x == x && mBounds.y == y &&
+ mBounds.width == width && mBounds.height == height) {
+ if (aRepaint) {
+ Invalidate();
+ }
+ return NS_OK;
+ }
+
+ // Set cached value for lightweight and printing
+ mBounds.x = x;
+ mBounds.y = y;
+ mBounds.width = width;
+ mBounds.height = height;
+
+ if (mWnd) {
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE;
+ if (!aRepaint) {
+ flags |= SWP_NOREDRAW;
+ }
+
+ ClearThemeRegion();
+ double oldScale = mDefaultScale;
+ VERIFY(::SetWindowPos(mWnd, nullptr, x, y,
+ width, GetHeight(height), flags));
+ 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);
+ // Every transition window is only used once.
+ mTransitionWnd = nullptr;
+ }
+ SetThemeRegion();
+ }
+
+ if (aRepaint)
+ Invalidate();
+
+ NotifyRollupGeometryChange();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+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()->button != WidgetMouseEvent::eLeftButton) {
+ // 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;
+
+ // 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 :
+ // Using SW_SHOWMINIMIZED prevents the working set from being trimmed but
+ // keeps the window active in the tray. So after the window is minimized,
+ // windows will fire WM_WINDOWPOSCHANGED (OnWindowPosChanged) at which point
+ // we will do some additional processing to get the active window set right.
+ // If sTrimOnMinimize is set, we let windows handle minimization normally
+ // using SW_MINIMIZE.
+ mode = sTrimOnMinimize ? SW_MINIMIZE : SW_SHOWMINIMIZED;
+ 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);
+ }
+}
+
+// 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
+NS_IMETHODIMP nsWindow::Enable(bool bState)
+{
+ if (mWnd) {
+ ::EnableWindow(mWnd, bState);
+ }
+ return NS_OK;
+}
+
+// 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.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP nsWindow::SetFocus(bool aRaise)
+{
+ if (mWnd) {
+#ifdef WINSTATE_DEBUG_OUTPUT
+ if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** SetFocus: [ top] raise=%d\n", aRaise));
+ } else {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** SetFocus: [child] raise=%d\n", aRaise));
+ }
+#endif
+ // Uniconify, if necessary
+ HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd);
+ if (aRaise && ::IsIconic(toplevelWnd)) {
+ ::ShowWindow(toplevelWnd, SW_RESTORE);
+ }
+ ::SetFocus(mWnd);
+ }
+ return NS_OK;
+}
+
+
+/**************************************************************
+ *
+ * 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.width = r.right - r.left;
+ rect.height = r.bottom - r.top;
+
+ // popup window bounds' are in screen coordinates, not relative to parent
+ // window
+ if (mWindowType == eWindowType_popup) {
+ rect.x = r.left;
+ rect.y = 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.x = r.left;
+ rect.y = 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.width = r.right - r.left;
+ rect.height = 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));
+
+ LayoutDeviceIntRect rect;
+ rect.x = r.left;
+ rect.y = r.top;
+ rect.width = r.right - r.left;
+ rect.height = r.bottom - r.top;
+ return rect;
+}
+
+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);
+ nsIntRect evRect(WinUtils::ToIntRect(clientRc));
+ OnResize(evRect);
+
+ // 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 GetWindowInfoPtr sGetWindowInfoPtrStub = nullptr;
+
+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;
+
+ if (!sGetWindowInfoPtrStub) {
+ sUser32Intercept.Init("user32.dll");
+ if (!sUser32Intercept.AddHook("GetWindowInfo", reinterpret_cast<intptr_t>(GetWindowInfoHook),
+ (void**) &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));
+
+ // 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
+ // `GetSystemMetrics(SM_CYFRAME)`
+ mCaptionHeight = GetSystemMetrics(SM_CYFRAME)
+ + (hasCaption ? GetSystemMetrics(SM_CYCAPTION)
+ + GetSystemMetrics(SM_CXPADDEDBORDER)
+ : 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
+ // `GetSystemMetrics(SM_CXFRAME)`
+ mHorResizeMargin = GetSystemMetrics(SM_CXFRAME)
+ + (hasCaption ? GetSystemMetrics(SM_CXPADDEDBORDER) : 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
+ // `GetSystemMetrics(SM_CYFRAME)`
+ mVertResizeMargin = GetSystemMetrics(SM_CYFRAME)
+ + (hasCaption ? GetSystemMetrics(SM_CXPADDEDBORDER) : 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 = nsUXThemeData::CheckForCompositor();
+
+ // 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;
+}
+
+NS_IMETHODIMP
+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
+NS_IMETHODIMP nsWindow::SetCursor(nsCursor aCursor)
+{
+ // Only change cursor if it's changing
+
+ //XXX mCursor isn't always right. Scrollbars and others change it, too.
+ //XXX If we want this optimization we need a better way to do it.
+ //if (aCursor != mCursor) {
+ HCURSOR newCursor = nullptr;
+
+ switch (aCursor) {
+ case eCursor_select:
+ newCursor = ::LoadCursor(nullptr, IDC_IBEAM);
+ break;
+
+ case eCursor_wait:
+ newCursor = ::LoadCursor(nullptr, IDC_WAIT);
+ break;
+
+ case eCursor_hyperlink:
+ {
+ newCursor = ::LoadCursor(nullptr, IDC_HAND);
+ break;
+ }
+
+ case eCursor_standard:
+ case eCursor_context_menu: // XXX See bug 258960.
+ newCursor = ::LoadCursor(nullptr, IDC_ARROW);
+ break;
+
+ case eCursor_n_resize:
+ case eCursor_s_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENS);
+ break;
+
+ case eCursor_w_resize:
+ case eCursor_e_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZEWE);
+ break;
+
+ case eCursor_nw_resize:
+ case eCursor_se_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENWSE);
+ break;
+
+ case eCursor_ne_resize:
+ case eCursor_sw_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENESW);
+ break;
+
+ case eCursor_crosshair:
+ newCursor = ::LoadCursor(nullptr, IDC_CROSS);
+ break;
+
+ case eCursor_move:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZEALL);
+ break;
+
+ case eCursor_help:
+ newCursor = ::LoadCursor(nullptr, IDC_HELP);
+ break;
+
+ case eCursor_copy: // CSS3
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY));
+ break;
+
+ case eCursor_alias:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS));
+ break;
+
+ case eCursor_cell:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL));
+ break;
+
+ case eCursor_grab:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB));
+ break;
+
+ case eCursor_grabbing:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRABBING));
+ break;
+
+ case eCursor_spinning:
+ newCursor = ::LoadCursor(nullptr, IDC_APPSTARTING);
+ break;
+
+ case eCursor_zoom_in:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN));
+ break;
+
+ case eCursor_zoom_out:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMOUT));
+ break;
+
+ case eCursor_not_allowed:
+ case eCursor_no_drop:
+ newCursor = ::LoadCursor(nullptr, IDC_NO);
+ break;
+
+ case eCursor_col_resize:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COLRESIZE));
+ break;
+
+ case eCursor_row_resize:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ROWRESIZE));
+ break;
+
+ case eCursor_vertical_text:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_VERTICALTEXT));
+ break;
+
+ case eCursor_all_scroll:
+ // XXX not 100% appropriate perhaps
+ newCursor = ::LoadCursor(nullptr, IDC_SIZEALL);
+ break;
+
+ case eCursor_nesw_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENESW);
+ break;
+
+ case eCursor_nwse_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENWSE);
+ break;
+
+ case eCursor_ns_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENS);
+ break;
+
+ case eCursor_ew_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZEWE);
+ break;
+
+ case eCursor_none:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE));
+ break;
+
+ default:
+ NS_ERROR("Invalid cursor type");
+ break;
+ }
+
+ if (nullptr != newCursor) {
+ mCursor = aCursor;
+ HCURSOR oldCursor = ::SetCursor(newCursor);
+
+ if (sHCursor == oldCursor) {
+ NS_IF_RELEASE(sCursorImgContainer);
+ if (sHCursor != nullptr)
+ ::DestroyIcon(sHCursor);
+ sHCursor = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Setting the actual cursor
+NS_IMETHODIMP nsWindow::SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY)
+{
+ if (sCursorImgContainer == aCursor && sHCursor) {
+ ::SetCursor(sHCursor);
+ return NS_OK;
+ }
+
+ int32_t width;
+ int32_t height;
+
+ nsresult rv;
+ rv = aCursor->GetWidth(&width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aCursor->GetHeight(&height);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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 NS_ERROR_NOT_AVAILABLE;
+
+ HCURSOR cursor;
+ double scale = GetDefaultScale().scale;
+ IntSize size = RoundedToInt(Size(width * scale, height * scale));
+ rv = nsWindowGfx::CreateIcon(aCursor, true, aHotspotX, aHotspotY, size, &cursor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCursor = nsCursor(-1);
+ ::SetCursor(cursor);
+
+ NS_IF_RELEASE(sCursorImgContainer);
+ sCursorImgContainer = aCursor;
+ NS_ADDREF(sCursorImgContainer);
+
+ if (sHCursor != nullptr)
+ ::DestroyIcon(sHCursor);
+ sHCursor = cursor;
+
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Get/SetTransparencyMode
+ *
+ * Manage the transparency mode of the top-level window
+ * containing this widget.
+ *
+ **************************************************************/
+
+#ifdef MOZ_XUL
+nsTransparencyMode nsWindow::GetTransparencyMode()
+{
+ return GetTopLevelWindow(true)->GetWindowTranslucencyInner();
+}
+
+void nsWindow::SetTransparencyMode(nsTransparencyMode aMode)
+{
+ GetTopLevelWindow(true)->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.y = std::max<uint32_t>(largest.y,
+ nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].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 (nsUXThemeData::CheckForCompositor()) {
+ WinUtils::dwmExtendFrameIntoClientAreaPtr(mWnd, &margins);
+ WinUtils::dwmSetWindowAttributePtr(mWnd, DWMWA_NCRENDERING_POLICY, &policy, sizeof policy);
+ }
+}
+#endif
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::HideWindowChrome
+ *
+ * Show or hide window chrome.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP 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 NS_ERROR_FAILURE;
+ }
+
+ if (mHideChrome == aShouldHide)
+ return NS_OK;
+
+ 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);
+
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsWindow::Invalidate
+ *
+ * Invalidate an area of the client for painting.
+ *
+ **************************************************************/
+
+// Invalidate this component visible area
+NS_IMETHODIMP nsWindow::Invalidate(bool aEraseBackground,
+ bool aUpdateNCArea,
+ bool aIncludeChildren)
+{
+ if (!mWnd) {
+ return NS_OK;
+ }
+
+#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));
+ return NS_OK;
+}
+
+// Invalidate this component visible area
+NS_IMETHODIMP 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.x + aRect.width;
+ rect.bottom = aRect.y + aRect.height;
+
+ VERIFY(::InvalidateRect(mWnd, &rect, FALSE));
+ }
+ return NS_OK;
+}
+
+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 (!nsUXThemeData::CheckForCompositor()) {
+ 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.x = NSToIntRound(x * scale);
+ initData.mBounds.y = NSToIntRound(y * scale);
+ initData.mBounds.width = NSToIntRound(width * scale);
+ initData.mBounds.height = 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);
+}
+
+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);
+
+ 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);
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeMode);
+ mWidgetListener->FullscreenChanged(aFullScreen);
+ }
+
+ // Send a eMouseEnterIntoWidget event since Windows has already sent
+ // a WM_MOUSELEAVE that caused us to send a eMouseExitFromWidget event.
+ if (aFullScreen && !sCurrentWindow) {
+ sCurrentWindow = this;
+ LPARAM pos = sCurrentWindow->lParamToClient(sMouseExitlParamScreen);
+ sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget,
+ sMouseExitwParam, pos, false,
+ WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ }
+
+ 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:
+ 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;
+ }
+ MOZ_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:
+ SetChildStyleAndParent(reinterpret_cast<HWND>(aVal), mWnd);
+ break;
+ case NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW:
+ SetChildStyleAndParent(reinterpret_cast<HWND>(aVal),
+ WinUtils::GetTopLevelHWND(mWnd));
+ 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.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP 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.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP nsWindow::SetIcon(const nsAString& aIconSpec)
+{
+ // Assume the given string is a local identifier for an icon file.
+
+ nsCOMPtr<nsIFile> iconFile;
+ ResolveIconName(aIconSpec, NS_LITERAL_STRING(".ico"),
+ getter_AddRefs(iconFile));
+ if (!iconFile)
+ return NS_OK; // not an error if icon is not found
+
+ 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) {
+ HICON icon = (HICON) ::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)bigIcon);
+ if (icon)
+ ::DestroyIcon(icon);
+ mIconBig = 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) {
+ HICON icon = (HICON) ::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)smallIcon);
+ if (icon)
+ ::DestroyIcon(icon);
+ mIconSmall = 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
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * 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)
+{
+ NS_ASSERTION(mWnd, "nsWindow::EnableDragDrop() called after Destroy()");
+
+ nsresult rv = NS_ERROR_FAILURE;
+ 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.
+NS_IMETHODIMP
+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)
+{
+ 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 TabChild
+ // 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.
+ CompositorWidgetInitData initData(
+ reinterpret_cast<uintptr_t>(mWnd),
+ reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
+ mTransparencyMode);
+ mBasicLayersSurface = new InProcessWinCompositorWidget(initData, this);
+ mCompositorWidgetDelegate = mBasicLayersSurface;
+ mLayerManager = CreateBasicLayerManager();
+ }
+
+ NS_ASSERTION(mLayerManager, "Couldn't provide a valid layer manager.");
+
+ return mLayerManager;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::OnDefaultButtonLoaded
+ *
+ * Called after the dialog is loaded and it has a default button.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP
+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() || !nsUXThemeData::CheckForCompositor()) {
+ // 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));
+ }
+
+ if (!IsWin10OrLater()) {
+ for (size_t i = 0; i < aThemeGeometries.Length(); i++) {
+ if (aThemeGeometries[i].mType == nsNativeThemeWin::eThemeGeometryTypeWindowButtons) {
+ LayoutDeviceIntRect bounds = aThemeGeometries[i].mRect;
+ 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() - 1.0, 1.0));
+ clearRegion.Or(clearRegion, gfx::IntRect::Truncate(bounds.X() + 2.0, bounds.YMost() - 1.0, bounds.Width() - 3.0, 1.0));
+ }
+ }
+ }
+
+ layerManager->SetRegionToClear(clearRegion);
+}
+
+uint32_t
+nsWindow::GetMaxTouchPoints() const
+{
+ return WinUtils::GetMaxTouchPoints();
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** 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.
+NS_IMETHODIMP 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;
+}
+
+// Deal with all sort of mouse event
+bool
+nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
+ LPARAM lParam, bool aIsContextMenuKey,
+ int16_t aButton, uint16_t aInputSource,
+ uint16_t aPointerId)
+{
+ 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::Accumulate(Telemetry::FX_TOUCH_USED, 1);
+ }
+
+ if (mTouchWindow) {
+ // If mTouchWindow is true, then we must have APZ enabled and be
+ // feeding it raw touch events. In that case we don't need to
+ // send touch-generated mouse events to content. The only exception is
+ // the touch-generated mouse double-click, which is used to start off the
+ // touch-based drag-and-drop gesture.
+ MOZ_ASSERT(mAPZC);
+ if (aEventMessage == eMouseDoubleClick) {
+ aEventMessage = eMouseTouchDrag;
+ } else {
+ return result;
+ }
+ }
+ }
+
+ // Since it is unclear whether a user will use the digitizer,
+ // Postpone initialization until first PEN message will be found.
+ if (nsIDOMMouseEvent::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.
+ && gfxPrefs::PointerEventsEnabled()) {
+ InkCollector::sInkCollector->SetTarget(mWnd);
+ InkCollector::sInkCollector->SetPointerId(aPointerId);
+ }
+
+ 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);
+ event.button = aButton;
+ event.inputSource = aInputSource;
+ event.pointerId = aPointerId;
+ // If we get here the mouse events must be from non-touch sources, so
+ // convert it to pointer events as well
+ event.convertToPointer = true;
+
+ 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 WidgetMouseEvent::eLeftButton:
+ eventButton = VK_LBUTTON;
+ break;
+ case WidgetMouseEvent::eMiddleButton:
+ eventButton = VK_MBUTTON;
+ break;
+ case WidgetMouseEvent::eRightButton:
+ 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.button = 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 =
+ IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::eTopLevel :
+ WidgetMouseEvent::eChild;
+ 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 WidgetMouseEvent::eLeftButton:
+ pluginEvent.event = WM_LBUTTONDOWN;
+ break;
+ case WidgetMouseEvent::eMiddleButton:
+ pluginEvent.event = WM_MBUTTONDOWN;
+ break;
+ case WidgetMouseEvent::eRightButton:
+ pluginEvent.event = WM_RBUTTONDOWN;
+ break;
+ default:
+ break;
+ }
+ break;
+ case eMouseUp:
+ switch (aButton) {
+ case WidgetMouseEvent::eLeftButton:
+ pluginEvent.event = WM_LBUTTONUP;
+ break;
+ case WidgetMouseEvent::eMiddleButton:
+ pluginEvent.event = WM_MBUTTONUP;
+ break;
+ case WidgetMouseEvent::eRightButton:
+ pluginEvent.event = WM_RBUTTONUP;
+ break;
+ default:
+ break;
+ }
+ break;
+ case eMouseDoubleClick:
+ switch (aButton) {
+ case WidgetMouseEvent::eLeftButton:
+ pluginEvent.event = WM_LBUTTONDBLCLK;
+ break;
+ case WidgetMouseEvent::eMiddleButton:
+ pluginEvent.event = WM_MBUTTONDBLCLK;
+ break;
+ case WidgetMouseEvent::eRightButton:
+ 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.x = 0;
+ rect.y = 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,
+ WidgetMouseEvent::eLeftButton,
+ aInputSource, aPointerId);
+ }
+ sCurrentWindow = this;
+ if (!mInDtor) {
+ LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
+ sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget,
+ wParam, pos, false,
+ WidgetMouseEvent::eLeftButton,
+ aInputSource, aPointerId);
+ }
+ }
+ }
+ } else if (aEventMessage == eMouseExitFromWidget) {
+ sMouseExitwParam = wParam;
+ sMouseExitlParamScreen = lParamToScreen(lParam);
+ 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;
+}
+
+void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate)
+{
+ if (aIsActivate)
+ sJustGotActivate = false;
+ sJustGotDeactivate = false;
+
+ // retrive the toplevel window or dialog
+ HWND curWnd = mWnd;
+ HWND toplevelWnd = nullptr;
+ while (curWnd) {
+ toplevelWnd = curWnd;
+
+ nsWindow *win = WinUtils::GetNSWindowPtr(curWnd);
+ if (win) {
+ nsWindowType wintype = win->WindowType();
+ if (wintype == eWindowType_toplevel || wintype == eWindowType_dialog)
+ break;
+ }
+
+ curWnd = ::GetParent(curWnd); // Parent or owner (if has no parent)
+ }
+
+ if (toplevelWnd) {
+ nsWindow *win = WinUtils::GetNSWindowPtr(toplevelWnd);
+ if (win && win->mWidgetListener) {
+ if (aIsActivate) {
+ win->mWidgetListener->WindowActivated();
+ } else {
+ if (!win->BlurEventsSuppressed()) {
+ win->mWidgetListener->WindowDeactivated();
+ }
+ }
+ }
+ }
+}
+
+bool nsWindow::IsTopLevelMouseExit(HWND aWnd)
+{
+ DWORD pos = ::GetMessagePos();
+ POINT mp;
+ mp.x = GET_X_LPARAM(pos);
+ mp.y = GET_Y_LPARAM(pos);
+ HWND mouseWnd = ::WindowFromPoint(mp);
+
+ // 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::BlurEventsSuppressed()
+{
+ // are they suppressed in this window?
+ if (mBlurSuppressLevel > 0)
+ return true;
+
+ // are they suppressed by any container widget?
+ HWND parentWnd = ::GetParent(mWnd);
+ if (parentWnd) {
+ nsWindow *parent = WinUtils::GetNSWindowPtr(parentWnd);
+ if (parent)
+ return parent->BlurEventsSuppressed();
+ }
+ return false;
+}
+
+// In some circumstances (opening dependent windows) it makes more sense
+// (and fixes a crash bug) to not blur the parent window. Called from
+// nsFilePicker.
+void nsWindow::SuppressBlurEvents(bool aSuppress)
+{
+ if (aSuppress)
+ ++mBlurSuppressLevel; // for this widget
+ else {
+ NS_ASSERTION(mBlurSuppressLevel > 0, "unbalanced blur event suppression");
+ if (mBlurSuppressLevel > 0)
+ --mBlurSuppressLevel;
+ }
+}
+
+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;
+ }
+ 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;
+}
+
+inline static mozilla::HangMonitor::ActivityType ActivityTypeForMessage(UINT msg)
+{
+ if ((msg >= WM_KEYFIRST && msg <= WM_IME_KEYLAST) ||
+ (msg >= WM_MOUSEFIRST && msg <= WM_MOUSELAST) ||
+ (msg >= MOZ_WM_MOUSEWHEEL_FIRST && msg <= MOZ_WM_MOUSEWHEEL_LAST) ||
+ (msg >= NS_WM_IMEFIRST && msg <= NS_WM_IMELAST)) {
+ return mozilla::HangMonitor::kUIActivity;
+ }
+
+ // This may not actually be right, but we don't want to reset the timer if
+ // we're not actually processing a UI message.
+ return mozilla::HangMonitor::kActivityUIAVail;
+}
+
+// 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();
+
+ HangMonitor::NotifyActivity(ActivityTypeForMessage(msg));
+
+ 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;
+}
+
+// The main windows message processing method for plugins.
+// The result means whether this method processed the native
+// event for plugin. If false, the native event should be
+// processed by the caller self.
+bool
+nsWindow::ProcessMessageForPlugin(const MSG &aMsg,
+ MSGResult& aResult)
+{
+ aResult.mResult = 0;
+ aResult.mConsumed = true;
+
+ bool eventDispatched = false;
+ switch (aMsg.message) {
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ aResult.mResult = ProcessCharMessage(aMsg, &eventDispatched);
+ break;
+
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ aResult.mResult = ProcessKeyUpMessage(aMsg, &eventDispatched);
+ break;
+
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ aResult.mResult = ProcessKeyDownMessage(aMsg, &eventDispatched);
+ break;
+
+ case WM_DEADCHAR:
+ case WM_SYSDEADCHAR:
+
+ case WM_CUT:
+ case WM_COPY:
+ case WM_PASTE:
+ case WM_CLEAR:
+ case WM_UNDO:
+ break;
+
+ default:
+ return false;
+ }
+
+ if (!eventDispatched) {
+ aResult.mConsumed = nsWindowBase::DispatchPluginEvent(aMsg);
+ }
+ if (!Destroyed()) {
+ DispatchPendingEvents();
+ }
+ return true;
+}
+
+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);
+}
+
+static bool CleartypeSettingChanged()
+{
+ static int currentQuality = -1;
+ BYTE quality = cairo_win32_get_system_text_quality();
+
+ if (currentQuality == quality)
+ return false;
+
+ if (currentQuality < 0) {
+ currentQuality = quality;
+ return false;
+ }
+ currentQuality = quality;
+ return true;
+}
+
+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;
+ }
+
+ if (PluginHasFocus()) {
+ MSG nativeMsg = WinUtils::InitMSG(aMessage, aWParam, aLParam, mWnd);
+ if (ProcessMessageForPlugin(nativeMsg, 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 &&
+ nsUXThemeData::CheckForCompositor() &&
+ /* We don't do this for win10 glass with a custom titlebar,
+ * in order to avoid the caption buttons breaking. */
+ !(IsWin10OrLater() && HasGlass()) &&
+ WinUtils::dwmDwmDefWindowProcPtr(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);
+ obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr);
+
+ bool abortQuit;
+ cancelQuit->GetData(&abortQuit);
+ sCanQuit = abortQuit ? TRI_FALSE : TRI_TRUE;
+ }
+ *aRetValue = sCanQuit ? TRUE : FALSE;
+ result = true;
+ 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();
+ NS_NAMED_LITERAL_STRING(context, "shutdown-persist");
+ NS_NAMED_LITERAL_STRING(syncShutdown, "syncShutdown");
+ obsServ->NotifyObservers(nullptr, "quit-application-granted", syncShutdown.get());
+ obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
+ obsServ->NotifyObservers(nullptr, "quit-application", nullptr);
+ obsServ->NotifyObservers(nullptr, "profile-change-net-teardown", context.get());
+ obsServ->NotifyObservers(nullptr, "profile-change-teardown", context.get());
+ obsServ->NotifyObservers(nullptr, "profile-before-change", context.get());
+ obsServ->NotifyObservers(nullptr, "profile-before-change-qm", context.get());
+ obsServ->NotifyObservers(nullptr, "profile-before-change-telemetry", context.get());
+ // Then a controlled but very quick exit.
+ _exit(0);
+ }
+ sCanQuit = TRI_UNKNOWN;
+ result = true;
+ break;
+
+ case WM_SYSCOLORCHANGE:
+ OnSysColorChanged();
+ break;
+
+ case WM_THEMECHANGED:
+ {
+ // Update non-client margin offsets
+ UpdateNonClientMargins();
+ nsUXThemeData::InitTitlebarInfo();
+ nsUXThemeData::UpdateNativeThemeInfo();
+
+ NotifyThemeChanged();
+
+ // 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 (IsWin10OrLater() && mWindowType == eWindowType_invisible && lParam) {
+ auto lParamString = reinterpret_cast<const wchar_t*>(lParam);
+ if (!wcscmp(lParamString, L"UserInteractionMode")) {
+ nsCOMPtr<nsIWindowsUIUtils> uiUtils(do_GetService("@mozilla.org/windows-ui-utils;1"));
+ if (uiUtils) {
+ uiUtils->UpdateTabletModeState();
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_NCCALCSIZE:
+ {
+ 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));
+ double scale = WinUtils::IsPerMonitorDPIAware()
+ ? WinUtils::LogToPhysFactor(mWnd) / WinUtils::SystemScaleFactor()
+ : 1.0;
+ clientRect->top +=
+ NSToIntRound((mCaptionHeight - mNonClientOffset.top) * scale);
+ clientRect->left +=
+ NSToIntRound((mHorResizeMargin - mNonClientOffset.left) * scale);
+ clientRect->right -=
+ NSToIntRound((mHorResizeMargin - mNonClientOffset.right) * scale);
+ clientRect->bottom -=
+ NSToIntRound((mVertResizeMargin - mNonClientOffset.bottom) * scale);
+
+ 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 && nsUXThemeData::CheckForCompositor()) ||
+ !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(this, &nsWindow::ForcePresent));
+ }
+
+ // let the dwm handle nc painting on glass
+ // Never allow native painting if we are on fullscreen
+ if(mSizeMode != nsSizeMode_Fullscreen &&
+ nsUXThemeData::CheckForCompositor())
+ 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:
+ {
+ /*
+ * 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(nsUXThemeData::CheckForCompositor())
+ 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.
+ OnDestroy();
+ result = true;
+ break;
+
+ case WM_PAINT:
+ if (CleartypeSettingChanged()) {
+ ForceFontUpdate();
+ gfxFontCache *fc = gfxFontCache::GetCache();
+ if (fc) {
+ fc->Flush();
+ }
+ }
+ *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:
+ {
+ 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.
+ LPARAM lParamScreen = lParamToScreen(lParam);
+ 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, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ if (userMovedMouse) {
+ DispatchPendingEvents();
+ }
+ }
+ break;
+
+ case WM_NCMOUSEMOVE:
+ // If we receive a mouse move event on non-client chrome, make sure and
+ // send an eMouseExitFromWidget event as well.
+ if (mMousePresent && !sIsInMouseCapture)
+ SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
+ break;
+
+ case WM_LBUTTONDOWN:
+ {
+ result = DispatchMouseEvent(eMouseDown, wParam, lParam,
+ false, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ {
+ result = DispatchMouseEvent(eMouseUp, wParam, lParam,
+ false, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ }
+ break;
+
+ case WM_MOUSELEAVE:
+ {
+ if (!mMousePresent)
+ break;
+ mMousePresent = false;
+
+ // 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,
+ WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ }
+ break;
+
+ case MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER:
+ {
+ LPARAM pos = lParamToClient(::GetMessagePos());
+ uint16_t pointerId = InkCollector::sInkCollector->GetPointerId();
+ if (pointerId != 0) {
+ DispatchMouseEvent(eMouseExitFromWidget, wParam, pos, false,
+ WidgetMouseEvent::eLeftButton,
+ nsIDOMMouseEvent::MOZ_SOURCE_PEN, pointerId);
+ 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() == nsIDOMMouseEvent::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 ?
+ WidgetMouseEvent::eLeftButton :
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ 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_LBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam,
+ lParam, false,
+ WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, wParam,
+ lParam, false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, wParam,
+ lParam, false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam,
+ lParam, false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, wParam,
+ lParam, false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, wParam,
+ lParam, false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam,
+ lParam, false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ 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:
+ {
+ // 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;
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, "live-resize-start",
+ nullptr);
+ }
+ }
+ 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,
+ // which 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.
+ ChangedDPI();
+ ResetLayout();
+ }
+ break;
+
+ case WM_ENTERSIZEMOVE:
+ {
+ if (mResizeState == NOT_RESIZING) {
+ mResizeState = IN_SIZEMOVE;
+ }
+ break;
+ }
+
+ case WM_EXITSIZEMOVE:
+ {
+ FinishLiveResizing(NOT_RESIZING);
+
+ if (!sIsInMouseCapture) {
+ NotifySizeMoveDone();
+ }
+
+ break;
+ }
+
+ case WM_NCLBUTTONDBLCLK:
+ DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
+ false, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ result =
+ DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam),
+ false, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ 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.
+ if (HIWORD(wParam))
+ 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);
+ }
+ 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
+ a11y::Accessible* rootAccessible = GetAccessible(); // Held by a11y cache
+ if (rootAccessible) {
+ IAccessible *msaaAccessible = nullptr;
+ rootAccessible->GetNativeInterface((void**)&msaaAccessible); // does an addref
+ if (msaaAccessible) {
+ *aRetValue = LresultFromObject(IID_IAccessible, wParam, msaaAccessible); // does an addref
+ msaaAccessible->Release(); // release extra addref
+ result = true; // We handled the WM_GETOBJECT message
+ }
+ }
+ }
+ }
+ break;
+#endif
+
+ case WM_SYSCOMMAND:
+ {
+ WPARAM filteredWParam = (wParam &0xFFF0);
+ // prevent Windows from trimming the working set. bug 76831
+ if (!sTrimOnMinimize && filteredWParam == SC_MINIMIZE) {
+ ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
+ result = true;
+ }
+
+ 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:
+ // First, update the compositor state to latest one. All other methods
+ // should use same state as here for consistency painting.
+ nsUXThemeData::CheckForCompositor(true);
+
+ UpdateNonClientMargins();
+ BroadcastMsg(mWnd, WM_DWMCOMPOSITIONCHANGED);
+ NotifyThemeChanged();
+ 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 showAccelerators = UIStateChangeType_NoChange;
+ UIStateChangeType showFocusRings = UIStateChangeType_NoChange;
+ if (flags & UISF_HIDEACCEL)
+ showAccelerators = (action == UIS_SET) ? UIStateChangeType_Clear : UIStateChangeType_Set;
+ if (flags & UISF_HIDEFOCUS)
+ showFocusRings = (action == UIS_SET) ? UIStateChangeType_Clear : UIStateChangeType_Set;
+
+ NotifyUIStateChanged(showAccelerators, 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) {
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, "live-resize-end", nullptr);
+ }
+ }
+ 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;
+ }
+ if (mDraggableRegion.Contains(pt.x, pt.y)) {
+ testResult = HTCAPTION;
+ } else {
+ testResult = HTCLIENT;
+ }
+ mCachedHitTestPoint = pt;
+ mCachedHitTestTime = TimeStamp::Now();
+ mCachedHitTestResult = testResult;
+ }
+
+ return testResult;
+}
+
+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);
+ return static_cast<LRESULT>(nativeKey.HandleKeyUpMessage(aEventDispatched));
+}
+
+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;
+
+ // If !sTrimOnMinimize, we minimize windows using SW_SHOWMINIMIZED (See
+ // SetSizeMode for internal calls, and WM_SYSCOMMAND for external). This
+ // prevents the working set from being trimmed but keeps the window active.
+ // After the window is minimized, we need to do some touch up work on the
+ // active window. (bugs 76831 & 499816)
+ if (!sTrimOnMinimize && nsSizeMode_Minimized == mSizeMode)
+ ActivateOtherWindowHelper(mWnd);
+
+#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 (mWidgetListener && mSizeMode != previousSizeMode)
+ mWidgetListener->SizeModeChanged(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.x = wp->x;
+ mBounds.y = 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;
+ nsIntRect rect(wp->x, wp->y, newWidth, newHeight);
+
+ 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.width = newWidth;
+ mBounds.height = 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 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;
+ }
+ }
+
+ // Recalculate the width and height based on the client area for gecko events.
+ if (::GetClientRect(mWnd, &r)) {
+ rect.width = r.right - r.left;
+ rect.height = r.bottom - r.top;
+ }
+
+ // Send a gecko resize event
+ OnResize(rect);
+ }
+}
+
+// static
+void nsWindow::ActivateOtherWindowHelper(HWND aWnd)
+{
+ // Find the next window that is enabled, visible, and not minimized.
+ HWND hwndBelow = ::GetNextWindow(aWnd, GW_HWNDNEXT);
+ while (hwndBelow && (!::IsWindowEnabled(hwndBelow) || !::IsWindowVisible(hwndBelow) ||
+ ::IsIconic(hwndBelow))) {
+ hwndBelow = ::GetNextWindow(hwndBelow, GW_HWNDNEXT);
+ }
+
+ // Push ourselves to the bottom of the stack, then activate the
+ // next window.
+ ::SetWindowPos(aWnd, HWND_BOTTOM, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
+ if (hwndBelow)
+ ::SetForegroundWindow(hwndBelow);
+
+ // Play the minimize sound while we're here, since that is also
+ // forgotten when we use SW_SHOWMINIMIZED.
+ nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
+ if (sound) {
+ sound->PlaySystemSound(NS_LITERAL_STRING("Minimize"));
+ }
+}
+
+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;
+
+ if (mWidgetListener)
+ mWidgetListener->SizeModeChanged(sizeMode);
+
+ UpdateNonClientMargins(sizeMode, false);
+ }
+
+ // 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;
+}
+
+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/idleservice;1");
+ }
+
+ // Check that we now have the idle service.
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+}
+
+bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam)
+{
+ uint32_t cInputs = LOWORD(wParam);
+ PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
+
+ if (mGesture.GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs)) {
+ 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].dwFlags & 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()) {
+ DispatchTouchInput(touchInput);
+ }
+ // Dispatch touch end event if we have one.
+ if (!touchEndInput.mTimeStamp.IsNull()) {
+ DispatchTouchInput(touchEndInput);
+ }
+ }
+
+ delete [] pInputs;
+ mGesture.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.button = 0;
+ wheelEvent.mTime = ::GetMessageTime();
+ wheelEvent.mTimeStamp = GetMessageTimeStamp(wheelEvent.mTime);
+ wheelEvent.inputSource = nsIDOMMouseEvent::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);
+ }
+
+ mGesture.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.button = 0;
+ event.mTime = ::GetMessageTime();
+ event.mTimeStamp = GetMessageTimeStamp(event.mTime);
+ event.inputSource = nsIDOMMouseEvent::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.
+ mGesture.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;
+
+ // 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 == -1)
+ SetCursor(eCursor_standard);
+
+ 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(nsIntRect &aWindowRect)
+{
+ bool result = mWidgetListener ?
+ mWidgetListener->WindowResized(this, aWindowRect.width, aWindowRect.height) : false;
+
+ // If there is an attached view, inform it as well as the normal widget listener.
+ if (mAttachedWidgetListener) {
+ return mAttachedWidgetListener->WindowResized(this, aWindowRect.width, aWindowRect.height);
+ }
+
+ return result;
+}
+
+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()
+{
+ // 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
+ if (mTransparencyMode == eTransparencyTransparent) {
+ 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(Feature::HW_COMPOSITING) &&
+ !gfxConfig::IsEnabled(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(Feature::HW_COMPOSITING))
+ {
+ sHasBogusPopupsDropShadowOnMultiMonitor = TRI_TRUE;
+ }
+ }
+ }
+ }
+ }
+ }
+ return !!sHasBogusPopupsDropShadowOnMultiMonitor;
+}
+
+void
+nsWindow::OnSysColorChanged()
+{
+ if (mWindowType == eWindowType_invisible) {
+ ::EnumThreadWindows(GetCurrentThreadId(), nsWindow::BroadcastMsg, WM_SYSCOLORCHANGE);
+ }
+ else {
+ // Note: This is sent for child windows as well as top-level windows.
+ // The Win32 toolkit normally only sends these events to top-level windows.
+ // But we cycle through all of the childwindows and send it to them as well
+ // so all presentations get notified properly.
+ // See nsWindow::GlobalMsgWindowProc.
+ NotifySysColorChanged();
+ }
+}
+
+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 (DefaultScaleOverride() > 0.0) {
+ return;
+ }
+ double oldScale = mDefaultScale;
+ mDefaultScale = -1.0; // force recomputation of scale factor
+ double newScale = GetDefaultScaleInternal();
+
+ 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);
+ }
+ ChangedDPI();
+ ResetLayout();
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: IME management and accessibility
+ **
+ ** Handles managing IME input and accessibility.
+ **
+ **************************************************************
+ **************************************************************/
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ InputContext newInputContext = aContext;
+ IMEHandler::SetInputContext(this, newInputContext, aAction);
+ mInputContext = newInputContext;
+}
+
+NS_IMETHODIMP_(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;
+}
+
+nsIMEUpdatePreference
+nsWindow::GetIMEUpdatePreference()
+{
+ return IMEHandler::GetUpdatePreference();
+}
+
+NS_IMETHODIMP_(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->PresContext()->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();
+}
+
+#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
+ 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)
+{
+ RECT r;
+ ::GetWindowRect(aWindow->mWnd, &r);
+ DWORD pos = ::GetMessagePos();
+ POINT mp;
+ 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)
+{
+ // 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))) {
+ // 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;
+ }
+ MOZ_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() == nsIDOMMouseEvent::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_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()) {
+ return false;
+ }
+ } 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.
+ if (activeWindow) {
+ if (activeWindow->IsPopup()) {
+ return false;
+ }
+ nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd);
+ if (deactiveWindow && deactiveWindow->IsPopup()) {
+ return false;
+ }
+ }
+ } 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.
+ if (EventIsInsideWindow(popupWindow) ||
+ !GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ return false;
+ }
+ }
+ break;
+
+ case MOZ_WM_REACTIVATE:
+ // The previous active window should take back focus.
+ if (::IsWindow(reinterpret_cast<HWND>(aLParam))) {
+ ::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(!mLastRollup, "mLastRollup is null");
+
+ if (nativeMessage == WM_LBUTTONDOWN) {
+ POINT pt;
+ pt.x = GET_X_LPARAM(aLParam);
+ pt.y = GET_Y_LPARAM(aLParam);
+ ::ClientToScreen(aWnd, &pt);
+ nsIntPoint pos(pt.x, pt.y);
+
+ consumeRollupEvent =
+ rollupListener->Rollup(popupsToRollup, true, &pos, &mLastRollup);
+ NS_IF_ADDREF(mLastRollup);
+ } 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;
+ }
+}
+
+static BOOL CALLBACK gEnumWindowsProc(HWND hwnd, LPARAM lParam)
+{
+ DWORD pid;
+ ::GetWindowThreadProcessId(hwnd, &pid);
+ if (pid == GetCurrentProcessId() && ::IsWindowVisible(hwnd))
+ {
+ gWindowsVisible = true;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+bool nsWindow::CanTakeFocus()
+{
+ gWindowsVisible = false;
+ EnumWindows(gEnumWindowsProc, 0);
+ if (!gWindowsVisible) {
+ return true;
+ } else {
+ HWND fgWnd = ::GetForegroundWindow();
+ if (!fgWnd) {
+ return true;
+ }
+ DWORD pid;
+ GetWindowThreadProcessId(fgWnd, &pid);
+ if (pid == GetCurrentProcessId()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */ const wchar_t*
+nsWindow::GetMainWindowClass()
+{
+ static const wchar_t* sMainWindowClass = nullptr;
+ if (!sMainWindowClass) {
+ nsAdoptingString className =
+ Preferences::GetString("ui.window_class_override");
+ 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());
+}
+
+void
+nsWindow::SetCandidateWindowForPlugin(const CandidateWindowPosition& aPosition)
+{
+ CANDIDATEFORM form;
+ form.dwIndex = 0;
+ if (aPosition.mExcludeRect) {
+ form.dwStyle = CFS_EXCLUDE;
+ form.rcArea.left = aPosition.mRect.x;
+ form.rcArea.top = aPosition.mRect.y;
+ form.rcArea.right = aPosition.mRect.x + aPosition.mRect.width;
+ form.rcArea.bottom = aPosition.mRect.y + aPosition.mRect.height;
+ } else {
+ form.dwStyle = CFS_CANDIDATEPOS;
+ }
+ form.ptCurrentPos.x = aPosition.mPoint.x;
+ form.ptCurrentPos.y = aPosition.mPoint.y;
+
+ IMEHandler::SetCandidateWindow(this, &form);
+}
+
+void
+nsWindow::DefaultProcOfPluginEvent(const WidgetPluginEvent& aEvent)
+{
+ const NPEvent* pPluginEvent =
+ static_cast<const NPEvent*>(aEvent.mPluginEvent);
+
+ if (NS_WARN_IF(!pPluginEvent)) {
+ return;
+ }
+
+ if (!mWnd) {
+ return;
+ }
+
+ // For WM_IME_*COMPOSITION
+ IMEHandler::DefaultProcOfPluginEvent(this, pPluginEvent);
+
+ CallWindowProcW(GetPrevWindowProc(), mWnd, pPluginEvent->event,
+ pPluginEvent->wParam, pPluginEvent->lParam);
+}
+
+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;
+ }
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: ChildWindow impl.
+ **
+ ** Child window overrides.
+ **
+ **************************************************************
+ **************************************************************/
+
+// return the style for a child nsWindow
+DWORD ChildWindow::WindowStyle()
+{
+ DWORD style = WS_CLIPCHILDREN | nsWindow::WindowStyle();
+ if (!(style & WS_POPUP))
+ style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive.
+ VERIFY_WINDOW_STYLE(style);
+ return style;
+}
+
+void
+nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData)
+{
+ aInitData->hWnd() = reinterpret_cast<uintptr_t>(mWnd);
+ aInitData->widgetKey() = reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this));
+ aInitData->transparencyMode() = mTransparencyMode;
+}
diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h
new file mode 100644
index 000000000..248978bd7
--- /dev/null
+++ b/widget/windows/nsWindow.h
@@ -0,0 +1,671 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsWindowBase.h"
+#include "nsdefs.h"
+#include "nsIdleService.h"
+#include "nsToolkit.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxWindowsSurface.h"
+#include "nsWindowDbg.h"
+#include "cairo.h"
+#include "nsITimer.h"
+#include "nsRegion.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "nsMargin.h"
+#include "nsRegionFwd.h"
+
+#include "nsWinGesture.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 "nsIDOMMouseEvent.h"
+#include "nsIIdleServiceInternal.h"
+
+#include "IMMHandler.h"
+
+/**
+ * Forward class definitions
+ */
+
+class nsNativeDragTarget;
+class nsIRollupListener;
+class imgIContainer;
+
+namespace mozilla {
+namespace widget {
+class NativeKey;
+class WinCompositorWidget;
+struct MSGResult;
+} // namespace widget
+} // namespacw mozilla;
+
+/**
+ * Native WIN32 window wrapper.
+ */
+
+class nsWindow : 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;
+
+public:
+ nsWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ friend class nsWindowGfx;
+
+ // 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
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+ virtual void Destroy() override;
+ NS_IMETHOD 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());
+ }
+ }
+
+ NS_IMETHOD Show(bool bState) 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 const SizeConstraints GetSizeConstraints() override;
+ NS_IMETHOD Move(double aX, double aY) override;
+ NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD 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;
+ NS_IMETHOD Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ NS_IMETHOD SetFocus(bool aRaise) override;
+ virtual LayoutDeviceIntRect GetBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ virtual MOZ_MUST_USE nsresult GetRestoredBounds(LayoutDeviceIntRect& aRect) override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ void SetBackgroundColor(const nscolor& aColor) override;
+ NS_IMETHOD SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) override;
+ NS_IMETHOD SetCursor(nsCursor aCursor) 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 nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aScreen = nullptr) override;
+ NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
+ NS_IMETHOD Invalidate(bool aEraseBackground = false,
+ bool aUpdateNCArea = false,
+ bool aIncludeChildren = false);
+ NS_IMETHOD 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;
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override;
+ NS_IMETHOD SetIcon(const nsAString& aIconSpec) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual LayoutDeviceIntSize ClientToWindowSize(const LayoutDeviceIntSize& aClientSize) override;
+ NS_IMETHOD 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;
+ NS_IMETHOD 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;
+ NS_IMETHOD 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;
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override;
+ NS_IMETHOD_(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 nsIMEUpdatePreference GetIMEUpdatePreference() override;
+ NS_IMETHOD 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;
+
+ /**
+ * Event helpers
+ */
+ virtual bool DispatchMouseEvent(
+ mozilla::EventMessage aEventMessage,
+ WPARAM wParam,
+ LPARAM lParam,
+ bool aIsContextMenuKey = false,
+ int16_t aButton =
+ mozilla::WidgetMouseEvent::eLeftButton,
+ uint16_t aInputSource =
+ nsIDOMMouseEvent::MOZ_SOURCE_MOUSE,
+ uint16_t aPointerId = 0);
+ virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus);
+ void DispatchPendingEvents();
+ bool DispatchPluginEvent(UINT aMessage,
+ WPARAM aWParam,
+ LPARAM aLParam,
+ bool aDispatchPendingEvents);
+
+ void SuppressBlurEvents(bool aSuppress); // Called from nsFilePicker
+ bool BlurEventsSuppressed();
+#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.
+ typedef void (WindowEnumCallback)(nsWindow*);
+ static void EnumAllWindows(WindowEnumCallback aCallback);
+
+ /**
+ * Misc.
+ */
+ virtual bool AutoErase(HDC dc);
+ bool WidgetTypeSupportsAcceleration() override;
+
+ void ForcePresent();
+
+ /**
+ * 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 void SetCandidateWindowForPlugin(
+ const mozilla::widget::CandidateWindowPosition&
+ aPosition) override;
+ virtual void DefaultProcOfPluginEvent(
+ const mozilla::WidgetPluginEvent& aEvent) override;
+ virtual nsresult OnWindowedPluginKeyEvent(
+ const mozilla::NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback) override;
+
+ void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) override;
+ bool IsTouchWindow() const { return mTouchWindow; }
+
+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
+ */
+ 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);
+ bool ProcessMessageForPlugin(const MSG &aMsg,
+ 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);
+ // 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);
+
+ /**
+ * Event handlers
+ */
+ virtual void OnDestroy() override;
+ virtual bool OnResize(nsIntRect &aWindowRect);
+ 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);
+
+ /**
+ * 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);
+ 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();
+protected:
+#endif // MOZ_XUL
+
+ static bool IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult);
+ void IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam);
+
+ /**
+ * Misc.
+ */
+ void StopFlashing();
+ static bool IsTopLevelMouseExit(HWND aWnd);
+ virtual nsresult SetWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting) override;
+ nsIntRegion GetRegionToPaint(bool aForceFullRepaint,
+ PAINTSTRUCT ps, HDC aDC);
+ static void ActivateOtherWindowHelper(HWND aWnd);
+ void ClearCachedResources();
+ nsIWidgetListener* GetPaintListener();
+
+ 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);
+
+protected:
+ nsCOMPtr<nsIWidget> mParent;
+ nsIntSize mLastSize;
+ nsIntPoint mLastPoint;
+ HWND mWnd;
+ HWND mTransitionWnd;
+ WNDPROC mPrevWndProc;
+ HBRUSH mBrush;
+ IMEContext mDefaultIMC;
+ bool mIsTopWidgetWindow;
+ bool mInDtor;
+ bool mIsVisible;
+ bool mUnicodeWidget;
+ bool mPainting;
+ bool mTouchWindow;
+ bool mDisplayPanFeedback;
+ bool mHideChrome;
+ bool mIsRTL;
+ bool mFullscreenMode;
+ bool mMousePresent;
+ bool mDestroyCalled;
+ uint32_t mBlurSuppressLevel;
+ DWORD_PTR mOldStyle;
+ DWORD_PTR mOldExStyle;
+ nsNativeDragTarget* mNativeDragTarget;
+ HKL mLastKeyboardLayout;
+ nsSizeMode mOldSizeMode;
+ nsSizeMode mLastSizeMode;
+ WindowHook mWindowHook;
+ uint32_t mPickerDisplayCount;
+ HICON mIconSmall;
+ HICON mIconBig;
+ 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 int sTrimOnMinimize;
+
+ // 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;
+
+ nsCOMPtr<nsIIdleServiceInternal> 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;
+
+ // 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;
+
+ // Caching for hit test results
+ POINT mCachedHitTestPoint;
+ TimeStamp mCachedHitTestTime;
+ int32_t mCachedHitTestResult;
+
+ RefPtr<mozilla::widget::WinCompositorWidget> mBasicLayersSurface;
+
+ static bool sNeedsToInitMouseWheelSettings;
+ static void InitMouseWheelScrollData();
+
+ double mSizeConstraintsScale; // scale in effect when setting constraints
+
+ // Used to remember the wParam (i.e. currently pressed modifier keys)
+ // and lParam (i.e. last mouse position) in screen coordinates from
+ // the previous mouse-exit. Static since it is not
+ // associated with a particular widget (since we exited the widget).
+ static WPARAM sMouseExitwParam;
+ static LPARAM sMouseExitlParamScreen;
+};
+
+/**
+ * A child window is a window with different style.
+ */
+class ChildWindow : public nsWindow {
+
+public:
+ ChildWindow() {}
+
+protected:
+ virtual DWORD WindowStyle();
+};
+
+#endif // Window_h__
diff --git a/widget/windows/nsWindowBase.cpp b/widget/windows/nsWindowBase.cpp
new file mode 100644
index 000000000..a374b3635
--- /dev/null
+++ b/widget/windows/nsWindowBase.cpp
@@ -0,0 +1,243 @@
+/* -*- 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 "KeyboardLayout.h"
+#include "WinUtils.h"
+#include "npapi.h"
+#include "nsAutoPtr.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)
+{
+ if (!ShouldDispatchPluginEvent()) {
+ return false;
+ }
+ WidgetPluginEvent pluginEvent(true, ePluginInputEvent, this);
+ LayoutDeviceIntPoint point(0, 0);
+ InitEvent(pluginEvent, &point);
+ NPEvent npEvent;
+ npEvent.event = aMsg.message;
+ npEvent.wParam = aMsg.wParam;
+ npEvent.lParam = aMsg.lParam;
+ pluginEvent.mPluginEvent.Copy(npEvent);
+ pluginEvent.mRetargetToFocusedDocument = true;
+ return DispatchWindowEvent(&pluginEvent);
+}
+
+bool
+nsWindowBase::ShouldDispatchPluginEvent()
+{
+ return PluginHasFocus();
+}
+
+// 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;
+
+ if (!sInjectTouchFuncPtr(1, &info)) {
+ WinUtils::Log("InjectTouchInput failure. GetLastError=%d", GetLastError());
+ return false;
+ }
+ return true;
+}
+
+void nsWindowBase::ChangedDPI()
+{
+ if (mWidgetListener) {
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->BackingScaleFactorChanged();
+ }
+ mWidgetListener->UIResolutionChanged();
+ }
+}
+
+nsresult
+nsWindowBase::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ nsIWidget::TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (gfxPrefs::APZTestFailsWithNativeInjection() || !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()) {
+ nsAutoPtr<PointerInfo>& info = iter.Data();
+ InjectTouchPoint(info.get()->mPointerId, info.get()->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 000000000..405f5b359
--- /dev/null
+++ b/widget/windows/nsWindowBase.h
@@ -0,0 +1,145 @@
+/* -*- 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.
+ */
+ virtual HWND GetWindowHandle() final {
+ 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);
+
+ /*
+ * Returns true if this should dispatch a plugin event.
+ */
+ bool ShouldDispatchPluginEvent();
+
+ /*
+ * 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);
+
+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 000000000..d5bfcddd9
--- /dev/null
+++ b/widget/windows/nsWindowDbg.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/. */
+
+/*
+ * nsWindowDbg - Debug related utilities for nsWindow.
+ */
+
+#include "mozilla/Logging.h"
+#include "nsWindowDbg.h"
+#include "WinUtils.h"
+
+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 000000000..660769952
--- /dev/null
+++ b/widget/windows/nsWindowDbg.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 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 000000000..48a9356d2
--- /dev/null
+++ b/widget/windows/nsWindowDefs.h
@@ -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/. */
+
+#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)
+ {
+ }
+};
+
+#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;
+
+ 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 000000000..a88631f89
--- /dev/null
+++ b/widget/windows/nsWindowGfx.cpp
@@ -0,0 +1,686 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "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 "prmem.h"
+#include "WinUtils.h"
+#include "nsIWidgetListener.h"
+#include "mozilla/Unused.h"
+#include "nsDebug.h"
+#include "nsIXULRuntime.h"
+
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "ClientLayerManager.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
+nsIntRegion nsWindow::GetRegionToPaint(bool aForceFullRepaint,
+ PAINTSTRUCT ps, HDC aDC)
+{
+ if (aForceFullRepaint) {
+ RECT paintRect;
+ ::GetClientRect(mWnd, &paintRect);
+ return nsIntRegion(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);
+ }
+ nsIntRegion rgn(WinUtils::ConvertHRGNToRegion(paintRgn));
+ ::DeleteObject(paintRgn);
+ return rgn;
+ }
+ return nsIntRegion(WinUtils::ToIntRect(ps.rcPaint));
+}
+
+#define WORDSSIZE(x) ((x).width * (x).height)
+static bool
+EnsureSharedSurfaceSize(IntSize size)
+{
+ IntSize screenSize;
+ screenSize.height = GetSystemMetrics(SM_CYSCREEN);
+ screenSize.width = GetSystemMetrics(SM_CXSCREEN);
+
+ if (WORDSSIZE(screenSize) > WORDSSIZE(size))
+ size = screenSize;
+
+ if (WORDSSIZE(screenSize) < WORDSSIZE(size))
+ NS_WARNING("Trying to create a shared surface larger than the screen");
+
+ if (!sSharedSurfaceData || (WORDSSIZE(size) > WORDSSIZE(sSharedSurfaceSize))) {
+ sSharedSurfaceSize = size;
+ sSharedSurfaceData =
+ MakeUniqueFallible<uint8_t[]>(WORDSSIZE(sSharedSurfaceSize) * 4);
+ }
+
+ return !sSharedSurfaceData;
+}
+
+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();
+ EnumAllWindows([] (nsWindow* aWindow) -> void {
+ aWindow->OnRenderingDeviceReset();
+ });
+
+ 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;
+ }
+
+ ClientLayerManager *clientLayerManager = GetLayerManager()->AsClientLayerManager();
+
+ if (clientLayerManager && !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.
+ clientLayerManager->Composite();
+ }
+ mLastPaintBounds = mBounds;
+
+ PAINTSTRUCT ps;
+
+#ifdef MOZ_XUL
+ if (!aDC && (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 = mCompositorWidgetDelegate->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
+ nsIntRegion region = GetRegionToPaint(forceRepaint, ps, hDC);
+
+ if (clientLayerManager) {
+ // 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.
+ clientLayerManager->SetNeedsComposite(true);
+ clientLayerManager->SendInvalidRegion(region);
+ }
+
+ 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 (clientLayerManager && clientLayerManager->NeedsComposite()) {
+ clientLayerManager->Composite();
+ clientLayerManager->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,
+ "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) {
+ 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::GetPlatform()->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, LayoutDeviceIntRegion::FromUnknownRegion(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:
+ {
+ result = listener->PaintWindow(
+ this, LayoutDeviceIntRegion::FromUnknownRegion(region));
+ if (!gfxEnv::DisableForcePresent() && gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(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;
+}
+
+IntSize 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 IntSize(width, height);
+}
+
+nsresult nsWindowGfx::CreateIcon(imgIContainer *aContainer,
+ bool aIsCursor,
+ uint32_t aHotspotX,
+ uint32_t aHotspotY,
+ IntSize aScaledSize,
+ HICON *aIcon) {
+
+ 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);
+ 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);
+ PR_Free(a1data);
+
+ ICONINFO info = {0};
+ info.fIcon = !aIsCursor;
+ info.xHotspot = aHotspotX;
+ info.yHotspot = aHotspotY;
+ 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*)PR_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 000000000..3f4d76362
--- /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::gfx::IntSize GetIconMetrics(IconSizeType aSizeType);
+ static nsresult CreateIcon(imgIContainer *aContainer, bool aIsCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY,
+ mozilla::gfx::IntSize 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 000000000..4e0c1c397
--- /dev/null
+++ b/widget/windows/nsdefs.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 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 000000000..8d9ac9478
--- /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 000000000..decfbdcac
--- /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 000000000..8f7f67512
--- /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 000000000..87f1519cd
--- /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 000000000..db7ad5aed
--- /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 000000000..e0dfd04e4
--- /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 000000000..2114dfaee
--- /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 000000000..a7369d32d
--- /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 000000000..5a88b3707
--- /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 000000000..3de04ebec
--- /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 000000000..b594d7927
--- /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 000000000..7e495fbaa
--- /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 000000000..45258ab2b
--- /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/TestWinDND.cpp b/widget/windows/tests/TestWinDND.cpp
new file mode 100644
index 000000000..096633b09
--- /dev/null
+++ b/widget/windows/tests/TestWinDND.cpp
@@ -0,0 +1,716 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIFileURL.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;
+ NS_NAMED_LITERAL_STRING(mozString, "Mozilla can drag and drop");
+ 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;
+ NS_NAMED_LITERAL_STRING(mozString, " twice over");
+ 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 000000000..28919c271
--- /dev/null
+++ b/widget/windows/tests/moz.build
@@ -0,0 +1,6 @@
+# -*- 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/.
+
diff --git a/widget/windows/touchinjection_sdk80.h b/widget/windows/touchinjection_sdk80.h
new file mode 100644
index 000000000..e93f62089
--- /dev/null
+++ b/widget/windows/touchinjection_sdk80.h
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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
+};
+
+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 \ No newline at end of file
diff --git a/widget/windows/widget.rc b/widget/windows/widget.rc
new file mode 100644
index 000000000..9361f9e48
--- /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 000000000..9ea0fbbfb
--- /dev/null
+++ b/widget/x11/keysym2ucs.c
@@ -0,0 +1,866 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/*
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ * 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[] = {
+ { 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 */
+};
+
+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 000000000..d3c49a629
--- /dev/null
+++ b/widget/x11/keysym2ucs.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/*
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ * 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 000000000..9b5dafb6d
--- /dev/null
+++ b/widget/x11/moz.build
@@ -0,0 +1,13 @@
+# -*- 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 += [
+ 'keysym2ucs.c',
+]
+
+FINAL_LIBRARY = 'xul'
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
diff --git a/widget/xremoteclient/XRemoteClient.cpp b/widget/xremoteclient/XRemoteClient.cpp
new file mode 100644
index 000000000..c4567f3cb
--- /dev/null
+++ b/widget/xremoteclient/XRemoteClient.cpp
@@ -0,0 +1,805 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=8:
+ */
+/* vim:set ts=8 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Sprintf.h"
+#include "XRemoteClient.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prsystem.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+#include "prdtoa.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <limits.h>
+#include <X11/Xatom.h>
+
+#define MOZILLA_VERSION_PROP "_MOZILLA_VERSION"
+#define MOZILLA_LOCK_PROP "_MOZILLA_LOCK"
+#define MOZILLA_COMMANDLINE_PROP "_MOZILLA_COMMANDLINE"
+#define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE"
+#define MOZILLA_USER_PROP "_MOZILLA_USER"
+#define MOZILLA_PROFILE_PROP "_MOZILLA_PROFILE"
+#define MOZILLA_PROGRAM_PROP "_MOZILLA_PROGRAM"
+
+#ifdef IS_BIG_ENDIAN
+#define TO_LITTLE_ENDIAN32(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+#else
+#define TO_LITTLE_ENDIAN32(x) (x)
+#endif
+
+#ifndef MAX_PATH
+#ifdef PATH_MAX
+#define MAX_PATH PATH_MAX
+#else
+#define MAX_PATH 1024
+#endif
+#endif
+
+using mozilla::LogLevel;
+
+static PRLogModuleInfo *sRemoteLm = nullptr;
+
+static int (*sOldHandler)(Display *, XErrorEvent *);
+static bool sGotBadWindow;
+
+XRemoteClient::XRemoteClient()
+{
+ mDisplay = 0;
+ mInitialized = false;
+ mMozVersionAtom = 0;
+ mMozLockAtom = 0;
+ mMozCommandLineAtom = 0;
+ mMozResponseAtom = 0;
+ mMozWMStateAtom = 0;
+ mMozUserAtom = 0;
+ mMozProfileAtom = 0;
+ mMozProgramAtom = 0;
+ mLockData = 0;
+ if (!sRemoteLm)
+ sRemoteLm = PR_NewLogModule("XRemoteClient");
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("XRemoteClient::XRemoteClient"));
+}
+
+XRemoteClient::~XRemoteClient()
+{
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("XRemoteClient::~XRemoteClient"));
+ if (mInitialized)
+ Shutdown();
+}
+
+// Minimize the roundtrips to the X-server
+static const char *XAtomNames[] = {
+ MOZILLA_VERSION_PROP,
+ MOZILLA_LOCK_PROP,
+ MOZILLA_RESPONSE_PROP,
+ "WM_STATE",
+ MOZILLA_USER_PROP,
+ MOZILLA_PROFILE_PROP,
+ MOZILLA_PROGRAM_PROP,
+ MOZILLA_COMMANDLINE_PROP
+};
+static Atom XAtoms[MOZ_ARRAY_LENGTH(XAtomNames)];
+
+nsresult
+XRemoteClient::Init()
+{
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("XRemoteClient::Init"));
+
+ if (mInitialized)
+ return NS_OK;
+
+ // try to open the display
+ mDisplay = XOpenDisplay(0);
+ if (!mDisplay)
+ return NS_ERROR_FAILURE;
+
+ // get our atoms
+ XInternAtoms(mDisplay, const_cast<char**>(XAtomNames),
+ MOZ_ARRAY_LENGTH(XAtomNames), False, XAtoms);
+
+ int i = 0;
+ mMozVersionAtom = XAtoms[i++];
+ mMozLockAtom = XAtoms[i++];
+ mMozResponseAtom = XAtoms[i++];
+ mMozWMStateAtom = XAtoms[i++];
+ mMozUserAtom = XAtoms[i++];
+ mMozProfileAtom = XAtoms[i++];
+ mMozProgramAtom = XAtoms[i++];
+ mMozCommandLineAtom = XAtoms[i++];
+
+ mInitialized = true;
+
+ return NS_OK;
+}
+
+void
+XRemoteClient::Shutdown (void)
+{
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("XRemoteClient::Shutdown"));
+
+ if (!mInitialized)
+ return;
+
+ // shut everything down
+ XCloseDisplay(mDisplay);
+ mDisplay = 0;
+ mInitialized = false;
+ if (mLockData) {
+ free(mLockData);
+ mLockData = 0;
+ }
+}
+
+static int
+HandleBadWindow(Display *display, XErrorEvent *event)
+{
+ if (event->error_code == BadWindow) {
+ sGotBadWindow = true;
+ return 0; // ignored
+ }
+ else {
+ return (*sOldHandler)(display, event);
+ }
+}
+
+nsresult
+XRemoteClient::SendCommandLine (const char *aProgram, const char *aUsername,
+ const char *aProfile,
+ int32_t argc, char **argv,
+ const char* aDesktopStartupID,
+ char **aResponse, bool *aWindowFound)
+{
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("XRemoteClient::SendCommandLine"));
+
+ *aWindowFound = false;
+
+ // FindBestWindow() iterates down the window hierarchy, so catch X errors
+ // when windows get destroyed before being accessed.
+ sOldHandler = XSetErrorHandler(HandleBadWindow);
+
+ Window w = FindBestWindow(aProgram, aUsername, aProfile);
+
+ nsresult rv = NS_OK;
+
+ if (w) {
+ // ok, let the caller know that we at least found a window.
+ *aWindowFound = true;
+
+ // Ignore BadWindow errors up to this point. The last request from
+ // FindBestWindow() was a synchronous XGetWindowProperty(), so no need to
+ // Sync. Leave the error handler installed to detect if w gets destroyed.
+ sGotBadWindow = false;
+
+ // make sure we get the right events on that window
+ XSelectInput(mDisplay, w,
+ (PropertyChangeMask|StructureNotifyMask));
+
+ bool destroyed = false;
+
+ // get the lock on the window
+ rv = GetLock(w, &destroyed);
+
+ if (NS_SUCCEEDED(rv)) {
+ // send our command
+ rv = DoSendCommandLine(w, argc, argv, aDesktopStartupID, aResponse,
+ &destroyed);
+
+ // if the window was destroyed, don't bother trying to free the
+ // lock.
+ if (!destroyed)
+ FreeLock(w); // doesn't really matter what this returns
+
+ }
+ }
+
+ XSetErrorHandler(sOldHandler);
+
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("SendCommandInternal returning 0x%x\n", rv));
+
+ return rv;
+}
+
+Window
+XRemoteClient::CheckWindow(Window aWindow)
+{
+ Atom type = None;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *data;
+ Window innerWindow;
+
+ XGetWindowProperty(mDisplay, aWindow, mMozWMStateAtom,
+ 0, 0, False, AnyPropertyType,
+ &type, &format, &nitems, &bytesafter, &data);
+
+ if (type) {
+ XFree(data);
+ return aWindow;
+ }
+
+ // didn't find it here so check the children of this window
+ innerWindow = CheckChildren(aWindow);
+
+ if (innerWindow)
+ return innerWindow;
+
+ return aWindow;
+}
+
+Window
+XRemoteClient::CheckChildren(Window aWindow)
+{
+ Window root, parent;
+ Window *children;
+ unsigned int nchildren;
+ unsigned int i;
+ Atom type = None;
+ int format;
+ unsigned long nitems, after;
+ unsigned char *data;
+ Window retval = None;
+
+ if (!XQueryTree(mDisplay, aWindow, &root, &parent, &children,
+ &nchildren))
+ return None;
+
+ // scan the list first before recursing into the list of windows
+ // which can get quite deep.
+ for (i=0; !retval && (i < nchildren); i++) {
+ XGetWindowProperty(mDisplay, children[i], mMozWMStateAtom,
+ 0, 0, False, AnyPropertyType, &type, &format,
+ &nitems, &after, &data);
+ if (type) {
+ XFree(data);
+ retval = children[i];
+ }
+ }
+
+ // otherwise recurse into the list
+ for (i=0; !retval && (i < nchildren); i++) {
+ retval = CheckChildren(children[i]);
+ }
+
+ if (children)
+ XFree((char *)children);
+
+ return retval;
+}
+
+nsresult
+XRemoteClient::GetLock(Window aWindow, bool *aDestroyed)
+{
+ bool locked = false;
+ bool waited = false;
+ *aDestroyed = false;
+
+ nsresult rv = NS_OK;
+
+ if (!mLockData) {
+
+ char pidstr[32];
+ char sysinfobuf[SYS_INFO_BUFFER_LENGTH];
+ SprintfLiteral(pidstr, "pid%d@", getpid());
+ PRStatus status;
+ status = PR_GetSystemInfo(PR_SI_HOSTNAME, sysinfobuf,
+ SYS_INFO_BUFFER_LENGTH);
+ if (status != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // allocate enough space for the string plus the terminating
+ // char
+ mLockData = (char *)malloc(strlen(pidstr) + strlen(sysinfobuf) + 1);
+ if (!mLockData)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ strcpy(mLockData, pidstr);
+ if (!strcat(mLockData, sysinfobuf))
+ return NS_ERROR_FAILURE;
+ }
+
+ do {
+ int result;
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *data = 0;
+
+ XGrabServer(mDisplay);
+
+ result = XGetWindowProperty (mDisplay, aWindow, mMozLockAtom,
+ 0, (65536 / sizeof (long)),
+ False, /* don't delete */
+ XA_STRING,
+ &actual_type, &actual_format,
+ &nitems, &bytes_after,
+ &data);
+
+ // aWindow may have been destroyed before XSelectInput was processed, in
+ // which case there may not be any DestroyNotify event in the queue to
+ // tell us. XGetWindowProperty() was synchronous so error responses have
+ // now been processed, setting sGotBadWindow.
+ if (sGotBadWindow) {
+ *aDestroyed = true;
+ rv = NS_ERROR_FAILURE;
+ }
+ else if (result != Success || actual_type == None) {
+ /* It's not now locked - lock it. */
+ XChangeProperty (mDisplay, aWindow, mMozLockAtom, XA_STRING, 8,
+ PropModeReplace,
+ (unsigned char *)mLockData,
+ strlen(mLockData));
+ locked = True;
+ }
+
+ XUngrabServer(mDisplay);
+ XFlush(mDisplay); // ungrab now!
+
+ if (!locked && !NS_FAILED(rv)) {
+ /* We tried to grab the lock this time, and failed because someone
+ else is holding it already. So, wait for a PropertyDelete event
+ to come in, and try again. */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("window 0x%x is locked by %s; waiting...\n",
+ (unsigned int) aWindow, data));
+ waited = True;
+ while (1) {
+ XEvent event;
+ int select_retval;
+ fd_set select_set;
+ struct timeval delay;
+ delay.tv_sec = 10;
+ delay.tv_usec = 0;
+
+ FD_ZERO(&select_set);
+ // add the x event queue to the select set
+ FD_SET(ConnectionNumber(mDisplay), &select_set);
+ select_retval = select(ConnectionNumber(mDisplay) + 1,
+ &select_set, nullptr, nullptr, &delay);
+ // did we time out?
+ if (select_retval == 0) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("timed out waiting for window\n"));
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("xevent...\n"));
+ XNextEvent (mDisplay, &event);
+ if (event.xany.type == DestroyNotify &&
+ event.xdestroywindow.window == aWindow) {
+ *aDestroyed = true;
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ else if (event.xany.type == PropertyNotify &&
+ event.xproperty.state == PropertyDelete &&
+ event.xproperty.window == aWindow &&
+ event.xproperty.atom == mMozLockAtom) {
+ /* Ok! Someone deleted their lock, so now we can try
+ again. */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("(0x%x unlocked, trying again...)\n",
+ (unsigned int) aWindow));
+ break;
+ }
+ }
+ }
+ if (data)
+ XFree(data);
+ } while (!locked && !NS_FAILED(rv));
+
+ if (waited && locked) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("obtained lock.\n"));
+ } else if (*aDestroyed) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("window 0x%x unexpectedly destroyed.\n",
+ (unsigned int) aWindow));
+ }
+
+ return rv;
+}
+
+Window
+XRemoteClient::FindBestWindow(const char *aProgram, const char *aUsername,
+ const char *aProfile)
+{
+ Window root = RootWindowOfScreen(DefaultScreenOfDisplay(mDisplay));
+ Window bestWindow = 0;
+ Window root2, parent, *kids;
+ unsigned int nkids;
+
+ // Get a list of the children of the root window, walk the list
+ // looking for the best window that fits the criteria.
+ if (!XQueryTree(mDisplay, root, &root2, &parent, &kids, &nkids)) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("XQueryTree failed in XRemoteClient::FindBestWindow"));
+ return 0;
+ }
+
+ if (!(kids && nkids)) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("root window has no children"));
+ return 0;
+ }
+
+ // We'll walk the list of windows looking for a window that best
+ // fits the criteria here.
+
+ for (unsigned int i = 0; i < nkids; i++) {
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *data_return = 0;
+ Window w;
+ w = kids[i];
+ // find the inner window with WM_STATE on it
+ w = CheckWindow(w);
+
+ int status = XGetWindowProperty(mDisplay, w, mMozVersionAtom,
+ 0, (65536 / sizeof (long)),
+ False, XA_STRING,
+ &type, &format, &nitems, &bytesafter,
+ &data_return);
+
+ if (!data_return)
+ continue;
+
+ double version = PR_strtod((char*) data_return, nullptr);
+ XFree(data_return);
+
+ if (!(version >= 5.1 && version < 6))
+ continue;
+
+ data_return = 0;
+
+ if (status != Success || type == None)
+ continue;
+
+ // If someone passed in a program name, check it against this one
+ // unless it's "any" in which case, we don't care. If someone did
+ // pass in a program name and this window doesn't support that
+ // protocol, we don't include it in our list.
+ if (aProgram && strcmp(aProgram, "any")) {
+ status = XGetWindowProperty(mDisplay, w, mMozProgramAtom,
+ 0, (65536 / sizeof(long)),
+ False, XA_STRING,
+ &type, &format, &nitems, &bytesafter,
+ &data_return);
+
+ // If the return name is not the same as what someone passed in,
+ // we don't want this window.
+ if (data_return) {
+ if (strcmp(aProgram, (const char *)data_return)) {
+ XFree(data_return);
+ continue;
+ }
+
+ // This is actually the success condition.
+ XFree(data_return);
+ }
+ else {
+ // Doesn't support the protocol, even though the user
+ // requested it. So we're not going to use this window.
+ continue;
+ }
+ }
+
+ // Check to see if it has the user atom on that window. If there
+ // is then we need to make sure that it matches what we have.
+ const char *username;
+ if (aUsername) {
+ username = aUsername;
+ }
+ else {
+ username = PR_GetEnv("LOGNAME");
+ }
+
+ if (username) {
+ status = XGetWindowProperty(mDisplay, w, mMozUserAtom,
+ 0, (65536 / sizeof(long)),
+ False, XA_STRING,
+ &type, &format, &nitems, &bytesafter,
+ &data_return);
+
+ // if there's a username compare it with what we have
+ if (data_return) {
+ // If the IDs aren't equal, we don't want this window.
+ if (strcmp(username, (const char *)data_return)) {
+ XFree(data_return);
+ continue;
+ }
+
+ XFree(data_return);
+ }
+ }
+
+ // Check to see if there's a profile name on this window. If
+ // there is, then we need to make sure it matches what someone
+ // passed in.
+ if (aProfile) {
+ status = XGetWindowProperty(mDisplay, w, mMozProfileAtom,
+ 0, (65536 / sizeof(long)),
+ False, XA_STRING,
+ &type, &format, &nitems, &bytesafter,
+ &data_return);
+
+ // If there's a profile compare it with what we have
+ if (data_return) {
+ // If the profiles aren't equal, we don't want this window.
+ if (strcmp(aProfile, (const char *)data_return)) {
+ XFree(data_return);
+ continue;
+ }
+
+ XFree(data_return);
+ }
+ }
+
+ // Check to see if the window supports the new command-line passing
+ // protocol, if that is requested.
+
+ // If we got this far, this is the best window. It passed
+ // all the tests.
+ bestWindow = w;
+ break;
+ }
+
+ if (kids)
+ XFree((char *) kids);
+
+ return bestWindow;
+}
+
+nsresult
+XRemoteClient::FreeLock(Window aWindow)
+{
+ int result;
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *data = 0;
+
+ result = XGetWindowProperty(mDisplay, aWindow, mMozLockAtom,
+ 0, (65536 / sizeof(long)),
+ True, /* atomic delete after */
+ XA_STRING,
+ &actual_type, &actual_format,
+ &nitems, &bytes_after,
+ &data);
+ if (result != Success) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("unable to read and delete " MOZILLA_LOCK_PROP
+ " property\n"));
+ return NS_ERROR_FAILURE;
+ }
+ else if (!data || !*data){
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("invalid data on " MOZILLA_LOCK_PROP
+ " of window 0x%x.\n",
+ (unsigned int) aWindow));
+ return NS_ERROR_FAILURE;
+ }
+ else if (strcmp((char *)data, mLockData)) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ (MOZILLA_LOCK_PROP " was stolen! Expected \"%s\", saw \"%s\"!\n",
+ mLockData, data));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (data)
+ XFree(data);
+ return NS_OK;
+}
+
+/* like strcpy, but return the char after the final null */
+static char*
+estrcpy(const char* s, char* d)
+{
+ while (*s)
+ *d++ = *s++;
+
+ *d++ = '\0';
+ return d;
+}
+
+nsresult
+XRemoteClient::DoSendCommandLine(Window aWindow, int32_t argc, char **argv,
+ const char* aDesktopStartupID,
+ char **aResponse, bool *aDestroyed)
+{
+ *aDestroyed = false;
+
+ char cwdbuf[MAX_PATH];
+ if (!getcwd(cwdbuf, MAX_PATH))
+ return NS_ERROR_UNEXPECTED;
+
+ // the commandline property is constructed as an array of int32_t
+ // followed by a series of null-terminated strings:
+ //
+ // [argc][offsetargv0][offsetargv1...]<workingdir>\0<argv[0]>\0argv[1]...\0
+ // (offset is from the beginning of the buffer)
+
+ static char desktopStartupPrefix[] = " DESKTOP_STARTUP_ID=";
+
+ int32_t argvlen = strlen(cwdbuf);
+ for (int i = 0; i < argc; ++i) {
+ int32_t len = strlen(argv[i]);
+ if (i == 0 && aDesktopStartupID) {
+ len += sizeof(desktopStartupPrefix) - 1 + strlen(aDesktopStartupID);
+ }
+ argvlen += len;
+ }
+
+ int32_t* buffer = (int32_t*) malloc(argvlen + argc + 1 +
+ sizeof(int32_t) * (argc + 1));
+ if (!buffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ buffer[0] = TO_LITTLE_ENDIAN32(argc);
+
+ char *bufend = (char*) (buffer + argc + 1);
+
+ bufend = estrcpy(cwdbuf, bufend);
+
+ for (int i = 0; i < argc; ++i) {
+ buffer[i + 1] = TO_LITTLE_ENDIAN32(bufend - ((char*) buffer));
+ bufend = estrcpy(argv[i], bufend);
+ if (i == 0 && aDesktopStartupID) {
+ bufend = estrcpy(desktopStartupPrefix, bufend - 1);
+ bufend = estrcpy(aDesktopStartupID, bufend - 1);
+ }
+ }
+
+#ifdef DEBUG_bsmedberg
+ int32_t debug_argc = TO_LITTLE_ENDIAN32(*buffer);
+ char *debug_workingdir = (char*) (buffer + argc + 1);
+
+ printf("Sending command line:\n"
+ " working dir: %s\n"
+ " argc:\t%i",
+ debug_workingdir,
+ debug_argc);
+
+ int32_t *debug_offset = buffer + 1;
+ for (int debug_i = 0; debug_i < debug_argc; ++debug_i)
+ printf(" argv[%i]:\t%s\n", debug_i,
+ ((char*) buffer) + TO_LITTLE_ENDIAN32(debug_offset[debug_i]));
+#endif
+
+ XChangeProperty (mDisplay, aWindow, mMozCommandLineAtom, XA_STRING, 8,
+ PropModeReplace, (unsigned char *) buffer,
+ bufend - ((char*) buffer));
+ free(buffer);
+
+ if (!WaitForResponse(aWindow, aResponse, aDestroyed, mMozCommandLineAtom))
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+bool
+XRemoteClient::WaitForResponse(Window aWindow, char **aResponse,
+ bool *aDestroyed, Atom aCommandAtom)
+{
+ bool done = false;
+ bool accepted = false;
+
+ while (!done) {
+ XEvent event;
+ XNextEvent (mDisplay, &event);
+ if (event.xany.type == DestroyNotify &&
+ event.xdestroywindow.window == aWindow) {
+ /* Print to warn user...*/
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("window 0x%x was destroyed.\n",
+ (unsigned int) aWindow));
+ *aResponse = strdup("Window was destroyed while reading response.");
+ *aDestroyed = true;
+ return false;
+ }
+ else if (event.xany.type == PropertyNotify &&
+ event.xproperty.state == PropertyNewValue &&
+ event.xproperty.window == aWindow &&
+ event.xproperty.atom == mMozResponseAtom) {
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char *data = 0;
+ Bool result;
+ result = XGetWindowProperty (mDisplay, aWindow, mMozResponseAtom,
+ 0, (65536 / sizeof (long)),
+ True, /* atomic delete after */
+ XA_STRING,
+ &actual_type, &actual_format,
+ &nitems, &bytes_after,
+ &data);
+ if (result != Success) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("failed reading " MOZILLA_RESPONSE_PROP
+ " from window 0x%0x.\n",
+ (unsigned int) aWindow));
+ *aResponse = strdup("Internal error reading response from window.");
+ done = true;
+ }
+ else if (!data || strlen((char *) data) < 5) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("invalid data on " MOZILLA_RESPONSE_PROP
+ " property of window 0x%0x.\n",
+ (unsigned int) aWindow));
+ *aResponse = strdup("Server returned invalid data in response.");
+ done = true;
+ }
+ else if (*data == '1') { /* positive preliminary reply */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("%s\n", data + 4));
+ /* keep going */
+ done = false;
+ }
+
+ else if (!strncmp ((char *)data, "200", 3)) { /* positive completion */
+ *aResponse = strdup((char *)data);
+ accepted = true;
+ done = true;
+ }
+
+ else if (*data == '2') { /* positive completion */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("%s\n", data + 4));
+ *aResponse = strdup((char *)data);
+ accepted = true;
+ done = true;
+ }
+
+ else if (*data == '3') { /* positive intermediate reply */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("internal error: "
+ "server wants more information? (%s)\n",
+ data));
+ *aResponse = strdup((char *)data);
+ done = true;
+ }
+
+ else if (*data == '4' || /* transient negative completion */
+ *data == '5') { /* permanent negative completion */
+ MOZ_LOG(sRemoteLm, LogLevel::Debug, ("%s\n", data + 4));
+ *aResponse = strdup((char *)data);
+ done = true;
+ }
+
+ else {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("unrecognised " MOZILLA_RESPONSE_PROP
+ " from window 0x%x: %s\n",
+ (unsigned int) aWindow, data));
+ *aResponse = strdup((char *)data);
+ done = true;
+ }
+
+ if (data)
+ XFree(data);
+ }
+
+ else if (event.xany.type == PropertyNotify &&
+ event.xproperty.window == aWindow &&
+ event.xproperty.state == PropertyDelete &&
+ event.xproperty.atom == aCommandAtom) {
+ MOZ_LOG(sRemoteLm, LogLevel::Debug,
+ ("(server 0x%x has accepted "
+ MOZILLA_COMMANDLINE_PROP ".)\n",
+ (unsigned int) aWindow));
+ }
+
+ }
+
+ return accepted;
+}
diff --git a/widget/xremoteclient/XRemoteClient.h b/widget/xremoteclient/XRemoteClient.h
new file mode 100644
index 000000000..840716ad9
--- /dev/null
+++ b/widget/xremoteclient/XRemoteClient.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+
+#include "nsRemoteClient.h"
+
+class XRemoteClient : public nsRemoteClient
+{
+public:
+ XRemoteClient();
+ ~XRemoteClient();
+
+ virtual nsresult Init();
+ virtual nsresult SendCommandLine(const char *aProgram, const char *aUsername,
+ const char *aProfile,
+ int32_t argc, char **argv,
+ const char* aDesktopStartupID,
+ char **aResponse, bool *aSucceeded);
+ void Shutdown();
+
+private:
+
+ Window CheckWindow (Window aWindow);
+ Window CheckChildren (Window aWindow);
+ nsresult GetLock (Window aWindow, bool *aDestroyed);
+ nsresult FreeLock (Window aWindow);
+ Window FindBestWindow (const char *aProgram,
+ const char *aUsername,
+ const char *aProfile);
+ nsresult DoSendCommandLine(Window aWindow,
+ int32_t argc, char **argv,
+ const char* aDesktopStartupID,
+ char **aResponse,
+ bool *aDestroyed);
+ bool WaitForResponse (Window aWindow, char **aResponse,
+ bool *aDestroyed, Atom aCommandAtom);
+
+ Display *mDisplay;
+
+ Atom mMozVersionAtom;
+ Atom mMozLockAtom;
+ Atom mMozCommandLineAtom;
+ Atom mMozResponseAtom;
+ Atom mMozWMStateAtom;
+ Atom mMozUserAtom;
+ Atom mMozProfileAtom;
+ Atom mMozProgramAtom;
+
+ char *mLockData;
+
+ bool mInitialized;
+};
diff --git a/widget/xremoteclient/moz.build b/widget/xremoteclient/moz.build
new file mode 100644
index 000000000..8d3f01c82
--- /dev/null
+++ b/widget/xremoteclient/moz.build
@@ -0,0 +1,11 @@
+# -*- 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/.
+
+FINAL_LIBRARY = 'xul'
+
+SOURCES += [
+ 'XRemoteClient.cpp',
+]
diff --git a/widget/xremoteclient/nsRemoteClient.h b/widget/xremoteclient/nsRemoteClient.h
new file mode 100644
index 000000000..6d90d693f
--- /dev/null
+++ b/widget/xremoteclient/nsRemoteClient.h
@@ -0,0 +1,62 @@
+/* -*- Mode: IDL; 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 nsRemoteClient_h__
+#define nsRemoteClient_h__
+
+#include "nscore.h"
+
+/**
+ * Pure-virtual common base class for remoting implementations.
+ */
+
+class nsRemoteClient
+{
+public:
+ /**
+ * Initializes the client
+ */
+ virtual nsresult Init() = 0;
+
+ /**
+ * Send a complete command line to a running instance.
+ *
+ * @param aProgram This is the preferred program that we want to use
+ * for this particular command.
+ *
+ * @param aUsername This allows someone to only talk to an instance
+ * of the server that's running under a particular username. If
+ * this isn't specified here it's pulled from the LOGNAME
+ * environmental variable if it's set.
+ *
+ * @param aProfile This allows you to specify a particular server
+ * running under a named profile. If it is not specified the
+ * profile is not checked.
+ *
+ * @param argc The number of command-line arguments.
+ *
+ * @param argv The command-line arguments.
+ *
+ * @param aDesktopStartupID the contents of the DESKTOP_STARTUP_ID environment
+ * variable defined by the Startup Notification specification
+ * http://standards.freedesktop.org/startup-notification-spec/startup-notification-0.1.txt
+ *
+ * @param aResponse If there is a response, it will be here. This
+ * includes error messages. The string is allocated using stdlib
+ * string functions, so free it with free().
+ *
+ * @return true if succeeded, false if no running instance was found.
+ *
+ */
+ virtual nsresult SendCommandLine(const char *aProgram, const char *aUsername,
+ const char *aProfile,
+ int32_t argc, char **argv,
+ const char* aDesktopStartupID,
+ char **aResponse, bool *aSucceeded) = 0;
+};
+
+#endif // nsRemoteClient_h__