summaryrefslogtreecommitdiff
path: root/widget/cocoa
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /widget/cocoa
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'widget/cocoa')
-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
149 files changed, 39986 insertions, 0 deletions
diff --git a/widget/cocoa/ComplexTextInputPanel.h b/widget/cocoa/ComplexTextInputPanel.h
new file mode 100644
index 0000000000..648e6d9114
--- /dev/null
+++ b/widget/cocoa/ComplexTextInputPanel.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Modified by Josh Aas of Mozilla Corporation.
+ */
+
+#ifndef ComplexTextInputPanel_h_
+#define ComplexTextInputPanel_h_
+
+#include "nsString.h"
+#include "npapi.h"
+
+class ComplexTextInputPanel
+{
+public:
+ static ComplexTextInputPanel* GetSharedComplexTextInputPanel();
+ virtual void PlacePanel(int32_t x, int32_t y) = 0; // Bottom left coordinate of plugin in screen coords
+ virtual void InterpretKeyEvent(void* aEvent, nsAString& aOutText) = 0;
+ virtual bool IsInComposition() = 0;
+ virtual void* GetInputContext() = 0;
+ virtual void CancelComposition() = 0;
+
+protected:
+ virtual ~ComplexTextInputPanel() {};
+};
+
+#endif // ComplexTextInputPanel_h_
diff --git a/widget/cocoa/ComplexTextInputPanel.mm b/widget/cocoa/ComplexTextInputPanel.mm
new file mode 100644
index 0000000000..a4b58955e2
--- /dev/null
+++ b/widget/cocoa/ComplexTextInputPanel.mm
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Modified by Josh Aas of Mozilla Corporation.
+ */
+
+#import "ComplexTextInputPanel.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include <algorithm>
+#include "mozilla/Preferences.h"
+#include "nsChildView.h"
+
+using namespace mozilla;
+
+extern "C" OSStatus TSMProcessRawKeyEvent(EventRef anEvent);
+
+#define kInputWindowHeight 20
+
+@interface ComplexTextInputPanelImpl : NSPanel {
+ NSTextView *mInputTextView;
+}
+
++ (ComplexTextInputPanelImpl*)sharedComplexTextInputPanelImpl;
+
+- (NSTextInputContext*)inputContext;
+- (void)interpretKeyEvent:(NSEvent*)event string:(NSString**)string;
+- (void)cancelComposition;
+- (BOOL)inComposition;
+
+// This places the text input panel fully onscreen and below the lower left
+// corner of the focused plugin.
+- (void)adjustTo:(NSPoint)point;
+
+@end
+
+@implementation ComplexTextInputPanelImpl
+
++ (ComplexTextInputPanelImpl*)sharedComplexTextInputPanelImpl
+{
+ static ComplexTextInputPanelImpl *sComplexTextInputPanelImpl;
+ if (!sComplexTextInputPanelImpl)
+ sComplexTextInputPanelImpl = [[ComplexTextInputPanelImpl alloc] init];
+ return sComplexTextInputPanelImpl;
+}
+
+- (id)init
+{
+ // In the original Apple code the style mask is given by a function which is not open source.
+ // What could possibly be worth hiding in that function, I do not know.
+ // Courtesy of gdb: stylemask: 011000011111, 0x61f
+ self = [super initWithContentRect:NSZeroRect styleMask:0x61f backing:NSBackingStoreBuffered defer:YES];
+ if (!self)
+ return nil;
+
+ // Set the frame size.
+ NSRect visibleFrame = [[NSScreen mainScreen] visibleFrame];
+ NSRect frame = NSMakeRect(visibleFrame.origin.x, visibleFrame.origin.y, visibleFrame.size.width, kInputWindowHeight);
+
+ [self setFrame:frame display:NO];
+
+ mInputTextView = [[NSTextView alloc] initWithFrame:[self.contentView frame]];
+ mInputTextView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable | NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin | NSViewMinYMargin;
+
+ NSScrollView* scrollView = [[NSScrollView alloc] initWithFrame:[self.contentView frame]];
+ scrollView.documentView = mInputTextView;
+ self.contentView = scrollView;
+ [scrollView release];
+
+ [self setFloatingPanel:YES];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(keyboardInputSourceChanged:)
+ name:NSTextInputContextKeyboardSelectionDidChangeNotification
+ object:nil];
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [mInputTextView release];
+
+ [super dealloc];
+}
+
+- (void)keyboardInputSourceChanged:(NSNotification *)notification
+{
+ static int8_t sDoCancel = -1;
+ if (!sDoCancel || ![self inComposition]) {
+ return;
+ }
+ if (sDoCancel < 0) {
+ bool cancelComposition = false;
+ static const char* kPrefName =
+ "ui.plugin.cancel_composition_at_input_source_changed";
+ nsresult rv = Preferences::GetBool(kPrefName, &cancelComposition);
+ NS_ENSURE_SUCCESS(rv, );
+ sDoCancel = cancelComposition ? 1 : 0;
+ }
+ if (sDoCancel) {
+ [self cancelComposition];
+ }
+}
+
+- (void)interpretKeyEvent:(NSEvent*)event string:(NSString**)string
+{
+ *string = nil;
+
+ if (![[mInputTextView inputContext] handleEvent:event]) {
+ return;
+ }
+
+ if ([mInputTextView hasMarkedText]) {
+ // Don't show the input method window for dead keys
+ if ([[event characters] length] > 0) {
+ [self orderFront:nil];
+ }
+ return;
+ } else {
+ [self orderOut:nil];
+
+ NSString *text = [[mInputTextView textStorage] string];
+ if ([text length] > 0) {
+ *string = [[text copy] autorelease];
+ }
+ }
+
+ [mInputTextView setString:@""];
+}
+
+- (NSTextInputContext*)inputContext
+{
+ return [mInputTextView inputContext];
+}
+
+- (void)cancelComposition
+{
+ [mInputTextView setString:@""];
+ [self orderOut:nil];
+}
+
+- (BOOL)inComposition
+{
+ return [mInputTextView hasMarkedText];
+}
+
+- (void)adjustTo:(NSPoint)point
+{
+ NSRect selfRect = [self frame];
+ NSRect rect = NSMakeRect(point.x,
+ point.y - selfRect.size.height,
+ 500,
+ selfRect.size.height);
+
+ // Adjust to screen.
+ NSRect screenRect = [[NSScreen mainScreen] visibleFrame];
+ if (rect.origin.x < screenRect.origin.x) {
+ rect.origin.x = screenRect.origin.x;
+ }
+ if (rect.origin.y < screenRect.origin.y) {
+ rect.origin.y = screenRect.origin.y;
+ }
+ CGFloat xMostOfScreen = screenRect.origin.x + screenRect.size.width;
+ CGFloat yMostOfScreen = screenRect.origin.y + screenRect.size.height;
+ CGFloat xMost = rect.origin.x + rect.size.width;
+ CGFloat yMost = rect.origin.y + rect.size.height;
+ if (xMostOfScreen < xMost) {
+ rect.origin.x -= xMost - xMostOfScreen;
+ }
+ if (yMostOfScreen < yMost) {
+ rect.origin.y -= yMost - yMostOfScreen;
+ }
+
+ [self setFrame:rect display:[self isVisible]];
+}
+
+@end
+
+class ComplexTextInputPanelPrivate : public ComplexTextInputPanel
+{
+public:
+ ComplexTextInputPanelPrivate();
+
+ virtual void InterpretKeyEvent(void* aEvent, nsAString& aOutText);
+ virtual bool IsInComposition();
+ virtual void PlacePanel(int32_t x, int32_t y);
+ virtual void* GetInputContext() { return [mPanel inputContext]; }
+ virtual void CancelComposition() { [mPanel cancelComposition]; }
+
+private:
+ ~ComplexTextInputPanelPrivate();
+ ComplexTextInputPanelImpl* mPanel;
+};
+
+ComplexTextInputPanelPrivate::ComplexTextInputPanelPrivate()
+{
+ mPanel = [[ComplexTextInputPanelImpl alloc] init];
+}
+
+ComplexTextInputPanelPrivate::~ComplexTextInputPanelPrivate()
+{
+ [mPanel release];
+}
+
+ComplexTextInputPanel*
+ComplexTextInputPanel::GetSharedComplexTextInputPanel()
+{
+ static ComplexTextInputPanelPrivate *sComplexTextInputPanelPrivate;
+ if (!sComplexTextInputPanelPrivate) {
+ sComplexTextInputPanelPrivate = new ComplexTextInputPanelPrivate();
+ }
+ return sComplexTextInputPanelPrivate;
+}
+
+void
+ComplexTextInputPanelPrivate::InterpretKeyEvent(void* aEvent, nsAString& aOutText)
+{
+ NSString* textString = nil;
+ [mPanel interpretKeyEvent:(NSEvent*)aEvent string:&textString];
+
+ if (textString) {
+ nsCocoaUtils::GetStringForNSString(textString, aOutText);
+ }
+}
+
+bool
+ComplexTextInputPanelPrivate::IsInComposition()
+{
+ return !![mPanel inComposition];
+}
+
+void
+ComplexTextInputPanelPrivate::PlacePanel(int32_t x, int32_t y)
+{
+ [mPanel adjustTo:NSMakePoint(x, y)];
+}
diff --git a/widget/cocoa/CustomCocoaEvents.h b/widget/cocoa/CustomCocoaEvents.h
new file mode 100644
index 0000000000..0043f0d695
--- /dev/null
+++ b/widget/cocoa/CustomCocoaEvents.h
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file defines constants to be used in the "subtype" field of
+ * NSApplicationDefined type NSEvents.
+ */
+
+#ifndef WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_
+#define WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_
+
+// Empty event, just used for prodding the event loop into responding.
+const short kEventSubtypeNone = 0;
+// Tracer event, used for timing the event loop responsiveness.
+const short kEventSubtypeTrace = 1;
+
+#endif /* WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_ */
diff --git a/widget/cocoa/GfxInfo.h b/widget/cocoa/GfxInfo.h
new file mode 100644
index 0000000000..05bdad158b
--- /dev/null
+++ b/widget/cocoa/GfxInfo.h
@@ -0,0 +1,95 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __mozilla_widget_GfxInfo_h__
+#define __mozilla_widget_GfxInfo_h__
+
+#include "GfxInfoBase.h"
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo : public GfxInfoBase
+{
+public:
+
+ GfxInfo();
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool *aD2DEnabled) override;
+ NS_IMETHOD GetDWriteEnabled(bool *aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString & aDwriteVersion) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString & aCleartypeParams) override;
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active) override;
+
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+ virtual nsresult Init() override;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ virtual uint32_t OperatingSystemVersion() override { return mOSXVersion; }
+
+ nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override;
+
+protected:
+
+ virtual ~GfxInfo() {}
+
+ virtual nsresult GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString &aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+private:
+
+ void GetDeviceInfo();
+ void GetSelectedCityInfo();
+ void AddCrashReportAnnotations();
+
+ nsString mAdapterRAMString;
+ nsString mDeviceID;
+ nsString mDriverVersion;
+ nsString mDriverDate;
+ nsString mDeviceKey;
+
+ nsString mAdapterVendorID;
+ nsString mAdapterDeviceID;
+
+ uint32_t mOSXVersion;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/cocoa/GfxInfo.mm b/widget/cocoa/GfxInfo.mm
new file mode 100644
index 0000000000..6789ae8b23
--- /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 0000000000..d1ba2c3701
--- /dev/null
+++ b/widget/cocoa/NativeKeyBindings.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_NativeKeyBindings_h_
+#define mozilla_widget_NativeKeyBindings_h_
+
+#import <Cocoa/Cocoa.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsDataHashtable.h"
+#include "nsIWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+typedef nsDataHashtable<nsPtrHashKey<struct objc_selector>, CommandInt>
+ SelectorCommandHashtable;
+
+class NativeKeyBindings final
+{
+ typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType;
+ typedef nsIWidget::DoCommandCallback DoCommandCallback;
+
+public:
+ static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
+ static void Shutdown();
+
+ void Init(NativeKeyBindingsType aType);
+
+ bool Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData);
+
+private:
+ NativeKeyBindings();
+
+ SelectorCommandHashtable mSelectorToCommand;
+
+ static NativeKeyBindings* sInstanceForSingleLineEditor;
+ static NativeKeyBindings* sInstanceForMultiLineEditor;
+}; // NativeKeyBindings
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_NativeKeyBindings_h_
diff --git a/widget/cocoa/NativeKeyBindings.mm b/widget/cocoa/NativeKeyBindings.mm
new file mode 100644
index 0000000000..2f4ecadff0
--- /dev/null
+++ b/widget/cocoa/NativeKeyBindings.mm
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NativeKeyBindings.h"
+
+#include "nsTArray.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/TextEvents.h"
+
+namespace mozilla {
+namespace widget {
+
+PRLogModuleInfo* gNativeKeyBindingsLog = nullptr;
+
+NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
+NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
+
+// static
+NativeKeyBindings*
+NativeKeyBindings::GetInstance(NativeKeyBindingsType aType)
+{
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ if (!sInstanceForSingleLineEditor) {
+ sInstanceForSingleLineEditor = new NativeKeyBindings();
+ sInstanceForSingleLineEditor->Init(aType);
+ }
+ return sInstanceForSingleLineEditor;
+ case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ if (!sInstanceForMultiLineEditor) {
+ sInstanceForMultiLineEditor = new NativeKeyBindings();
+ sInstanceForMultiLineEditor->Init(aType);
+ }
+ return sInstanceForMultiLineEditor;
+ default:
+ MOZ_CRASH("Not implemented");
+ return nullptr;
+ }
+}
+
+// static
+void
+NativeKeyBindings::Shutdown()
+{
+ delete sInstanceForSingleLineEditor;
+ sInstanceForSingleLineEditor = nullptr;
+ delete sInstanceForMultiLineEditor;
+ sInstanceForMultiLineEditor = nullptr;
+}
+
+NativeKeyBindings::NativeKeyBindings()
+{
+}
+
+#define SEL_TO_COMMAND(aSel, aCommand) \
+ mSelectorToCommand.Put( \
+ reinterpret_cast<struct objc_selector *>(@selector(aSel)), aCommand)
+
+void
+NativeKeyBindings::Init(NativeKeyBindingsType aType)
+{
+ if (!gNativeKeyBindingsLog) {
+ gNativeKeyBindingsLog = PR_NewLogModule("NativeKeyBindings");
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::Init", this));
+
+ // Many selectors have a one-to-one mapping to a Gecko command. Those mappings
+ // are registered in mSelectorToCommand.
+
+ // Selectors from NSResponder's "Responding to Action Messages" section and
+ // from NSText's "Action Methods for Editing" section
+
+ // TODO: Improves correctness of left / right meaning
+ // TODO: Add real paragraph motions
+
+ // SEL_TO_COMMAND(cancelOperation:, );
+ // SEL_TO_COMMAND(capitalizeWord:, );
+ // SEL_TO_COMMAND(centerSelectionInVisibleArea:, );
+ // SEL_TO_COMMAND(changeCaseOfLetter:, );
+ // SEL_TO_COMMAND(complete:, );
+ SEL_TO_COMMAND(copy:, CommandCopy);
+ // SEL_TO_COMMAND(copyFont:, );
+ // SEL_TO_COMMAND(copyRuler:, );
+ SEL_TO_COMMAND(cut:, CommandCut);
+ SEL_TO_COMMAND(delete:, CommandDelete);
+ SEL_TO_COMMAND(deleteBackward:, CommandDeleteCharBackward);
+ // SEL_TO_COMMAND(deleteBackwardByDecomposingPreviousCharacter:, );
+ SEL_TO_COMMAND(deleteForward:, CommandDeleteCharForward);
+
+ // TODO: deleteTo* selectors are also supposed to add text to a kill buffer
+ SEL_TO_COMMAND(deleteToBeginningOfLine:, CommandDeleteToBeginningOfLine);
+ SEL_TO_COMMAND(deleteToBeginningOfParagraph:, CommandDeleteToBeginningOfLine);
+ SEL_TO_COMMAND(deleteToEndOfLine:, CommandDeleteToEndOfLine);
+ SEL_TO_COMMAND(deleteToEndOfParagraph:, CommandDeleteToEndOfLine);
+ // SEL_TO_COMMAND(deleteToMark:, );
+
+ SEL_TO_COMMAND(deleteWordBackward:, CommandDeleteWordBackward);
+ SEL_TO_COMMAND(deleteWordForward:, CommandDeleteWordForward);
+ // SEL_TO_COMMAND(indent:, );
+ // SEL_TO_COMMAND(insertBacktab:, );
+ // SEL_TO_COMMAND(insertContainerBreak:, );
+ // SEL_TO_COMMAND(insertLineBreak:, );
+ // SEL_TO_COMMAND(insertNewline:, );
+ // SEL_TO_COMMAND(insertNewlineIgnoringFieldEditor:, );
+ // SEL_TO_COMMAND(insertParagraphSeparator:, );
+ // SEL_TO_COMMAND(insertTab:, );
+ // SEL_TO_COMMAND(insertTabIgnoringFieldEditor:, );
+ // SEL_TO_COMMAND(insertDoubleQuoteIgnoringSubstitution:, );
+ // SEL_TO_COMMAND(insertSingleQuoteIgnoringSubstitution:, );
+ // SEL_TO_COMMAND(lowercaseWord:, );
+ SEL_TO_COMMAND(moveBackward:, CommandCharPrevious);
+ SEL_TO_COMMAND(moveBackwardAndModifySelection:, CommandSelectCharPrevious);
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(moveDown:, CommandEndLine);
+ } else {
+ SEL_TO_COMMAND(moveDown:, CommandLineNext);
+ }
+ SEL_TO_COMMAND(moveDownAndModifySelection:, CommandSelectLineNext);
+ SEL_TO_COMMAND(moveForward:, CommandCharNext);
+ SEL_TO_COMMAND(moveForwardAndModifySelection:, CommandSelectCharNext);
+ SEL_TO_COMMAND(moveLeft:, CommandCharPrevious);
+ SEL_TO_COMMAND(moveLeftAndModifySelection:, CommandSelectCharPrevious);
+ SEL_TO_COMMAND(moveParagraphBackwardAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveParagraphForwardAndModifySelection:, CommandSelectEndLine);
+ SEL_TO_COMMAND(moveRight:, CommandCharNext);
+ SEL_TO_COMMAND(moveRightAndModifySelection:, CommandSelectCharNext);
+ SEL_TO_COMMAND(moveToBeginningOfDocument:, CommandMoveTop);
+ SEL_TO_COMMAND(moveToBeginningOfDocumentAndModifySelection:,
+ CommandSelectTop);
+ SEL_TO_COMMAND(moveToBeginningOfLine:, CommandBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfLineAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfParagraph:, CommandBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfParagraphAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveToEndOfDocument:, CommandMoveBottom);
+ SEL_TO_COMMAND(moveToEndOfDocumentAndModifySelection:, CommandSelectBottom);
+ SEL_TO_COMMAND(moveToEndOfLine:, CommandEndLine);
+ SEL_TO_COMMAND(moveToEndOfLineAndModifySelection:, CommandSelectEndLine);
+ SEL_TO_COMMAND(moveToEndOfParagraph:, CommandEndLine);
+ SEL_TO_COMMAND(moveToEndOfParagraphAndModifySelection:, CommandSelectEndLine);
+ SEL_TO_COMMAND(moveToLeftEndOfLine:, CommandBeginLine);
+ SEL_TO_COMMAND(moveToLeftEndOfLineAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveToRightEndOfLine:, CommandEndLine);
+ SEL_TO_COMMAND(moveToRightEndOfLineAndModifySelection:, CommandSelectEndLine);
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(moveUp:, CommandBeginLine);
+ } else {
+ SEL_TO_COMMAND(moveUp:, CommandLinePrevious);
+ }
+ SEL_TO_COMMAND(moveUpAndModifySelection:, CommandSelectLinePrevious);
+ SEL_TO_COMMAND(moveWordBackward:, CommandWordPrevious);
+ SEL_TO_COMMAND(moveWordBackwardAndModifySelection:,
+ CommandSelectWordPrevious);
+ SEL_TO_COMMAND(moveWordForward:, CommandWordNext);
+ SEL_TO_COMMAND(moveWordForwardAndModifySelection:, CommandSelectWordNext);
+ SEL_TO_COMMAND(moveWordLeft:, CommandWordPrevious);
+ SEL_TO_COMMAND(moveWordLeftAndModifySelection:, CommandSelectWordPrevious);
+ SEL_TO_COMMAND(moveWordRight:, CommandWordNext);
+ SEL_TO_COMMAND(moveWordRightAndModifySelection:, CommandSelectWordNext);
+ SEL_TO_COMMAND(pageDown:, CommandMovePageDown);
+ SEL_TO_COMMAND(pageDownAndModifySelection:, CommandSelectPageDown);
+ SEL_TO_COMMAND(pageUp:, CommandMovePageUp);
+ SEL_TO_COMMAND(pageUpAndModifySelection:, CommandSelectPageUp);
+ SEL_TO_COMMAND(paste:, CommandPaste);
+ // SEL_TO_COMMAND(pasteFont:, );
+ // SEL_TO_COMMAND(pasteRuler:, );
+ SEL_TO_COMMAND(scrollLineDown:, CommandScrollLineDown);
+ SEL_TO_COMMAND(scrollLineUp:, CommandScrollLineUp);
+ SEL_TO_COMMAND(scrollPageDown:, CommandScrollPageDown);
+ SEL_TO_COMMAND(scrollPageUp:, CommandScrollPageUp);
+ SEL_TO_COMMAND(scrollToBeginningOfDocument:, CommandScrollTop);
+ SEL_TO_COMMAND(scrollToEndOfDocument:, CommandScrollBottom);
+ SEL_TO_COMMAND(selectAll:, CommandSelectAll);
+ // selectLine: is complex, see KeyDown
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(selectParagraph:, CommandSelectAll);
+ }
+ // SEL_TO_COMMAND(selectToMark:, );
+ // selectWord: is complex, see KeyDown
+ // SEL_TO_COMMAND(setMark:, );
+ // SEL_TO_COMMAND(showContextHelp:, );
+ // SEL_TO_COMMAND(supplementalTargetForAction:sender:, );
+ // SEL_TO_COMMAND(swapWithMark:, );
+ // SEL_TO_COMMAND(transpose:, );
+ // SEL_TO_COMMAND(transposeWords:, );
+ // SEL_TO_COMMAND(uppercaseWord:, );
+ // SEL_TO_COMMAND(yank:, );
+}
+
+#undef SEL_TO_COMMAND
+
+bool
+NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress", this));
+
+ // Recover the current event, which should always be the key down we are
+ // responding to.
+
+ NSEvent* cocoaEvent = reinterpret_cast<NSEvent*>(aEvent.mNativeKeyEvent);
+
+ if (!cocoaEvent || [cocoaEvent type] != NSKeyDown) {
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, no Cocoa key down event", this));
+
+ return false;
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, interpreting", this));
+
+ AutoTArray<KeyBindingsCommand, 2> bindingCommands;
+ nsCocoaUtils::GetCommandsFromKeyEvent(cocoaEvent, bindingCommands);
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, bindingCommands=%u",
+ this, bindingCommands.Length()));
+
+ AutoTArray<Command, 4> geckoCommands;
+
+ for (uint32_t i = 0; i < bindingCommands.Length(); i++) {
+ SEL selector = bindingCommands[i].selector;
+
+ if (MOZ_LOG_TEST(gNativeKeyBindingsLog, LogLevel::Info)) {
+ NSString* selectorString = NSStringFromSelector(selector);
+ nsAutoString nsSelectorString;
+ nsCocoaUtils::GetStringForNSString(selectorString, nsSelectorString);
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, selector=%s",
+ this, NS_LossyConvertUTF16toASCII(nsSelectorString).get()));
+ }
+
+ // Try to find a simple mapping in the hashtable
+ Command geckoCommand = static_cast<Command>(mSelectorToCommand.Get(
+ reinterpret_cast<struct objc_selector*>(selector)));
+
+ if (geckoCommand) {
+ geckoCommands.AppendElement(geckoCommand);
+ } else if (selector == @selector(selectLine:)) {
+ // This is functional, but Cocoa's version is direction-less in that
+ // selection direction is not determined until some future directed action
+ // is taken. See bug 282097, comment 79 for more details.
+ geckoCommands.AppendElement(CommandBeginLine);
+ geckoCommands.AppendElement(CommandSelectEndLine);
+ } else if (selector == @selector(selectWord:)) {
+ // This is functional, but Cocoa's version is direction-less in that
+ // selection direction is not determined until some future directed action
+ // is taken. See bug 282097, comment 79 for more details.
+ geckoCommands.AppendElement(CommandWordPrevious);
+ geckoCommands.AppendElement(CommandSelectWordNext);
+ }
+ }
+
+ if (geckoCommands.IsEmpty()) {
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, handled=false", this));
+
+ return false;
+ }
+
+ for (uint32_t i = 0; i < geckoCommands.Length(); i++) {
+ Command geckoCommand = geckoCommands[i];
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, command=%s",
+ this, WidgetKeyboardEvent::GetCommandStr(geckoCommand)));
+
+ // Execute the Gecko command
+ aCallback(geckoCommand, aCallbackData);
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, handled=true", this));
+
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/OSXNotificationCenter.h b/widget/cocoa/OSXNotificationCenter.h
new file mode 100644
index 0000000000..30767b5c55
--- /dev/null
+++ b/widget/cocoa/OSXNotificationCenter.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef OSXNotificationCenter_h
+#define OSXNotificationCenter_h
+
+#import <Foundation/Foundation.h>
+#include "nsIAlertsService.h"
+#include "imgINotificationObserver.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+@class mozNotificationCenterDelegate;
+
+#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
+typedef NSInteger NSUserNotificationActivationType;
+#endif
+
+namespace mozilla {
+
+class OSXNotificationInfo;
+
+class OSXNotificationCenter : public nsIAlertsService,
+ public nsIAlertsIconData,
+ public nsIAlertNotificationImageListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_NSIALERTSICONDATA
+ NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+
+ OSXNotificationCenter();
+
+ nsresult Init();
+ void CloseAlertCocoaString(NSString *aAlertName);
+ void OnActivate(NSString *aAlertName, NSUserNotificationActivationType aActivationType,
+ unsigned long long aAdditionalActionIndex);
+ void ShowPendingNotification(OSXNotificationInfo *osxni);
+
+protected:
+ virtual ~OSXNotificationCenter();
+
+private:
+ mozNotificationCenterDelegate *mDelegate;
+ nsTArray<RefPtr<OSXNotificationInfo> > mActiveAlerts;
+ nsTArray<RefPtr<OSXNotificationInfo> > mPendingAlerts;
+};
+
+} // namespace mozilla
+
+#endif // OSXNotificationCenter_h
diff --git a/widget/cocoa/OSXNotificationCenter.mm b/widget/cocoa/OSXNotificationCenter.mm
new file mode 100644
index 0000000000..e9e36a96be
--- /dev/null
+++ b/widget/cocoa/OSXNotificationCenter.mm
@@ -0,0 +1,589 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSXNotificationCenter.h"
+#import <AppKit/AppKit.h>
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "nsICancelable.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#import "nsCocoaUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+
+using namespace mozilla;
+
+#define MAX_NOTIFICATION_NAME_LEN 5000
+
+#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
+@protocol NSUserNotificationCenterDelegate
+@end
+static NSString * const NSUserNotificationDefaultSoundName = @"DefaultSoundName";
+enum {
+ NSUserNotificationActivationTypeNone = 0,
+ NSUserNotificationActivationTypeContentsClicked = 1,
+ NSUserNotificationActivationTypeActionButtonClicked = 2,
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_9) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9)
+enum {
+ NSUserNotificationActivationTypeReplied = 3,
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
+enum {
+ NSUserNotificationActivationTypeAdditionalActionClicked = 4
+};
+#endif
+
+@protocol FakeNSUserNotification <NSObject>
+@property (copy) NSString* title;
+@property (copy) NSString* subtitle;
+@property (copy) NSString* informativeText;
+@property (copy) NSString* actionButtonTitle;
+@property (copy) NSDictionary* userInfo;
+@property (copy) NSDate* deliveryDate;
+@property (copy) NSTimeZone* deliveryTimeZone;
+@property (copy) NSDateComponents* deliveryRepeatInterval;
+@property (readonly) NSDate* actualDeliveryDate;
+@property (readonly, getter=isPresented) BOOL presented;
+@property (readonly, getter=isRemote) BOOL remote;
+@property (copy) NSString* soundName;
+@property BOOL hasActionButton;
+@property (readonly) NSUserNotificationActivationType activationType;
+@property (copy) NSString *otherButtonTitle;
+@property (copy) NSImage *contentImage;
+@end
+
+@protocol FakeNSUserNotificationCenter <NSObject>
++ (id<FakeNSUserNotificationCenter>)defaultUserNotificationCenter;
+@property (assign) id <NSUserNotificationCenterDelegate> delegate;
+@property (copy) NSArray *scheduledNotifications;
+- (void)scheduleNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeScheduledNotification:(id<FakeNSUserNotification>)notification;
+@property (readonly) NSArray *deliveredNotifications;
+- (void)deliverNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeDeliveredNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeAllDeliveredNotifications;
+- (void)_removeAllDisplayedNotifications;
+- (void)_removeDisplayedNotification:(id<FakeNSUserNotification>)notification;
+@end
+
+@interface mozNotificationCenterDelegate : NSObject <NSUserNotificationCenterDelegate>
+{
+ OSXNotificationCenter *mOSXNC;
+}
+ - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc;
+@end
+
+@implementation mozNotificationCenterDelegate
+
+- (id)initWithOSXNC:(OSXNotificationCenter*)osxnc
+{
+ [super init];
+ // We should *never* outlive this OSXNotificationCenter.
+ mOSXNC = osxnc;
+ return self;
+}
+
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didDeliverNotification:(id<FakeNSUserNotification>)notification
+{
+
+}
+
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didActivateNotification:(id<FakeNSUserNotification>)notification
+{
+ unsigned long long additionalActionIndex = ULLONG_MAX;
+ if ([notification respondsToSelector:@selector(_alternateActionIndex)]) {
+ NSNumber *alternateActionIndex = [(NSObject*)notification valueForKey:@"_alternateActionIndex"];
+ additionalActionIndex = [alternateActionIndex unsignedLongLongValue];
+ }
+ mOSXNC->OnActivate([[notification userInfo] valueForKey:@"name"],
+ notification.activationType,
+ additionalActionIndex);
+}
+
+- (BOOL)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ shouldPresentNotification:(id<FakeNSUserNotification>)notification
+{
+ return YES;
+}
+
+// This is an undocumented method that we need for parity with Safari.
+// Apple bug #15440664.
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didRemoveDeliveredNotifications:(NSArray *)notifications
+{
+ for (id<FakeNSUserNotification> notification in notifications) {
+ NSString *name = [[notification userInfo] valueForKey:@"name"];
+ mOSXNC->CloseAlertCocoaString(name);
+ }
+}
+
+// This is an undocumented method that we need to be notified if a user clicks the close button.
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didDismissAlert:(id<FakeNSUserNotification>)notification
+{
+ NSString *name = [[notification userInfo] valueForKey:@"name"];
+ mOSXNC->CloseAlertCocoaString(name);
+}
+
+@end
+
+namespace mozilla {
+
+enum {
+ OSXNotificationActionDisable = 0,
+ OSXNotificationActionSettings = 1,
+};
+
+class OSXNotificationInfo final : public nsISupports {
+private:
+ virtual ~OSXNotificationInfo();
+
+public:
+ NS_DECL_ISUPPORTS
+ OSXNotificationInfo(NSString *name, nsIObserver *observer,
+ const nsAString & alertCookie);
+
+ NSString *mName;
+ nsCOMPtr<nsIObserver> mObserver;
+ nsString mCookie;
+ RefPtr<nsICancelable> mIconRequest;
+ id<FakeNSUserNotification> mPendingNotifiction;
+};
+
+NS_IMPL_ISUPPORTS0(OSXNotificationInfo)
+
+OSXNotificationInfo::OSXNotificationInfo(NSString *name, nsIObserver *observer,
+ const nsAString & alertCookie)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ASSERTION(name, "Cannot create OSXNotificationInfo without a name!");
+ mName = [name retain];
+ mObserver = observer;
+ mCookie = alertCookie;
+ mPendingNotifiction = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+OSXNotificationInfo::~OSXNotificationInfo()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mName release];
+ [mPendingNotifiction release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static id<FakeNSUserNotificationCenter> GetNotificationCenter() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ Class c = NSClassFromString(@"NSUserNotificationCenter");
+ return [c performSelector:@selector(defaultUserNotificationCenter)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+OSXNotificationCenter::OSXNotificationCenter()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mDelegate = [[mozNotificationCenterDelegate alloc] initWithOSXNC:this];
+ GetNotificationCenter().delegate = mDelegate;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+OSXNotificationCenter::~OSXNotificationCenter()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [GetNotificationCenter() removeAllDeliveredNotifications];
+ [mDelegate release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMPL_ISUPPORTS(OSXNotificationCenter, nsIAlertsService, nsIAlertsIconData,
+ nsIAlertNotificationImageListener)
+
+nsresult OSXNotificationCenter::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ return (!!NSClassFromString(@"NSUserNotification")) ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle,
+ const nsAString & aAlertText, bool aAlertTextClickable,
+ const nsAString & aAlertCookie,
+ nsIObserver * aAlertListener,
+ const nsAString & aAlertName,
+ const nsAString & aBidi,
+ const nsAString & aLang,
+ const nsAString & aData,
+ nsIPrincipal * aPrincipal,
+ bool aInPrivateBrowsing,
+ bool aRequireInteraction)
+{
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
+ nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle,
+ aAlertText, aAlertTextClickable,
+ aAlertCookie, aBidi, aLang, aData,
+ aPrincipal, aInPrivateBrowsing,
+ aRequireInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowPersistentNotification(const nsAString& aPersistentData,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ return ShowAlertWithIconData(aAlert, aAlertListener, 0, nullptr);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlertWithIconData(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener,
+ uint32_t aIconSize,
+ const uint8_t* aIconData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_ARG(aAlert);
+
+ Class unClass = NSClassFromString(@"NSUserNotification");
+ id<FakeNSUserNotification> notification = [[unClass alloc] init];
+
+ nsAutoString title;
+ nsresult rv = aAlert->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ notification.title = nsCocoaUtils::ToNSString(title);
+
+ nsAutoString hostPort;
+ rv = aAlert->GetSource(hostPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ sbs->CreateBundle("chrome://alerts/locale/alert.properties", getter_AddRefs(bundle));
+
+ if (!hostPort.IsEmpty() && bundle) {
+ const char16_t* formatStrings[] = { hostPort.get() };
+ nsXPIDLString notificationSource;
+ bundle->FormatStringFromName(u"source.label",
+ formatStrings,
+ ArrayLength(formatStrings),
+ getter_Copies(notificationSource));
+ notification.subtitle = nsCocoaUtils::ToNSString(notificationSource);
+ }
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+ notification.informativeText = nsCocoaUtils::ToNSString(text);
+
+ notification.soundName = NSUserNotificationDefaultSoundName;
+ notification.hasActionButton = NO;
+
+ // If this is not an application/extension alert, show additional actions dealing with permissions.
+ bool isActionable;
+ if (bundle && NS_SUCCEEDED(aAlert->GetActionable(&isActionable)) && isActionable) {
+ nsXPIDLString closeButtonTitle, actionButtonTitle, disableButtonTitle, settingsButtonTitle;
+ bundle->GetStringFromName(u"closeButton.title",
+ getter_Copies(closeButtonTitle));
+ bundle->GetStringFromName(u"actionButton.label",
+ getter_Copies(actionButtonTitle));
+ if (!hostPort.IsEmpty()) {
+ const char16_t* formatStrings[] = { hostPort.get() };
+ bundle->FormatStringFromName(u"webActions.disableForOrigin.label",
+ formatStrings,
+ ArrayLength(formatStrings),
+ getter_Copies(disableButtonTitle));
+ }
+ bundle->GetStringFromName(u"webActions.settings.label",
+ getter_Copies(settingsButtonTitle));
+
+ notification.otherButtonTitle = nsCocoaUtils::ToNSString(closeButtonTitle);
+
+ // OS X 10.8 only shows action buttons if the "Alerts" style is set in
+ // Notification Center preferences, and doesn't support the alternate
+ // action menu.
+ if ([notification respondsToSelector:@selector(set_showsButtons:)] &&
+ [notification respondsToSelector:@selector(set_alwaysShowAlternateActionMenu:)] &&
+ [notification respondsToSelector:@selector(set_alternateActionButtonTitles:)]) {
+
+ notification.hasActionButton = YES;
+ notification.actionButtonTitle = nsCocoaUtils::ToNSString(actionButtonTitle);
+
+ [(NSObject*)notification setValue:@(YES) forKey:@"_showsButtons"];
+ [(NSObject*)notification setValue:@(YES) forKey:@"_alwaysShowAlternateActionMenu"];
+ [(NSObject*)notification setValue:@[
+ nsCocoaUtils::ToNSString(disableButtonTitle),
+ nsCocoaUtils::ToNSString(settingsButtonTitle)
+ ]
+ forKey:@"_alternateActionButtonTitles"];
+ }
+ }
+ nsAutoString name;
+ rv = aAlert->GetName(name);
+ // Don't let an alert name be more than MAX_NOTIFICATION_NAME_LEN characters.
+ // More than that shouldn't be necessary and userInfo (assigned to below) has
+ // a length limit of 16k on OS X 10.11. Exception thrown if limit exceeded.
+ if (name.Length() > MAX_NOTIFICATION_NAME_LEN) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ NSString *alertName = nsCocoaUtils::ToNSString(name);
+ if (!alertName) {
+ return NS_ERROR_FAILURE;
+ }
+ notification.userInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil]
+ forKeys:[NSArray arrayWithObjects:@"name", nil]];
+
+ nsAutoString cookie;
+ rv = aAlert->GetCookie(cookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, cookie);
+
+ // Show the favicon if supported on this version of OS X.
+ if (aIconSize > 0 &&
+ [notification respondsToSelector:@selector(set_identityImage:)] &&
+ [notification respondsToSelector:@selector(set_identityImageHasBorder:)]) {
+
+ NSData *iconData = [NSData dataWithBytes:aIconData length:aIconSize];
+ NSImage *icon = [[[NSImage alloc] initWithData:iconData] autorelease];
+
+ [(NSObject*)notification setValue:icon forKey:@"_identityImage"];
+ [(NSObject*)notification setValue:@(NO) forKey:@"_identityImageHasBorder"];
+ }
+
+ bool inPrivateBrowsing;
+ rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Show the notification without waiting for an image if there is no icon URL or
+ // notification icons are not supported on this version of OS X.
+ if (![unClass instancesRespondToSelector:@selector(setContentImage:)]) {
+ CloseAlertCocoaString(alertName);
+ mActiveAlerts.AppendElement(osxni);
+ [GetNotificationCenter() deliverNotification:notification];
+ [notification release];
+ if (aAlertListener) {
+ aAlertListener->Observe(nullptr, "alertshow", cookie.get());
+ }
+ } else {
+ mPendingAlerts.AppendElement(osxni);
+ osxni->mPendingNotifiction = notification;
+ // Wait six seconds for the image to load.
+ rv = aAlert->LoadImage(6000, this, osxni,
+ getter_AddRefs(osxni->mIconRequest));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ShowPendingNotification(osxni);
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::CloseAlert(const nsAString& aAlertName,
+ nsIPrincipal* aPrincipal)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString *alertName = nsCocoaUtils::ToNSString(aAlertName);
+ CloseAlertCocoaString(alertName);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+OSXNotificationCenter::CloseAlertCocoaString(NSString *aAlertName)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aAlertName) {
+ return; // Can't do anything without a name
+ }
+
+ NSArray *notifications = [GetNotificationCenter() deliveredNotifications];
+ for (id<FakeNSUserNotification> notification in notifications) {
+ NSString *name = [[notification userInfo] valueForKey:@"name"];
+ if ([name isEqualToString:aAlertName]) {
+ [GetNotificationCenter() removeDeliveredNotification:notification];
+ [GetNotificationCenter() _removeDisplayedNotification:notification];
+ break;
+ }
+ }
+
+ for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
+ OSXNotificationInfo *osxni = mActiveAlerts[i];
+ if ([aAlertName isEqualToString:osxni->mName]) {
+ if (osxni->mObserver) {
+ osxni->mObserver->Observe(nullptr, "alertfinished", osxni->mCookie.get());
+ }
+ if (osxni->mIconRequest) {
+ osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
+ osxni->mIconRequest = nullptr;
+ }
+ mActiveAlerts.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+OSXNotificationCenter::OnActivate(NSString *aAlertName,
+ NSUserNotificationActivationType aActivationType,
+ unsigned long long aAdditionalActionIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aAlertName) {
+ return; // Can't do anything without a name
+ }
+
+ for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
+ OSXNotificationInfo *osxni = mActiveAlerts[i];
+ if ([aAlertName isEqualToString:osxni->mName]) {
+ if (osxni->mObserver) {
+ switch ((int)aActivationType) {
+ case NSUserNotificationActivationTypeAdditionalActionClicked:
+ case NSUserNotificationActivationTypeActionButtonClicked:
+ switch (aAdditionalActionIndex) {
+ case OSXNotificationActionDisable:
+ osxni->mObserver->Observe(nullptr, "alertdisablecallback", osxni->mCookie.get());
+ break;
+ case OSXNotificationActionSettings:
+ osxni->mObserver->Observe(nullptr, "alertsettingscallback", osxni->mCookie.get());
+ break;
+ default:
+ NS_WARNING("Unknown NSUserNotification additional action clicked");
+ break;
+ }
+ break;
+ default:
+ osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get());
+ break;
+ }
+ }
+ return;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+OSXNotificationCenter::ShowPendingNotification(OSXNotificationInfo *osxni)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (osxni->mIconRequest) {
+ osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
+ osxni->mIconRequest = nullptr;
+ }
+
+ CloseAlertCocoaString(osxni->mName);
+
+ for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
+ if (mPendingAlerts[i] == osxni) {
+ mActiveAlerts.AppendElement(osxni);
+ mPendingAlerts.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ [GetNotificationCenter() deliverNotification:osxni->mPendingNotifiction];
+
+ if (osxni->mObserver) {
+ osxni->mObserver->Observe(nullptr, "alertshow", osxni->mCookie.get());
+ }
+
+ [osxni->mPendingNotifiction release];
+ osxni->mPendingNotifiction = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::OnImageMissing(nsISupports* aUserData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ OSXNotificationInfo *osxni = static_cast<OSXNotificationInfo*>(aUserData);
+ if (osxni->mPendingNotifiction) {
+ // If there was an error getting the image, or the request timed out, show
+ // the notification without a content image.
+ ShowPendingNotification(osxni);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::OnImageReady(nsISupports* aUserData,
+ imgIRequest* aRequest)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCOMPtr<imgIContainer> image;
+ nsresult rv = aRequest->GetImage(getter_AddRefs(image));
+ if (NS_WARN_IF(NS_FAILED(rv) || !image)) {
+ return rv;
+ }
+
+ OSXNotificationInfo *osxni = static_cast<OSXNotificationInfo*>(aUserData);
+ if (!osxni->mPendingNotifiction) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSImage *cocoaImage = nil;
+ nsCocoaUtils::CreateNSImageFromImageContainer(image, imgIContainer::FRAME_FIRST, &cocoaImage, 1.0f);
+ (osxni->mPendingNotifiction).contentImage = cocoaImage;
+ [cocoaImage release];
+ ShowPendingNotification(osxni);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/RectTextureImage.h b/widget/cocoa/RectTextureImage.h
new file mode 100644
index 0000000000..022b216c6b
--- /dev/null
+++ b/widget/cocoa/RectTextureImage.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RectTextureImage_h_
+#define RectTextureImage_h_
+
+#include "mozilla/RefPtr.h"
+
+class MacIOSurface;
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+} // namespace gl
+
+namespace widget {
+
+// Manages a texture which can resize dynamically, binds to the
+// LOCAL_GL_TEXTURE_RECTANGLE_ARB texture target and is automatically backed
+// by a power-of-two size GL texture. The latter two features are used for
+// compatibility with older Mac hardware which we block GL layers on.
+// RectTextureImages are used both for accelerated GL layers drawing and for
+// OMTC BasicLayers drawing.
+class RectTextureImage {
+public:
+ RectTextureImage();
+
+ virtual ~RectTextureImage();
+
+ already_AddRefed<gfx::DrawTarget>
+ BeginUpdate(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion =
+ LayoutDeviceIntRegion());
+ void EndUpdate();
+
+ void UpdateIfNeeded(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion,
+ void (^aCallback)(gfx::DrawTarget*,
+ const LayoutDeviceIntRegion&))
+ {
+ RefPtr<gfx::DrawTarget> drawTarget = BeginUpdate(aNewSize, aDirtyRegion);
+ if (drawTarget) {
+ aCallback(drawTarget, GetUpdateRegion());
+ EndUpdate();
+ }
+ }
+
+ void UpdateFromCGContext(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion,
+ CGContextRef aCGContext);
+
+ LayoutDeviceIntRegion GetUpdateRegion() {
+ MOZ_ASSERT(mInUpdate, "update region only valid during update");
+ return mUpdateRegion;
+ }
+
+ void Draw(mozilla::layers::GLManager* aManager,
+ const LayoutDeviceIntPoint& aLocation,
+ const gfx::Matrix4x4& aTransform = gfx::Matrix4x4());
+
+
+protected:
+ void DeleteTexture();
+ void BindIOSurfaceToTexture(gl::GLContext* aGL);
+
+ RefPtr<MacIOSurface> mIOSurface;
+ gl::GLContext* mGLContext;
+ LayoutDeviceIntRegion mUpdateRegion;
+ LayoutDeviceIntSize mBufferSize;
+ GLuint mTexture;
+ bool mInUpdate;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // RectTextureImage_h_
diff --git a/widget/cocoa/RectTextureImage.mm b/widget/cocoa/RectTextureImage.mm
new file mode 100644
index 0000000000..c67af97d0a
--- /dev/null
+++ b/widget/cocoa/RectTextureImage.mm
@@ -0,0 +1,171 @@
+/* -*- Mode: objc; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RectTextureImage.h"
+
+#include "gfxUtils.h"
+#include "GLContextCGL.h"
+#include "mozilla/layers/GLManager.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "OGLShaderProgram.h"
+#include "ScopedGLHelpers.h"
+
+namespace mozilla {
+namespace widget {
+
+RectTextureImage::RectTextureImage()
+ : mGLContext(nullptr)
+ , mTexture(0)
+ , mInUpdate(false)
+{
+}
+
+RectTextureImage::~RectTextureImage()
+{
+ DeleteTexture();
+}
+
+already_AddRefed<gfx::DrawTarget>
+RectTextureImage::BeginUpdate(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion)
+{
+ MOZ_ASSERT(!mInUpdate, "Beginning update during update!");
+ mUpdateRegion = aDirtyRegion;
+ bool needRecreate = false;
+ if (aNewSize != mBufferSize) {
+ mBufferSize = aNewSize;
+ mUpdateRegion =
+ LayoutDeviceIntRect(LayoutDeviceIntPoint(0, 0), aNewSize);
+ needRecreate = true;
+ }
+
+ if (mUpdateRegion.IsEmpty()) {
+ return nullptr;
+ }
+
+ if (!mIOSurface || needRecreate) {
+ DeleteTexture();
+ mIOSurface = MacIOSurface::CreateIOSurface(mBufferSize.width,
+ mBufferSize.height);
+
+ if (!mIOSurface) {
+ return nullptr;
+ }
+ }
+
+ mInUpdate = true;
+
+ mIOSurface->Lock(false);
+ unsigned char* ioData = (unsigned char*)mIOSurface->GetBaseAddress();
+ gfx::IntSize size(mBufferSize.width, mBufferSize.height);
+ int32_t stride = mIOSurface->GetBytesPerRow();
+ gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
+ RefPtr<gfx::DrawTarget> drawTarget =
+ gfx::Factory::CreateDrawTargetForData(gfx::BackendType::SKIA,
+ ioData, size,
+ stride, format);
+ return drawTarget.forget();
+}
+
+void
+RectTextureImage::EndUpdate()
+{
+ MOZ_ASSERT(mInUpdate, "Ending update while not in update");
+ mIOSurface->Unlock(false);
+ mInUpdate = false;
+}
+
+void
+RectTextureImage::UpdateFromCGContext(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion,
+ CGContextRef aCGContext)
+{
+ gfx::IntSize size = gfx::IntSize(CGBitmapContextGetWidth(aCGContext),
+ CGBitmapContextGetHeight(aCGContext));
+ RefPtr<gfx::DrawTarget> dt = BeginUpdate(aNewSize, aDirtyRegion);
+ if (dt) {
+ gfx::Rect rect(0, 0, size.width, size.height);
+ gfxUtils::ClipToRegion(dt, GetUpdateRegion().ToUnknownRegion());
+ RefPtr<gfx::SourceSurface> sourceSurface =
+ dt->CreateSourceSurfaceFromData(static_cast<uint8_t *>(CGBitmapContextGetData(aCGContext)),
+ size,
+ CGBitmapContextGetBytesPerRow(aCGContext),
+ gfx::SurfaceFormat::B8G8R8A8);
+ dt->DrawSurface(sourceSurface, rect, rect,
+ gfx::DrawSurfaceOptions(),
+ gfx::DrawOptions(1.0, gfx::CompositionOp::OP_SOURCE));
+ dt->PopClip();
+ EndUpdate();
+ }
+}
+
+void
+RectTextureImage::Draw(layers::GLManager* aManager,
+ const LayoutDeviceIntPoint& aLocation,
+ const gfx::Matrix4x4& aTransform)
+{
+ gl::GLContext* gl = aManager->gl();
+
+ BindIOSurfaceToTexture(gl);
+
+ layers::ShaderProgramOGL* program =
+ aManager->GetProgram(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ gfx::SurfaceFormat::R8G8B8A8);
+
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0);
+ gl::ScopedBindTexture texture(gl, mTexture, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+
+ aManager->ActivateProgram(program);
+ program->SetProjectionMatrix(aManager->GetProjMatrix());
+ program->SetLayerTransform(gfx::Matrix4x4(aTransform).PostTranslate(aLocation.x, aLocation.y, 0));
+ program->SetTextureTransform(gfx::Matrix4x4());
+ program->SetRenderOffset(nsIntPoint(0, 0));
+ program->SetTexCoordMultiplier(mBufferSize.width, mBufferSize.height);
+ program->SetTextureUnit(0);
+
+ aManager->BindAndDrawQuad(program,
+ gfx::Rect(0.0, 0.0, mBufferSize.width, mBufferSize.height),
+ gfx::Rect(0.0, 0.0, 1.0f, 1.0f));
+}
+
+void
+RectTextureImage::DeleteTexture()
+{
+ if (mTexture) {
+ MOZ_ASSERT(mGLContext);
+ mGLContext->MakeCurrent();
+ mGLContext->fDeleteTextures(1, &mTexture);
+ mTexture = 0;
+ }
+}
+
+void
+RectTextureImage::BindIOSurfaceToTexture(gl::GLContext* aGL)
+{
+ if (!mTexture) {
+ MOZ_ASSERT(aGL);
+ aGL->fGenTextures(1, &mTexture);
+ aGL->fActiveTexture(LOCAL_GL_TEXTURE0);
+ gl::ScopedBindTexture texture(aGL, mTexture, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_LINEAR);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_LINEAR);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+
+ mIOSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(aGL)->GetCGLContext());
+ mGLContext = aGL;
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/SwipeTracker.h b/widget/cocoa/SwipeTracker.h
new file mode 100644
index 0000000000..b5eb2a4812
--- /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 0000000000..9c06ee0c39
--- /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 0000000000..de7c775931
--- /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 0000000000..39965d0133
--- /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 0000000000..7a7ea3af13
--- /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 0000000000..b6176de2bd
--- /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 0000000000..a8efccaff3
--- /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 0000000000..b3605caa21
--- /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 0000000000..7023a17ba0
--- /dev/null
+++ b/widget/cocoa/WidgetTraceEvent.mm
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <Cocoa/Cocoa.h>
+#include "CustomCocoaEvents.h"
+#include <Foundation/NSAutoreleasePool.h>
+#include <mozilla/CondVar.h>
+#include <mozilla/Mutex.h>
+#include "mozilla/WidgetTraceEvent.h"
+
+using mozilla::CondVar;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+
+namespace {
+
+Mutex* sMutex = NULL;
+CondVar* sCondVar = NULL;
+bool sTracerProcessed = false;
+
+} // namespace
+
+namespace mozilla {
+
+bool InitWidgetTracing()
+{
+ sMutex = new Mutex("Event tracer thread mutex");
+ sCondVar = new CondVar(*sMutex, "Event tracer thread condvar");
+ return sMutex && sCondVar;
+}
+
+void CleanUpWidgetTracing()
+{
+ delete sMutex;
+ delete sCondVar;
+ sMutex = NULL;
+ sCondVar = NULL;
+}
+
+// This function is called from the main (UI) thread.
+void SignalTracerThread()
+{
+ if (!sMutex || !sCondVar)
+ return;
+ MutexAutoLock lock(*sMutex);
+ if (!sTracerProcessed) {
+ sTracerProcessed = true;
+ sCondVar->Notify();
+ }
+}
+
+// This function is called from the background tracer thread.
+bool FireAndWaitForTracerEvent()
+{
+ MOZ_ASSERT(sMutex && sCondVar, "Tracing not initialized!");
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ MutexAutoLock lock(*sMutex);
+ if (sTracerProcessed) {
+ // Things are out of sync. This is likely because we're in
+ // the middle of shutting down. Just return false and hope the
+ // tracer thread is quitting anyway.
+ return false;
+ }
+
+ // Post an application-defined event to the main thread's event queue
+ // and wait for it to get processed.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeTrace
+ data1:0
+ data2:0]
+ atStart:NO];
+ while (!sTracerProcessed)
+ sCondVar->Wait();
+ sTracerProcessed = false;
+ [pool release];
+ return true;
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/crashtests/373122-1-inner.html b/widget/cocoa/crashtests/373122-1-inner.html
new file mode 100644
index 0000000000..5c14166b75
--- /dev/null
+++ b/widget/cocoa/crashtests/373122-1-inner.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+
+<script>
+function boom()
+{
+ document.body.style.position = "fixed"
+
+ setTimeout(boom2, 1);
+}
+
+function boom2()
+{
+ lappy = document.getElementById("lappy");
+ lappy.style.display = "none"
+
+ setTimeout(boom3, 200);
+}
+
+function boom3()
+{
+ dump("Reloading\n");
+ location.reload();
+}
+
+</script>
+
+
+</head>
+
+
+<body bgcolor="black" onload="boom()">
+
+ <span style="overflow: scroll; display: -moz-box;"></span>
+
+ <embed id="lappy" src="" width=550 height=400 TYPE="application/x-shockwave-flash" ></embed>
+
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/373122-1.html b/widget/cocoa/crashtests/373122-1.html
new file mode 100644
index 0000000000..a57e5f4249
--- /dev/null
+++ b/widget/cocoa/crashtests/373122-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="373122-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/397209-1.html b/widget/cocoa/crashtests/397209-1.html
new file mode 100644
index 0000000000..554b2dac72
--- /dev/null
+++ b/widget/cocoa/crashtests/397209-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<button style="width: 8205em;"></button>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/403296-1.xhtml b/widget/cocoa/crashtests/403296-1.xhtml
new file mode 100644
index 0000000000..800eaa3558
--- /dev/null
+++ b/widget/cocoa/crashtests/403296-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ class="reftest-wait"
+ style="margin: 12em; padding: 20px 10em; opacity: 0.2; font-size: 11.2px; -moz-appearance: toolbar; white-space: nowrap;"><body
+ style="position: absolute;"
+ onload="setTimeout(function() { document.body.removeChild(document.getElementById('tr')); document.documentElement.removeAttribute('class'); }, 30);">
+
+xxx
+yyy
+
+<tr id="tr">300</tr></body></html>
diff --git a/widget/cocoa/crashtests/419737-1.html b/widget/cocoa/crashtests/419737-1.html
new file mode 100644
index 0000000000..fe6e4532b4
--- /dev/null
+++ b/widget/cocoa/crashtests/419737-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div><span style="-moz-appearance: radio; padding: 15000px;"></span></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/435223-1.html b/widget/cocoa/crashtests/435223-1.html
new file mode 100644
index 0000000000..1bbc27ba01
--- /dev/null
+++ b/widget/cocoa/crashtests/435223-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div style="min-width: -moz-max-content;"><div style="-moz-appearance: button;"><div style="margin: 0 100%;"></div></div></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/444260-1.xul b/widget/cocoa/crashtests/444260-1.xul
new file mode 100644
index 0000000000..f1a84023df
--- /dev/null
+++ b/widget/cocoa/crashtests/444260-1.xul
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<hbox><button width="7788025414616">S</button></hbox>
+</window>
diff --git a/widget/cocoa/crashtests/444864-1.html b/widget/cocoa/crashtests/444864-1.html
new file mode 100644
index 0000000000..f8bac76e6a
--- /dev/null
+++ b/widget/cocoa/crashtests/444864-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="padding: 10px;"><input type="button" value="Go" style="letter-spacing: 331989pt;"></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/449111-1.html b/widget/cocoa/crashtests/449111-1.html
new file mode 100644
index 0000000000..4494591803
--- /dev/null
+++ b/widget/cocoa/crashtests/449111-1.html
@@ -0,0 +1,4 @@
+<html>
+<head></head>
+<body><div style="display: -moz-box; word-spacing: 549755813889px;"><button>T </button></div></body>
+</html>
diff --git a/widget/cocoa/crashtests/460349-1.xhtml b/widget/cocoa/crashtests/460349-1.xhtml
new file mode 100644
index 0000000000..cc9b9700c7
--- /dev/null
+++ b/widget/cocoa/crashtests/460349-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body><div><mstyle xmlns="http://www.w3.org/1998/Math/MathML" style="-moz-appearance: button;"/></div></body>
+</html>
diff --git a/widget/cocoa/crashtests/460387-1.html b/widget/cocoa/crashtests/460387-1.html
new file mode 100644
index 0000000000..cab7e7eb32
--- /dev/null
+++ b/widget/cocoa/crashtests/460387-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><head></head><body><div style="display: table; padding: 625203mm; -moz-appearance: menulist;"></div></body></html>
diff --git a/widget/cocoa/crashtests/464589-1.html b/widget/cocoa/crashtests/464589-1.html
new file mode 100644
index 0000000000..d25d92315d
--- /dev/null
+++ b/widget/cocoa/crashtests/464589-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var o2 = document.createElement("option");
+ document.getElementById("o1").appendChild(o2);
+ o2.style.padding = "131072cm";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<select><option id="o1" style="height: 0cm;"></option></select>
+
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/crashtests.list b/widget/cocoa/crashtests/crashtests.list
new file mode 100644
index 0000000000..b65fe01394
--- /dev/null
+++ b/widget/cocoa/crashtests/crashtests.list
@@ -0,0 +1,11 @@
+skip-if(!cocoaWidget) load 373122-1.html # bug 1300017
+load 397209-1.html
+load 403296-1.xhtml
+load 419737-1.html
+load 435223-1.html
+load 444260-1.xul
+load 444864-1.html
+load 449111-1.html
+load 460349-1.xhtml
+load 460387-1.html
+load 464589-1.html
diff --git a/widget/cocoa/cursors/arrowN.png b/widget/cocoa/cursors/arrowN.png
new file mode 100644
index 0000000000..5ca8ec5ac6
--- /dev/null
+++ b/widget/cocoa/cursors/arrowN.png
Binary files differ
diff --git a/widget/cocoa/cursors/arrowN@2x.png b/widget/cocoa/cursors/arrowN@2x.png
new file mode 100644
index 0000000000..d00e87636c
--- /dev/null
+++ b/widget/cocoa/cursors/arrowN@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/arrowS.png b/widget/cocoa/cursors/arrowS.png
new file mode 100644
index 0000000000..9b2d19e0fd
--- /dev/null
+++ b/widget/cocoa/cursors/arrowS.png
Binary files differ
diff --git a/widget/cocoa/cursors/arrowS@2x.png b/widget/cocoa/cursors/arrowS@2x.png
new file mode 100644
index 0000000000..5d011c1fd1
--- /dev/null
+++ b/widget/cocoa/cursors/arrowS@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/cell.png b/widget/cocoa/cursors/cell.png
new file mode 100644
index 0000000000..5284eaec57
--- /dev/null
+++ b/widget/cocoa/cursors/cell.png
Binary files differ
diff --git a/widget/cocoa/cursors/cell@2x.png b/widget/cocoa/cursors/cell@2x.png
new file mode 100644
index 0000000000..5e6738cff7
--- /dev/null
+++ b/widget/cocoa/cursors/cell@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/colResize.png b/widget/cocoa/cursors/colResize.png
new file mode 100644
index 0000000000..4e3e19e223
--- /dev/null
+++ b/widget/cocoa/cursors/colResize.png
Binary files differ
diff --git a/widget/cocoa/cursors/colResize@2x.png b/widget/cocoa/cursors/colResize@2x.png
new file mode 100644
index 0000000000..6a92cf6806
--- /dev/null
+++ b/widget/cocoa/cursors/colResize@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/help.png b/widget/cocoa/cursors/help.png
new file mode 100644
index 0000000000..5e5416b4e3
--- /dev/null
+++ b/widget/cocoa/cursors/help.png
Binary files differ
diff --git a/widget/cocoa/cursors/help@2x.png b/widget/cocoa/cursors/help@2x.png
new file mode 100644
index 0000000000..0ac53a9733
--- /dev/null
+++ b/widget/cocoa/cursors/help@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/move.png b/widget/cocoa/cursors/move.png
new file mode 100644
index 0000000000..1360f82277
--- /dev/null
+++ b/widget/cocoa/cursors/move.png
Binary files differ
diff --git a/widget/cocoa/cursors/move@2x.png b/widget/cocoa/cursors/move@2x.png
new file mode 100644
index 0000000000..ad146e4863
--- /dev/null
+++ b/widget/cocoa/cursors/move@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/rowResize.png b/widget/cocoa/cursors/rowResize.png
new file mode 100644
index 0000000000..4c16bb8bd6
--- /dev/null
+++ b/widget/cocoa/cursors/rowResize.png
Binary files differ
diff --git a/widget/cocoa/cursors/rowResize@2x.png b/widget/cocoa/cursors/rowResize@2x.png
new file mode 100644
index 0000000000..b48f03ae0d
--- /dev/null
+++ b/widget/cocoa/cursors/rowResize@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNE.png b/widget/cocoa/cursors/sizeNE.png
new file mode 100644
index 0000000000..f62c046575
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNE.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNE@2x.png b/widget/cocoa/cursors/sizeNE@2x.png
new file mode 100644
index 0000000000..98d19e9efa
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNE@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNESW.png b/widget/cocoa/cursors/sizeNESW.png
new file mode 100644
index 0000000000..0a077fa674
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNESW.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNESW@2x.png b/widget/cocoa/cursors/sizeNESW@2x.png
new file mode 100644
index 0000000000..31bca3c901
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNESW@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNS.png b/widget/cocoa/cursors/sizeNS.png
new file mode 100644
index 0000000000..0419be0af7
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNS.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNS@2x.png b/widget/cocoa/cursors/sizeNS@2x.png
new file mode 100644
index 0000000000..e48fd0cb3a
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNS@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNW.png b/widget/cocoa/cursors/sizeNW.png
new file mode 100644
index 0000000000..8f5faee5f7
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNW.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNW@2x.png b/widget/cocoa/cursors/sizeNW@2x.png
new file mode 100644
index 0000000000..3a80e7ce91
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNW@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNWSE.png b/widget/cocoa/cursors/sizeNWSE.png
new file mode 100644
index 0000000000..0574a584c1
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNWSE.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNWSE@2x.png b/widget/cocoa/cursors/sizeNWSE@2x.png
new file mode 100644
index 0000000000..9a0a276c34
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNWSE@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSE.png b/widget/cocoa/cursors/sizeSE.png
new file mode 100644
index 0000000000..6a1948f521
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSE.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSE@2x.png b/widget/cocoa/cursors/sizeSE@2x.png
new file mode 100644
index 0000000000..7d637f4be6
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSE@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSW.png b/widget/cocoa/cursors/sizeSW.png
new file mode 100644
index 0000000000..5dd054dd46
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSW.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSW@2x.png b/widget/cocoa/cursors/sizeSW@2x.png
new file mode 100644
index 0000000000..5ac63c25c6
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSW@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/vtIBeam.png b/widget/cocoa/cursors/vtIBeam.png
new file mode 100644
index 0000000000..ee7528c595
--- /dev/null
+++ b/widget/cocoa/cursors/vtIBeam.png
Binary files differ
diff --git a/widget/cocoa/cursors/vtIBeam@2x.png b/widget/cocoa/cursors/vtIBeam@2x.png
new file mode 100644
index 0000000000..41c47af116
--- /dev/null
+++ b/widget/cocoa/cursors/vtIBeam@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomIn.png b/widget/cocoa/cursors/zoomIn.png
new file mode 100644
index 0000000000..275bf1c69d
--- /dev/null
+++ b/widget/cocoa/cursors/zoomIn.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomIn@2x.png b/widget/cocoa/cursors/zoomIn@2x.png
new file mode 100644
index 0000000000..fdd3f8e71d
--- /dev/null
+++ b/widget/cocoa/cursors/zoomIn@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomOut.png b/widget/cocoa/cursors/zoomOut.png
new file mode 100644
index 0000000000..19d2d89125
--- /dev/null
+++ b/widget/cocoa/cursors/zoomOut.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomOut@2x.png b/widget/cocoa/cursors/zoomOut@2x.png
new file mode 100644
index 0000000000..0ed46ce75e
--- /dev/null
+++ b/widget/cocoa/cursors/zoomOut@2x.png
Binary files differ
diff --git a/widget/cocoa/moz.build b/widget/cocoa/moz.build
new file mode 100644
index 0000000000..7c995d900a
--- /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 0000000000..9e94e3ab4c
--- /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 0000000000..b7836b6391
--- /dev/null
+++ b/widget/cocoa/nsAppShell.h
@@ -0,0 +1,71 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Runs the main native Cocoa run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#ifndef nsAppShell_h_
+#define nsAppShell_h_
+
+#include "nsBaseAppShell.h"
+#include "nsTArray.h"
+
+// GeckoNSApplication
+//
+// Subclass of NSApplication for filtering out certain events.
+@interface GeckoNSApplication : NSApplication
+{
+}
+@end
+
+@class AppShellDelegate;
+
+class nsAppShell : public nsBaseAppShell
+{
+public:
+ NS_IMETHOD ResumeNative(void);
+
+ nsAppShell();
+
+ nsresult Init();
+
+ NS_IMETHOD Run(void);
+ NS_IMETHOD Exit(void);
+ NS_IMETHOD OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait);
+ NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal *aThread,
+ bool aEventWasProcessed);
+
+ // public only to be visible to Objective-C code that must call it
+ void WillTerminate();
+
+protected:
+ virtual ~nsAppShell();
+
+ virtual void ScheduleNativeEventCallback();
+ virtual bool ProcessNextNativeEvent(bool aMayWait);
+
+ static void ProcessGeckoEvents(void* aInfo);
+
+protected:
+ CFMutableArrayRef mAutoreleasePools;
+
+ AppShellDelegate* mDelegate;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mCFRunLoopSource;
+
+ bool mRunningEventLoop;
+ bool mStarted;
+ bool mTerminated;
+ bool mSkippedNativeCallback;
+ bool mRunningCocoaEmbedded;
+
+ int32_t mNativeEventCallbackDepth;
+ // Can be set from different threads, so must be modified atomically
+ int32_t mNativeEventScheduledDepth;
+};
+
+#endif // nsAppShell_h_
diff --git a/widget/cocoa/nsAppShell.mm b/widget/cocoa/nsAppShell.mm
new file mode 100644
index 0000000000..33ce8e742a
--- /dev/null
+++ b/widget/cocoa/nsAppShell.mm
@@ -0,0 +1,907 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Runs the main native Cocoa run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#include "CustomCocoaEvents.h"
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "nsIWindowMediator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsChildView.h"
+#include "nsToolkit.h"
+#include "TextInputHandler.h"
+#include "mozilla/HangMonitor.h"
+#include "GeckoProfiler.h"
+#include "pratom.h"
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+#include "nsSandboxViolationSink.h"
+#endif
+
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+
+using namespace mozilla::widget;
+
+// A wake lock listener that disables screen saver when requested by
+// Gecko. For example when we're playing video in a foreground tab we
+// don't want the screen saver to turn on.
+
+class MacWakeLockListener final : public nsIDOMMozWakeLockListener {
+public:
+ NS_DECL_ISUPPORTS;
+
+private:
+ ~MacWakeLockListener() {}
+
+ IOPMAssertionID mAssertionID = kIOPMNullAssertionID;
+
+ NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) override {
+ if (!aTopic.EqualsASCII("screen")) {
+ return NS_OK;
+ }
+ // Note the wake lock code ensures that we're not sent duplicate
+ // "locked-foreground" notifications when multiple wake locks are held.
+ if (aState.EqualsASCII("locked-foreground")) {
+ // Prevent screen saver.
+ CFStringRef cf_topic =
+ ::CFStringCreateWithCharacters(kCFAllocatorDefault,
+ reinterpret_cast<const UniChar*>
+ (aTopic.Data()),
+ aTopic.Length());
+ IOReturn success =
+ ::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn,
+ cf_topic,
+ &mAssertionID);
+ CFRelease(cf_topic);
+ if (success != kIOReturnSuccess) {
+ NS_WARNING("failed to disable screensaver");
+ }
+ } else {
+ // Re-enable screen saver.
+ NS_WARNING("Releasing screensaver");
+ if (mAssertionID != kIOPMNullAssertionID) {
+ IOReturn result = ::IOPMAssertionRelease(mAssertionID);
+ if (result != kIOReturnSuccess) {
+ NS_WARNING("failed to release screensaver");
+ }
+ }
+ }
+ return NS_OK;
+ }
+}; // MacWakeLockListener
+
+// defined in nsCocoaWindow.mm
+extern int32_t gXULModalLevel;
+
+static bool gAppShellMethodsSwizzled = false;
+
+@implementation GeckoNSApplication
+
+- (void)sendEvent:(NSEvent *)anEvent
+{
+ mozilla::HangMonitor::NotifyActivity();
+ if ([anEvent type] == NSApplicationDefined &&
+ [anEvent subtype] == kEventSubtypeTrace) {
+ mozilla::SignalTracerThread();
+ return;
+ }
+ [super sendEvent:anEvent];
+}
+
+#if defined(MAC_OS_X_VERSION_10_12) && \
+ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 && \
+ __LP64__
+// 10.12 changed `mask` to NSEventMask (unsigned long long) for x86_64 builds.
+- (NSEvent*)nextEventMatchingMask:(NSEventMask)mask
+#else
+- (NSEvent*)nextEventMatchingMask:(NSUInteger)mask
+#endif
+ untilDate:(NSDate*)expiration
+ inMode:(NSString*)mode
+ dequeue:(BOOL)flag
+{
+ if (expiration) {
+ mozilla::HangMonitor::Suspend();
+ }
+ NSEvent* nextEvent = [super nextEventMatchingMask:mask
+ untilDate:expiration inMode:mode dequeue:flag];
+ if (expiration) {
+ mozilla::HangMonitor::NotifyActivity();
+ }
+ return nextEvent;
+}
+
+@end
+
+// AppShellDelegate
+//
+// Cocoa bridge class. An object of this class is registered to receive
+// notifications.
+//
+@interface AppShellDelegate : NSObject
+{
+ @private
+ nsAppShell* mAppShell;
+}
+
+- (id)initWithAppShell:(nsAppShell*)aAppShell;
+- (void)applicationWillTerminate:(NSNotification*)aNotification;
+- (void)beginMenuTracking:(NSNotification*)aNotification;
+@end
+
+// nsAppShell implementation
+
+NS_IMETHODIMP
+nsAppShell::ResumeNative(void)
+{
+ nsresult retval = nsBaseAppShell::ResumeNative();
+ if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
+ mSkippedNativeCallback)
+ {
+ mSkippedNativeCallback = false;
+ ScheduleNativeEventCallback();
+ }
+ return retval;
+}
+
+nsAppShell::nsAppShell()
+: mAutoreleasePools(nullptr)
+, mDelegate(nullptr)
+, mCFRunLoop(NULL)
+, mCFRunLoopSource(NULL)
+, mRunningEventLoop(false)
+, mStarted(false)
+, mTerminated(false)
+, mSkippedNativeCallback(false)
+, mNativeEventCallbackDepth(0)
+, mNativeEventScheduledDepth(0)
+{
+ // A Cocoa event loop is running here if (and only if) we've been embedded
+ // by a Cocoa app.
+ mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
+}
+
+nsAppShell::~nsAppShell()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mCFRunLoop) {
+ if (mCFRunLoopSource) {
+ ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
+ kCFRunLoopCommonModes);
+ ::CFRelease(mCFRunLoopSource);
+ }
+ ::CFRelease(mCFRunLoop);
+ }
+
+ if (mAutoreleasePools) {
+ NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
+ "nsAppShell destroyed without popping all autorelease pools");
+ ::CFRelease(mAutoreleasePools);
+ }
+
+ [mDelegate release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
+mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
+
+static void
+AddScreenWakeLockListener()
+{
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
+ POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sWakeLockListener = new MacWakeLockListener();
+ sPowerManagerService->AddWakeLockListener(sWakeLockListener);
+ } else {
+ NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+static void
+RemoveScreenWakeLockListener()
+{
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
+ POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
+ sPowerManagerService = nullptr;
+ sWakeLockListener = nullptr;
+ }
+}
+
+// An undocumented CoreGraphics framework method, present in the same form
+// since at least OS X 10.5.
+extern "C" CGError CGSSetDebugOptions(int options);
+
+// Init
+//
+// Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
+// interrupt the main native run loop.
+//
+// public
+nsresult
+nsAppShell::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // No event loop is running yet (unless an embedding app that uses
+ // NSApplicationMain() is running).
+ NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
+
+ // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
+ // by |this|. CFArray is used instead of NSArray because NSArray wants to
+ // retain each object you add to it, and you can't retain an
+ // NSAutoreleasePool.
+ mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
+ NS_ENSURE_STATE(mAutoreleasePools);
+
+ // Get the path of the nib file, which lives in the GRE location
+ nsCOMPtr<nsIFile> nibFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
+ nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
+
+ nsAutoCString nibPath;
+ rv = nibFile->GetNativePath(nibPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This call initializes NSApplication unless:
+ // 1) we're using xre -- NSApp's already been initialized by
+ // MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
+ // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
+ // already been initialized and its main run loop is already running.
+ [NSBundle loadNibFile:
+ [NSString stringWithUTF8String:(const char*)nibPath.get()]
+ externalNameTable:
+ [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
+ forKey:@"NSOwner"]
+ withZone:NSDefaultMallocZone()];
+
+ mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
+ NS_ENSURE_STATE(mDelegate);
+
+ // Add a CFRunLoopSource to the main native run loop. The source is
+ // responsible for interrupting the run loop when Gecko events are ready.
+
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ NS_ENSURE_STATE(mCFRunLoop);
+ ::CFRetain(mCFRunLoop);
+
+ CFRunLoopSourceContext context;
+ bzero(&context, sizeof(context));
+ // context.version = 0;
+ context.info = this;
+ context.perform = ProcessGeckoEvents;
+
+ mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
+ NS_ENSURE_STATE(mCFRunLoopSource);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
+
+ rv = nsBaseAppShell::Init();
+
+ if (!gAppShellMethodsSwizzled) {
+ // We should only replace the original terminate: method if we're not
+ // running in a Cocoa embedder. See bug 604901.
+ if (!mRunningCocoaEmbedded) {
+ nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
+ @selector(nsAppShell_NSApplication_terminate:));
+ }
+ gAppShellMethodsSwizzled = true;
+ }
+
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ // Explicitly turn off CGEvent logging. This works around bug 1092855.
+ // If there are already CGEvents in the log, turning off logging also
+ // causes those events to be written to disk. But at this point no
+ // CGEvents have yet been processed. CGEvents are events (usually
+ // input events) pulled from the WindowServer. An option of 0x80000008
+ // turns on CGEvent logging.
+ CGSSetDebugOptions(0x80000007);
+ }
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ if (Preferences::GetBool("security.sandbox.mac.track.violations", false)) {
+ nsSandboxViolationSink::Start();
+ }
+#endif
+
+ [localPool release];
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// ProcessGeckoEvents
+//
+// The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
+// signalled from ScheduleNativeEventCallback.
+//
+// Arrange for Gecko events to be processed on demand (in response to a call
+// to ScheduleNativeEventCallback(), if processing of Gecko events via "native
+// methods" hasn't been suspended). This happens in NativeEventCallback().
+//
+// protected static
+void
+nsAppShell::ProcessGeckoEvents(void* aInfo)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ PROFILER_LABEL("Events", "ProcessGeckoEvents",
+ js::ProfileEntry::Category::EVENTS);
+
+ nsAppShell* self = static_cast<nsAppShell*> (aInfo);
+
+ if (self->mRunningEventLoop) {
+ self->mRunningEventLoop = false;
+
+ // The run loop may be sleeping -- [NSRunLoop runMode:...]
+ // won't return until it's given a reason to wake up. Awaken it by
+ // posting a bogus event. There's no need to make the event
+ // presentable.
+ //
+ // But _don't_ set windowNumber to '-1' -- that can lead to nasty
+ // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
+ // these fake events, because the -1 has gotten changed into the number
+ // of an actual NSWindow object, and that NSWindow object has just been
+ // destroyed). Setting windowNumber to '0' seems to work fine -- this
+ // seems to prevent the OS from ever trying to associate our bogus event
+ // with a particular NSWindow object.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeNone
+ data1:0
+ data2:0]
+ atStart:NO];
+ }
+
+ if (self->mSuspendNativeCount <= 0) {
+ ++self->mNativeEventCallbackDepth;
+ self->NativeEventCallback();
+ --self->mNativeEventCallbackDepth;
+ } else {
+ self->mSkippedNativeCallback = true;
+ }
+
+ // Still needed to avoid crashes on quit in most Mochitests.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeNone
+ data1:0
+ data2:0]
+ atStart:NO];
+
+ // Normally every call to ScheduleNativeEventCallback() results in
+ // exactly one call to ProcessGeckoEvents(). So each Release() here
+ // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
+ // But if Exit() is called just after ScheduleNativeEventCallback(), the
+ // corresponding call to ProcessGeckoEvents() will never happen. We check
+ // for this possibility in two different places -- here and in Exit()
+ // itself. If we find here that Exit() has been called (that mTerminated
+ // is true), it's because we've been called recursively, that Exit() was
+ // called from self->NativeEventCallback() above, and that we're unwinding
+ // the recursion. In this case we'll never be called again, and we balance
+ // here any extra calls to ScheduleNativeEventCallback().
+ //
+ // When ProcessGeckoEvents() is called recursively, it's because of a
+ // call to ScheduleNativeEventCallback() from NativeEventCallback(). We
+ // balance the "extra" AddRefs here (rather than always in Exit()) in order
+ // to ensure that 'self' stays alive until the end of this method. We also
+ // make sure not to finish the balancing until all the recursion has been
+ // unwound.
+ if (self->mTerminated) {
+ int32_t releaseCount = 0;
+ if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
+ releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
+ self->mNativeEventCallbackDepth);
+ }
+ while (releaseCount-- > self->mNativeEventCallbackDepth)
+ self->Release();
+ } else {
+ // As best we can tell, every call to ProcessGeckoEvents() is triggered
+ // by a call to ScheduleNativeEventCallback(). But we've seen a few
+ // (non-reproducible) cases of double-frees that *might* have been caused
+ // by spontaneous calls (from the OS) to ProcessGeckoEvents(). So we
+ // deal with that possibility here.
+ if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
+ PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
+ NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
+ } else {
+ self->Release();
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// WillTerminate
+//
+// Called by the AppShellDelegate when an NSApplicationWillTerminate
+// notification is posted. After this method is called, native events should
+// no longer be processed. The NSApplicationWillTerminate notification is
+// only posted when [NSApp terminate:] is called, which doesn't happen on a
+// "normal" application quit.
+//
+// public
+void
+nsAppShell::WillTerminate()
+{
+ if (mTerminated)
+ return;
+
+ // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
+ // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+
+ mTerminated = true;
+}
+
+// ScheduleNativeEventCallback
+//
+// Called (possibly on a non-main thread) when Gecko has an event that
+// needs to be processed. The Gecko event needs to be processed on the
+// main thread, so the native run loop must be interrupted.
+//
+// In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
+// ensure that ScheduleNativeEventCallback() is called no more than once
+// per call to NativeEventCallback(). ProcessGeckoEvents() can skip its
+// call to NativeEventCallback() if processing of Gecko events by native
+// means is suspended (using nsIAppShell::SuspendNative()), which will
+// suspend calls from nsBaseAppShell::OnDispatchedEvent() to
+// ScheduleNativeEventCallback(). But when Gecko event processing by
+// native means is resumed (in ResumeNative()), an extra call is made to
+// ScheduleNativeEventCallback() (from ResumeNative()). This triggers
+// another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
+// and nsBaseAppShell::OnDispatchedEvent() resumes calling
+// ScheduleNativeEventCallback().
+//
+// protected virtual
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTerminated)
+ return;
+
+ // Each AddRef() here is normally balanced by exactly one Release() in
+ // ProcessGeckoEvents(). But there are exceptions, for which see
+ // ProcessGeckoEvents() and Exit().
+ NS_ADDREF_THIS();
+ PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
+
+ // This will invoke ProcessGeckoEvents on the main thread.
+ ::CFRunLoopSourceSignal(mCFRunLoopSource);
+ ::CFRunLoopWakeUp(mCFRunLoop);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Undocumented Cocoa Event Manager function, present in the same form since
+// at least OS X 10.6.
+extern "C" EventAttributes GetEventAttributes(EventRef inEvent);
+
+// ProcessNextNativeEvent
+//
+// If aMayWait is false, process a single native event. If it is true, run
+// the native run loop until stopped by ProcessGeckoEvents.
+//
+// Returns true if more events are waiting in the native event queue.
+//
+// protected virtual
+bool
+nsAppShell::ProcessNextNativeEvent(bool aMayWait)
+{
+ bool moreEvents = false;
+
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ bool eventProcessed = false;
+ NSString* currentMode = nil;
+
+ if (mTerminated)
+ return false;
+
+ bool wasRunningEventLoop = mRunningEventLoop;
+ mRunningEventLoop = aMayWait;
+ NSDate* waitUntil = nil;
+ if (aMayWait)
+ waitUntil = [NSDate distantFuture];
+
+ NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
+
+ EventQueueRef currentEventQueue = GetCurrentEventQueue();
+ EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
+
+ if (aMayWait) {
+ mozilla::HangMonitor::Suspend();
+ }
+
+ // Only call -[NSApp sendEvent:] (and indirectly send user-input events to
+ // Gecko) if aMayWait is true. Tbis ensures most calls to -[NSApp
+ // sendEvent:] happen under nsAppShell::Run(), at the lowest level of
+ // recursion -- thereby making it less likely Gecko will process user-input
+ // events in the wrong order or skip some of them. It also avoids eating
+ // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls
+ // us) -- thereby avoiding the starvation of nsIRunnable events in
+ // nsThread::ProcessNextEvent(). For more information see bug 996848.
+ do {
+ // No autorelease pool is provided here, because OnProcessNextEvent
+ // and AfterProcessNextEvent are responsible for maintaining it.
+ NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
+ "No autorelease pool for native event");
+
+ if (aMayWait) {
+ currentMode = [currentRunLoop currentMode];
+ if (!currentMode)
+ currentMode = NSDefaultRunLoopMode;
+ NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:waitUntil
+ inMode:currentMode
+ dequeue:YES];
+ if (nextEvent) {
+ mozilla::HangMonitor::NotifyActivity();
+ [NSApp sendEvent:nextEvent];
+ eventProcessed = true;
+ }
+ } else {
+ // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
+ // loop, though it does queue up any newly available events from the
+ // window server.
+ EventRef currentEvent = AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
+ kEventQueueOptionsNone);
+ if (!currentEvent) {
+ continue;
+ }
+ EventAttributes attrs = GetEventAttributes(currentEvent);
+ UInt32 eventKind = GetEventKind(currentEvent);
+ UInt32 eventClass = GetEventClass(currentEvent);
+ bool osCocoaEvent =
+ ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) ||
+ ((eventClass == 'cgs ') && (eventKind != NSApplicationDefined)));
+ // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
+ // (i.e. a user input event), we shouldn't process it here while
+ // aMayWait is false. Likewise if currentEvent will eventually be
+ // turned into an OS-defined Cocoa event, or otherwise needs AppKit
+ // processing. Doing otherwise risks doing too much work here, and
+ // preventing the event from being properly processed by the AppKit
+ // framework.
+ if ((attrs != kEventAttributeNone) || osCocoaEvent) {
+ // Since we can't process the next event here (while aMayWait is false),
+ // we want moreEvents to be false on return.
+ eventProcessed = false;
+ // This call to ReleaseEvent() matches a call to RetainEvent() in
+ // AcquireFirstMatchingEventInQueue() above.
+ ReleaseEvent(currentEvent);
+ break;
+ }
+ // This call to RetainEvent() matches a call to ReleaseEvent() in
+ // RemoveEventFromQueue() below.
+ RetainEvent(currentEvent);
+ RemoveEventFromQueue(currentEventQueue, currentEvent);
+ SendEventToEventTarget(currentEvent, eventDispatcherTarget);
+ // This call to ReleaseEvent() matches a call to RetainEvent() in
+ // AcquireFirstMatchingEventInQueue() above.
+ ReleaseEvent(currentEvent);
+ eventProcessed = true;
+ }
+ } while (mRunningEventLoop);
+
+ if (eventProcessed) {
+ moreEvents =
+ (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
+ kEventQueueOptionsNone) != NULL);
+ }
+
+ mRunningEventLoop = wasRunningEventLoop;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+
+ if (!moreEvents) {
+ nsChildView::UpdateCurrentInputEventCount();
+ }
+
+ return moreEvents;
+}
+
+// Run
+//
+// Overrides the base class's Run() method to call [NSApp run] (which spins
+// the native run loop until the application quits). Since (unlike the base
+// class's Run() method) we don't process any Gecko events here, they need
+// to be processed elsewhere (in NativeEventCallback(), called from
+// ProcessGeckoEvents()).
+//
+// Camino called [NSApp run] on its own (via NSApplicationMain()), and so
+// didn't call nsAppShell::Run().
+//
+// public
+NS_IMETHODIMP
+nsAppShell::Run(void)
+{
+ NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
+ if (mStarted || mTerminated)
+ return NS_OK;
+
+ mStarted = true;
+
+ AddScreenWakeLockListener();
+
+ NS_OBJC_TRY_ABORT([NSApp run]);
+
+ RemoveScreenWakeLockListener();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // This method is currently called more than once -- from (according to
+ // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
+ // XPCOM shutdown notification that nsBaseAppShell has registered to
+ // receive. So we need to ensure that multiple calls won't break anything.
+ // But we should also complain about it (since it isn't quite kosher).
+ if (mTerminated) {
+ NS_WARNING("nsAppShell::Exit() called redundantly");
+ return NS_OK;
+ }
+
+ mTerminated = true;
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ nsSandboxViolationSink::Stop();
+#endif
+
+ // Quoting from Apple's doc on the [NSApplication stop:] method (from their
+ // doc on the NSApplication class): "If this method is invoked during a
+ // modal event loop, it will break that loop but not the main event loop."
+ // nsAppShell::Exit() shouldn't be called from a modal event loop. So if
+ // it is we complain about it (to users of debug builds) and call [NSApp
+ // stop:] one extra time. (I'm not sure if modal event loops can be nested
+ // -- Apple's docs don't say one way or the other. But the return value
+ // of [NSApp _isRunningModal] doesn't change immediately after a call to
+ // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
+ // will do the job.)
+ BOOL cocoaModal = [NSApp _isRunningModal];
+ NS_ASSERTION(!cocoaModal,
+ "Don't call nsAppShell::Exit() from a modal event loop!");
+ if (cocoaModal)
+ [NSApp stop:nullptr];
+ [NSApp stop:nullptr];
+
+ // A call to Exit() just after a call to ScheduleNativeEventCallback()
+ // prevents the (normally) matching call to ProcessGeckoEvents() from
+ // happening. If we've been called from ProcessGeckoEvents() (as usually
+ // happens), we take care of it there. But if we have an unbalanced call
+ // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
+ // stack, we need to take care of the problem here.
+ if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
+ int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
+ while (releaseCount-- > 0)
+ NS_RELEASE_THIS();
+ }
+
+ return nsBaseAppShell::Exit();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// OnProcessNextEvent
+//
+// This nsIThreadObserver method is called prior to processing an event.
+// Set up an autorelease pool that will service any autoreleased Cocoa
+// objects during this event. This includes native events processed by
+// ProcessNextNativeEvent. The autorelease pool will be popped by
+// AfterProcessNextEvent, it is important for these two methods to be
+// tightly coupled.
+//
+// public
+NS_IMETHODIMP
+nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ASSERTION(mAutoreleasePools,
+ "No stack on which to store autorelease pool");
+
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ ::CFArrayAppendValue(mAutoreleasePools, pool);
+
+ return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// AfterProcessNextEvent
+//
+// This nsIThreadObserver method is called after event processing is complete.
+// The Cocoa implementation cleans up the autorelease pool create by the
+// previous OnProcessNextEvent call.
+//
+// public
+NS_IMETHODIMP
+nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
+ bool aEventWasProcessed)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
+
+ NS_ASSERTION(mAutoreleasePools && count,
+ "Processed an event, but there's no autorelease pool?");
+
+ const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>
+ (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
+ ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
+ [pool release];
+
+ return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+
+// AppShellDelegate implementation
+
+
+@implementation AppShellDelegate
+// initWithAppShell:
+//
+// Constructs the AppShellDelegate object
+- (id)initWithAppShell:(nsAppShell*)aAppShell
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [self init])) {
+ mAppShell = aAppShell;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationWillTerminate:)
+ name:NSApplicationWillTerminateNotification
+ object:NSApp];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationDidBecomeActive:)
+ name:NSApplicationDidBecomeActiveNotification
+ object:NSApp];
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(beginMenuTracking:)
+ name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
+ object:nil];
+ }
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// applicationWillTerminate:
+//
+// Notify the nsAppShell that native event processing should be discontinued.
+- (void)applicationWillTerminate:(NSNotification*)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mAppShell->WillTerminate();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// applicationDidBecomeActive
+//
+// Make sure TextInputHandler::sLastModifierState is updated when we become
+// active (since we won't have received [ChildView flagsChanged:] messages
+// while inactive).
+- (void)applicationDidBecomeActive:(NSNotification*)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
+ // to worry about getting an NSInternalInconsistencyException here.
+ NSEvent* currentEvent = [NSApp currentEvent];
+ if (currentEvent) {
+ TextInputHandler::sLastModifierState =
+ [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// beginMenuTracking
+//
+// Roll up our context menu (if any) when some other app (or the OS) opens
+// any sort of menu. But make sure we don't do this for notifications we
+// send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
+- (void)beginMenuTracking:(NSNotification*)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSString *sender = [aNotification object];
+ if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget)
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// We hook terminate: in order to make OS-initiated termination work nicely
+// with Gecko's shutdown sequence. (Two ways to trigger OS-initiated
+// termination: 1) Quit from the Dock menu; 2) Log out from (or shut down)
+// your computer while the browser is active.)
+@interface NSApplication (MethodSwizzling)
+- (void)nsAppShell_NSApplication_terminate:(id)sender;
+@end
+
+@implementation NSApplication (MethodSwizzling)
+
+// Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
+// has returned NSTerminateNow. This method "subclasses" and replaces the
+// OS's original implementation. The only thing the orginal method does which
+// we need is that it posts NSApplicationWillTerminateNotification. Everything
+// else is unneeded (because it's handled elsewhere), or actively interferes
+// with Gecko's shutdown sequence. For example the original terminate: method
+// causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
+// above), which means that nothing runs after the call to nsAppStartup::Run()
+// in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
+// and NS_ShutdownXPCOM() never get called.
+- (void)nsAppShell_NSApplication_terminate:(id)sender
+{
+ [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification
+ object:NSApp];
+}
+
+@end
diff --git a/widget/cocoa/nsBidiKeyboard.h b/widget/cocoa/nsBidiKeyboard.h
new file mode 100644
index 0000000000..e7e7ac8722
--- /dev/null
+++ b/widget/cocoa/nsBidiKeyboard.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBidiKeyboard_h_
+#define nsBidiKeyboard_h_
+
+#include "nsIBidiKeyboard.h"
+
+class nsBidiKeyboard : public nsIBidiKeyboard
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBIDIKEYBOARD
+
+ nsBidiKeyboard();
+
+protected:
+ virtual ~nsBidiKeyboard();
+};
+
+#endif // nsBidiKeyboard_h_
diff --git a/widget/cocoa/nsBidiKeyboard.mm b/widget/cocoa/nsBidiKeyboard.mm
new file mode 100644
index 0000000000..e0fc86aeb7
--- /dev/null
+++ b/widget/cocoa/nsBidiKeyboard.mm
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBidiKeyboard.h"
+#include "nsCocoaUtils.h"
+#include "TextInputHandler.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard)
+
+nsBidiKeyboard::nsBidiKeyboard() : nsIBidiKeyboard()
+{
+ Reset();
+}
+
+nsBidiKeyboard::~nsBidiKeyboard()
+{
+}
+
+NS_IMETHODIMP nsBidiKeyboard::Reset()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(bool *aIsRTL)
+{
+ *aIsRTL = TISInputSourceWrapper::CurrentInputSource().IsForRTLLanguage();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult)
+{
+ // not implemented yet
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/widget/cocoa/nsChangeObserver.h b/widget/cocoa/nsChangeObserver.h
new file mode 100644
index 0000000000..1b9a001735
--- /dev/null
+++ b/widget/cocoa/nsChangeObserver.h
@@ -0,0 +1,44 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsChangeObserver_h_
+#define nsChangeObserver_h_
+
+class nsIContent;
+class nsIDocument;
+class nsIAtom;
+
+#define NS_DECL_CHANGEOBSERVER \
+void ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute) override; \
+void ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, int32_t aIndexInContainer) override; \
+void ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer, nsIContent *aChild) override;
+
+// Something that wants to be alerted to changes in attributes or changes in
+// its corresponding content object.
+//
+// This interface is used by our menu code so we only have to have one
+// nsIDocumentObserver.
+//
+// Any class that implements this interface must take care to unregister itself
+// on deletion.
+class nsChangeObserver
+{
+public:
+ // XXX use dom::Element
+ virtual void ObserveAttributeChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ nsIAtom* aAttribute)=0;
+
+ virtual void ObserveContentRemoved(nsIDocument* aDocument,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)=0;
+
+ virtual void ObserveContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild)=0;
+};
+
+#endif // nsChangeObserver_h_
diff --git a/widget/cocoa/nsChildView.h b/widget/cocoa/nsChildView.h
new file mode 100644
index 0000000000..f6fb44633c
--- /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 0000000000..92ccd8b6c1
--- /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 0000000000..45871efe10
--- /dev/null
+++ b/widget/cocoa/nsClipboard.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsClipboard_h_
+#define nsClipboard_h_
+
+#include "nsIClipboard.h"
+#include "nsXPIDLString.h"
+#include "mozilla/StaticPtr.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsITransferable;
+
+class nsClipboard : public nsIClipboard
+{
+
+public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ // On macOS, cache the transferable of the current selection (chrome/content)
+ // in the parent process. This is needed for the services menu which
+ // requires synchronous access to the current selection.
+ static mozilla::StaticRefPtr<nsITransferable> sSelectionCache;
+
+ // Helper methods, used also by nsDragService
+ static NSDictionary* PasteboardDictFromTransferable(nsITransferable *aTransferable);
+ static bool IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType);
+ static NSString* WrapHtmlForSystemPasteboard(NSString* aString);
+ static nsresult TransferableFromPasteboard(nsITransferable *aTransferable, NSPasteboard *pboard);
+
+protected:
+
+ // impelement the native clipboard behavior
+ NS_IMETHOD SetNativeClipboardData(int32_t aWhichClipboard);
+ NS_IMETHOD GetNativeClipboardData(nsITransferable * aTransferable, int32_t aWhichClipboard);
+ void ClearSelectionCache();
+ void SetSelectionCache(nsITransferable* aTransferable);
+
+private:
+ virtual ~nsClipboard();
+ int32_t mCachedClipboard;
+ int32_t mChangeCount; // Set to the native change count after any modification of the clipboard.
+
+ bool mIgnoreEmptyNotification;
+ nsCOMPtr<nsIClipboardOwner> mClipboardOwner;
+ nsCOMPtr<nsITransferable> mTransferable;
+};
+
+#endif // nsClipboard_h_
diff --git a/widget/cocoa/nsClipboard.mm b/widget/cocoa/nsClipboard.mm
new file mode 100644
index 0000000000..4146f17851
--- /dev/null
+++ b/widget/cocoa/nsClipboard.mm
@@ -0,0 +1,775 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include "mozilla/Unused.h"
+
+#include "gfxPlatform.h"
+#include "nsArrayUtils.h"
+#include "nsCOMPtr.h"
+#include "nsClipboard.h"
+#include "nsString.h"
+#include "nsISupportsPrimitives.h"
+#include "nsXPIDLString.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsMemory.h"
+#include "nsIFile.h"
+#include "nsStringStream.h"
+#include "nsDragService.h"
+#include "nsEscape.h"
+#include "nsPrintfCString.h"
+#include "nsObjCExceptions.h"
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::SourceSurface;
+using mozilla::LogLevel;
+
+// Screenshots use the (undocumented) png pasteboard type.
+#define IMAGE_PASTEBOARD_TYPES NSTIFFPboardType, @"Apple PNG pasteboard type", nil
+
+extern PRLogModuleInfo* sCocoaLog;
+
+extern void EnsureLogInitialized();
+
+mozilla::StaticRefPtr<nsITransferable> nsClipboard::sSelectionCache;
+
+nsClipboard::nsClipboard()
+ : mCachedClipboard(-1)
+ , mChangeCount(0)
+ , mIgnoreEmptyNotification(false)
+{
+ EnsureLogInitialized();
+}
+
+nsClipboard::~nsClipboard()
+{
+ EmptyClipboard(kGlobalClipboard);
+ EmptyClipboard(kFindClipboard);
+ ClearSelectionCache();
+}
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
+
+// We separate this into its own function because after an @try, all local
+// variables within that function get marked as volatile, and our C++ type
+// system doesn't like volatile things.
+static NSData*
+GetDataFromPasteboard(NSPasteboard* aPasteboard, NSString* aType)
+{
+ NSData *data = nil;
+ @try {
+ data = [aPasteboard dataForType:aType];
+ } @catch (NSException* e) {
+ NS_WARNING(nsPrintfCString("Exception raised while getting data from the pasteboard: \"%s - %s\"",
+ [[e name] UTF8String], [[e reason] UTF8String]).get());
+ mozilla::Unused << e;
+ }
+ return data;
+}
+
+void
+nsClipboard::SetSelectionCache(nsITransferable *aTransferable)
+{
+ sSelectionCache = aTransferable;
+}
+
+void
+nsClipboard::ClearSelectionCache()
+{
+ sSelectionCache = nullptr;
+}
+
+NS_IMETHODIMP
+nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !mTransferable)
+ return NS_ERROR_FAILURE;
+
+ mIgnoreEmptyNotification = true;
+
+ NSDictionary* pasteboardOutputDict = PasteboardDictFromTransferable(mTransferable);
+ if (!pasteboardOutputDict)
+ return NS_ERROR_FAILURE;
+
+ unsigned int outputCount = [pasteboardOutputDict count];
+ NSArray* outputKeys = [pasteboardOutputDict allKeys];
+ NSPasteboard* cocoaPasteboard;
+ if (aWhichClipboard == kFindClipboard) {
+ cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
+ [cocoaPasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
+ } else {
+ // Write everything else out to the general pasteboard.
+ cocoaPasteboard = [NSPasteboard generalPasteboard];
+ [cocoaPasteboard declareTypes:outputKeys owner:nil];
+ }
+
+ for (unsigned int i = 0; i < outputCount; i++) {
+ NSString* currentKey = [outputKeys objectAtIndex:i];
+ id currentValue = [pasteboardOutputDict valueForKey:currentKey];
+ if (aWhichClipboard == kFindClipboard) {
+ if (currentKey == NSStringPboardType)
+ [cocoaPasteboard setString:currentValue forType:currentKey];
+ } else {
+ if (currentKey == NSStringPboardType ||
+ currentKey == kCorePboardType_url ||
+ currentKey == kCorePboardType_urld ||
+ currentKey == kCorePboardType_urln) {
+ [cocoaPasteboard setString:currentValue forType:currentKey];
+ } else if (currentKey == NSHTMLPboardType) {
+ [cocoaPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
+ forType:currentKey];
+ } else {
+ [cocoaPasteboard setData:currentValue forType:currentKey];
+ }
+ }
+ }
+
+ mCachedClipboard = aWhichClipboard;
+ mChangeCount = [cocoaPasteboard changeCount];
+
+ mIgnoreEmptyNotification = false;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsClipboard::TransferableFromPasteboard(nsITransferable *aTransferable, NSPasteboard *cocoaPasteboard)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+
+ for (uint32_t i = 0; i < flavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr)); // i has a flavr
+
+ // printf("looking for clipboard data of type %s\n", flavorStr.get());
+
+ NSString *pboardType = nil;
+ if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
+ NSString* pString = [cocoaPasteboard stringForType:pboardType];
+ if (!pString)
+ continue;
+
+ NSData* stringData;
+ if ([pboardType isEqualToString:NSRTFPboardType]) {
+ stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
+ } else {
+ stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
+ }
+ unsigned int dataLength = [stringData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ [stringData getBytes:clipboardDataPtr];
+
+ // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
+ int32_t signedDataLength = dataLength;
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
+ dataLength = signedDataLength;
+
+ // skip BOM (Byte Order Mark to distinguish little or big endian)
+ char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
+ if ((dataLength > 2) &&
+ ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
+ (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
+ dataLength -= sizeof(char16_t);
+ clipboardDataPtrNoBOM += 1;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
+ free(clipboardDataPtr);
+ break;
+ }
+ else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ NSString* type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]];
+ if (!type) {
+ continue;
+ }
+
+ NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
+ if (!pasteboardData) {
+ continue;
+ }
+
+ unsigned int dataLength = [pasteboardData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ [pasteboardData getBytes:clipboardDataPtr];
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
+ getter_AddRefs(genericDataWrapper));
+
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
+ free(clipboardDataPtr);
+ }
+ else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ // Figure out if there's data on the pasteboard we can grab (sanity check)
+ NSString *type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
+ if (!type)
+ continue;
+
+ // Read data off the clipboard
+ NSData *pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
+ if (!pasteboardData)
+ continue;
+
+ // Figure out what type we're converting to
+ CFStringRef outputType = NULL;
+ if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime))
+ outputType = CFSTR("public.jpeg");
+ else if (flavorStr.EqualsLiteral(kPNGImageMime))
+ outputType = CFSTR("public.png");
+ else if (flavorStr.EqualsLiteral(kGIFImageMime))
+ outputType = CFSTR("com.compuserve.gif");
+ else
+ continue;
+
+ // Use ImageIO to interpret the data on the clipboard and transcode.
+ // Note that ImageIO, like all CF APIs, allows NULLs to propagate freely
+ // and safely in most cases (like ObjC). A notable exception is CFRelease.
+ NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
+ (NSNumber*)kCFBooleanTrue, kCGImageSourceShouldAllowFloat,
+ (type == NSTIFFPboardType ? @"public.tiff" : @"public.png"),
+ kCGImageSourceTypeIdentifierHint, nil];
+
+ CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)pasteboardData,
+ (CFDictionaryRef)options);
+ NSMutableData *encodedData = [NSMutableData data];
+ CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)encodedData,
+ outputType,
+ 1, NULL);
+ CGImageDestinationAddImageFromSource(dest, source, 0, NULL);
+ bool successfullyConverted = CGImageDestinationFinalize(dest);
+
+ if (successfullyConverted) {
+ // Put the converted data in a form Gecko can understand
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream), (const char*)[encodedData bytes],
+ [encodedData length], NS_ASSIGNMENT_COPY);
+
+ aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
+ }
+
+ if (dest)
+ CFRelease(dest);
+ if (source)
+ CFRelease(source);
+
+ if (successfullyConverted)
+ break;
+ else
+ continue;
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhichClipboard)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !aTransferable)
+ return NS_ERROR_FAILURE;
+
+ NSPasteboard* cocoaPasteboard;
+ if (aWhichClipboard == kFindClipboard) {
+ cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
+ } else {
+ cocoaPasteboard = [NSPasteboard generalPasteboard];
+ }
+ if (!cocoaPasteboard)
+ return NS_ERROR_FAILURE;
+
+ // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+
+ // If we were the last ones to put something on the pasteboard, then just use the cached
+ // transferable. Otherwise clear it because it isn't relevant any more.
+ if (mCachedClipboard == aWhichClipboard &&
+ mChangeCount == [cocoaPasteboard changeCount]) {
+ if (mTransferable) {
+ for (uint32_t i = 0; i < flavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ nsCOMPtr<nsISupports> dataSupports;
+ uint32_t dataSize = 0;
+ rv = mTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
+ if (NS_SUCCEEDED(rv)) {
+ aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
+ return NS_OK; // maybe try to fill in more types? Is there a point?
+ }
+ }
+ }
+ } else {
+ EmptyClipboard(aWhichClipboard);
+ }
+
+ // at this point we can't satisfy the request from cache data so let's look
+ // for things other people put on the system clipboard
+
+ return nsClipboard::TransferableFromPasteboard(aTransferable, cocoaPasteboard);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// returns true if we have *any* of the passed in flavors available for pasting
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
+ int32_t aWhichClipboard, bool* outResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *outResult = false;
+
+ if ((aWhichClipboard != kGlobalClipboard) || !aFlavorList)
+ return NS_OK;
+
+ // first see if we have data for this in our cached transferable
+ if (mTransferable) {
+ nsCOMPtr<nsIArray> transferableFlavorList;
+ nsresult rv = mTransferable->FlavorsTransferableCanImport(getter_AddRefs(transferableFlavorList));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t transferableFlavorCount;
+ transferableFlavorList->GetLength(&transferableFlavorCount);
+ for (uint32_t j = 0; j < transferableFlavorCount; j++) {
+ nsCOMPtr<nsISupportsCString> currentTransferableFlavor =
+ do_QueryElementAt(transferableFlavorList, j);
+ if (!currentTransferableFlavor)
+ continue;
+ nsXPIDLCString transferableFlavorStr;
+ currentTransferableFlavor->ToString(getter_Copies(transferableFlavorStr));
+
+ for (uint32_t k = 0; k < aLength; k++) {
+ if (transferableFlavorStr.Equals(aFlavorList[k])) {
+ *outResult = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+
+ NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
+
+ for (uint32_t i = 0; i < aLength; i++) {
+ nsDependentCString mimeType(aFlavorList[i]);
+ NSString *pboardType = nil;
+
+ if (nsClipboard::IsStringType(mimeType, &pboardType)) {
+ NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
+ if (availableType && [availableType isEqualToString:pboardType]) {
+ *outResult = true;
+ break;
+ }
+ } else if (!strcmp(aFlavorList[i], kCustomTypesMime)) {
+ NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]];
+ if (availableType) {
+ *outResult = true;
+ break;
+ }
+ } else if (!strcmp(aFlavorList[i], kJPEGImageMime) ||
+ !strcmp(aFlavorList[i], kJPGImageMime) ||
+ !strcmp(aFlavorList[i], kPNGImageMime) ||
+ !strcmp(aFlavorList[i], kGIFImageMime)) {
+ NSString* availableType = [generalPBoard availableTypeFromArray:
+ [NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
+ if (availableType) {
+ *outResult = true;
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = true;
+ return NS_OK;
+}
+
+// This function converts anything that other applications might understand into the system format
+// and puts it into a dictionary which it returns.
+// static
+NSDictionary*
+nsClipboard::PasteboardDictFromTransferable(nsITransferable* aTransferable)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!aTransferable)
+ return nil;
+
+ NSMutableDictionary* pasteboardOutputDict = [NSMutableDictionary dictionary];
+
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return nil;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+ for (uint32_t i = 0; i < flavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("writing out clipboard data of type %s (%d)\n", flavorStr.get(), i));
+
+ NSString *pboardType = nil;
+
+ if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
+ void* data = nullptr;
+ uint32_t dataSize = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &dataSize);
+ nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
+
+ NSString* nativeString;
+ if (data)
+ nativeString = [NSString stringWithCharacters:(const unichar*)data length:(dataSize / sizeof(char16_t))];
+ else
+ nativeString = [NSString string];
+
+ // be nice to Carbon apps, normalize the receiver's contents using Form C.
+ nativeString = [nativeString precomposedStringWithCanonicalMapping];
+
+ [pasteboardOutputDict setObject:nativeString forKey:pboardType];
+
+ free(data);
+ }
+ else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ void* data = nullptr;
+ uint32_t dataSize = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &dataSize);
+ nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
+
+ if (data) {
+ NSData* nativeData = [NSData dataWithBytes:data length:dataSize];
+
+ [pasteboardOutputDict setObject:nativeData forKey:kCustomTypesPboardType];
+ free(data);
+ }
+ }
+ else if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime) ||
+ flavorStr.EqualsLiteral(kNativeImageMime)) {
+ uint32_t dataSize = 0;
+ nsCOMPtr<nsISupports> transferSupports;
+ aTransferable->GetTransferData(flavorStr, getter_AddRefs(transferSupports), &dataSize);
+ nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive(do_QueryInterface(transferSupports));
+ if (!ptrPrimitive)
+ continue;
+
+ nsCOMPtr<nsISupports> primitiveData;
+ ptrPrimitive->GetData(getter_AddRefs(primitiveData));
+
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
+ if (!image) {
+ NS_WARNING("Image isn't an imgIContainer in transferable");
+ continue;
+ }
+
+ RefPtr<SourceSurface> surface =
+ image->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ continue;
+ }
+ CGImageRef imageRef = NULL;
+ rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
+ if (NS_FAILED(rv) || !imageRef) {
+ continue;
+ }
+
+ // Convert the CGImageRef to TIFF data.
+ CFMutableDataRef tiffData = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ CGImageDestinationRef destRef = CGImageDestinationCreateWithData(tiffData,
+ CFSTR("public.tiff"),
+ 1,
+ NULL);
+ CGImageDestinationAddImage(destRef, imageRef, NULL);
+ bool successfullyConverted = CGImageDestinationFinalize(destRef);
+
+ CGImageRelease(imageRef);
+ if (destRef)
+ CFRelease(destRef);
+
+ if (!successfullyConverted) {
+ if (tiffData)
+ CFRelease(tiffData);
+ continue;
+ }
+
+ [pasteboardOutputDict setObject:(NSMutableData*)tiffData forKey:NSTIFFPboardType];
+ if (tiffData)
+ CFRelease(tiffData);
+ }
+ else if (flavorStr.EqualsLiteral(kFileMime)) {
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericFile;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericFile), &len);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> file(do_QueryInterface(genericFile));
+ if (!file) {
+ nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericFile));
+
+ if (ptr) {
+ ptr->GetData(getter_AddRefs(genericFile));
+ file = do_QueryInterface(genericFile);
+ }
+ }
+
+ if (!file) {
+ continue;
+ }
+
+ nsAutoString fileURI;
+ rv = file->GetPath(fileURI);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ NSString* str = nsCocoaUtils::ToNSString(fileURI);
+ NSArray* fileList = [NSArray arrayWithObjects:str, nil];
+ [pasteboardOutputDict setObject:fileList forKey:NSFilenamesPboardType];
+ }
+ else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] forKey:NSFilesPromisePboardType];
+ }
+ else if (flavorStr.EqualsLiteral(kURLMime)) {
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericURL;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericURL), &len);
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+
+ nsAutoString url;
+ urlObject->GetData(url);
+
+ // A newline embedded in the URL means that the form is actually URL + title.
+ int32_t newlinePos = url.FindChar(char16_t('\n'));
+ if (newlinePos >= 0) {
+ url.Truncate(newlinePos);
+
+ nsAutoString urlTitle;
+ urlObject->GetData(urlTitle);
+ urlTitle.Mid(urlTitle, newlinePos + 1, len - (newlinePos + 1));
+
+ NSString *nativeTitle = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(urlTitle.get())
+ length:urlTitle.Length()];
+ // be nice to Carbon apps, normalize the receiver's contents using Form C.
+ [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urln];
+ // Also put the title out as 'urld', since some recipients will look for that.
+ [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urld];
+ [nativeTitle release];
+ }
+
+ // The Finder doesn't like getting random binary data aka
+ // Unicode, so change it into an escaped URL containing only
+ // ASCII.
+ nsAutoCString utf8Data = NS_ConvertUTF16toUTF8(url.get(), url.Length());
+ nsAutoCString escData;
+ NS_EscapeURL(utf8Data.get(), utf8Data.Length(), esc_OnlyNonASCII|esc_AlwaysCopy, escData);
+
+ // printf("Escaped url is %s, length %d\n", escData.get(), escData.Length());
+
+ NSString *nativeURL = [NSString stringWithUTF8String:escData.get()];
+ [pasteboardOutputDict setObject:nativeURL forKey:kCorePboardType_url];
+ }
+ // If it wasn't a type that we recognize as exportable we don't put it on the system
+ // clipboard. We'll just access it from our cached transferable when we need it.
+ }
+
+ return pasteboardOutputDict;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool nsClipboard::IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType)
+{
+ if (aMIMEType.EqualsLiteral(kUnicodeMime)) {
+ *aPasteboardType = NSStringPboardType;
+ return true;
+ } else if (aMIMEType.EqualsLiteral(kRTFMime)) {
+ *aPasteboardType = NSRTFPboardType;
+ return true;
+ } else if (aMIMEType.EqualsLiteral(kHTMLMime)) {
+ *aPasteboardType = NSHTMLPboardType;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+NSString* nsClipboard::WrapHtmlForSystemPasteboard(NSString* aString)
+{
+ NSString* wrapped =
+ [NSString stringWithFormat:
+ @"<html>"
+ "<head>"
+ "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"
+ "</head>"
+ "<body>"
+ "%@"
+ "</body>"
+ "</html>", aString];
+ return wrapped;
+}
+
+/**
+ * Sets the transferable object
+ *
+ */
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* anOwner,
+ int32_t aWhichClipboard)
+{
+ NS_ASSERTION (aTransferable, "clipboard given a null transferable");
+
+ if (aWhichClipboard == kSelectionCache) {
+ if (aTransferable) {
+ SetSelectionCache(aTransferable);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aTransferable == mTransferable && anOwner == mClipboardOwner) {
+ return NS_OK;
+ }
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EmptyClipboard(aWhichClipboard);
+
+ mClipboardOwner = anOwner;
+ mTransferable = aTransferable;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (mTransferable) {
+ rv = SetNativeClipboardData(aWhichClipboard);
+ }
+
+ return rv;
+}
+
+/**
+ * Gets the transferable object
+ *
+ */
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard)
+{
+ NS_ASSERTION (aTransferable, "clipboard given a null transferable");
+
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard)
+ return NS_ERROR_FAILURE;
+
+ if (aTransferable) {
+ return GetNativeClipboardData(aTransferable, aWhichClipboard);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard == kSelectionCache) {
+ ClearSelectionCache();
+ return NS_OK;
+ }
+
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mIgnoreEmptyNotification) {
+ return NS_OK;
+ }
+
+ if (mClipboardOwner) {
+ mClipboardOwner->LosingOwnership(mTransferable);
+ mClipboardOwner = nullptr;
+ }
+
+ mTransferable = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool* _retval)
+{
+ *_retval = false; // we don't support the selection clipboard by default.
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsCocoaDebugUtils.h b/widget/cocoa/nsCocoaDebugUtils.h
new file mode 100644
index 0000000000..814f060878
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCocoaDebugUtils_h_
+#define nsCocoaDebugUtils_h_
+
+#include <CoreServices/CoreServices.h>
+
+// Definitions and declarations of stuff used by us from the CoreSymbolication
+// framework. This is an undocumented, private framework available on OS X
+// 10.6 and up. It's used by Apple utilities like dtrace, atos, ReportCrash
+// and crashreporterd.
+
+typedef struct _CSTypeRef {
+ unsigned long type;
+ void* contents;
+} CSTypeRef;
+
+typedef CSTypeRef CSSymbolicatorRef;
+typedef CSTypeRef CSSymbolOwnerRef;
+typedef CSTypeRef CSSymbolRef;
+typedef CSTypeRef CSSourceInfoRef;
+
+typedef struct _CSRange {
+ unsigned long long location;
+ unsigned long long length;
+} CSRange;
+
+typedef unsigned long long CSArchitecture;
+
+#define kCSNow LONG_MAX
+
+extern "C" {
+
+CSSymbolicatorRef
+CSSymbolicatorCreateWithPid(pid_t pid);
+
+CSSymbolicatorRef
+CSSymbolicatorCreateWithPidFlagsAndNotification(pid_t pid,
+ uint32_t flags,
+ uint32_t notification);
+
+CSArchitecture
+CSSymbolicatorGetArchitecture(CSSymbolicatorRef symbolicator);
+
+CSSymbolOwnerRef
+CSSymbolicatorGetSymbolOwnerWithAddressAtTime(CSSymbolicatorRef symbolicator,
+ unsigned long long address,
+ long time);
+
+const char*
+CSSymbolOwnerGetName(CSSymbolOwnerRef owner);
+
+unsigned long long
+CSSymbolOwnerGetBaseAddress(CSSymbolOwnerRef owner);
+
+CSSymbolRef
+CSSymbolOwnerGetSymbolWithAddress(CSSymbolOwnerRef owner,
+ unsigned long long address);
+
+CSSourceInfoRef
+CSSymbolOwnerGetSourceInfoWithAddress(CSSymbolOwnerRef owner,
+ unsigned long long address);
+
+const char*
+CSSymbolGetName(CSSymbolRef symbol);
+
+CSRange
+CSSymbolGetRange(CSSymbolRef symbol);
+
+const char*
+CSSourceInfoGetFilename(CSSourceInfoRef info);
+
+uint32_t
+CSSourceInfoGetLineNumber(CSSourceInfoRef info);
+
+CSTypeRef
+CSRetain(CSTypeRef);
+
+void
+CSRelease(CSTypeRef);
+
+bool
+CSIsNull(CSTypeRef);
+
+void
+CSShow(CSTypeRef);
+
+const char*
+CSArchitectureGetFamilyName(CSArchitecture);
+
+} // extern "C"
+
+class nsCocoaDebugUtils
+{
+public:
+ // Like NSLog() but records more information (for example the full path to
+ // the executable and the "thread name"). Like NSLog(), writes to both
+ // stdout and the system log.
+ static void DebugLog(const char* aFormat, ...);
+
+ // Logs a stack trace of the current point of execution, to both stdout and
+ // the system log.
+ static void PrintStackTrace();
+
+ // Returns the name of the module that "owns" aAddress. This must be
+ // free()ed by the caller.
+ static char* GetOwnerName(void* aAddress);
+
+ // Returns a symbolicated representation of aAddress. This must be
+ // free()ed by the caller.
+ static char* GetAddressString(void* aAddress);
+
+private:
+ static void DebugLogInt(bool aDecorate, const char* aFormat, ...);
+ static void DebugLogV(bool aDecorate, CFStringRef aFormat, va_list aArgs);
+
+ static void PrintAddress(void* aAddress);
+
+ // The values returned by GetOwnerNameInt() and GetAddressStringInt() must
+ // be free()ed by the caller.
+ static char* GetOwnerNameInt(void* aAddress,
+ CSTypeRef aOwner = sInitializer);
+ static char* GetAddressStringInt(void* aAddress,
+ CSTypeRef aOwner = sInitializer);
+
+ static CSSymbolicatorRef GetSymbolicatorRef();
+ static void ReleaseSymbolicator();
+
+ static CSTypeRef sInitializer;
+ static CSSymbolicatorRef sSymbolicator;
+};
+
+#endif // nsCocoaDebugUtils_h_
diff --git a/widget/cocoa/nsCocoaDebugUtils.mm b/widget/cocoa/nsCocoaDebugUtils.mm
new file mode 100644
index 0000000000..35896dc401
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.mm
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCocoaDebugUtils.h"
+
+#include <pthread.h>
+#include <libproc.h>
+#include <stdarg.h>
+#include <time.h>
+#include <execinfo.h>
+#include <asl.h>
+
+static char gProcPath[PROC_PIDPATHINFO_MAXSIZE] = {0};
+static char gBundleID[MAXPATHLEN] = {0};
+
+static void MaybeGetPathAndID()
+{
+ if (!gProcPath[0]) {
+ proc_pidpath(getpid(), gProcPath, sizeof(gProcPath));
+ }
+ if (!gBundleID[0]) {
+ // Apple's CFLog() uses "com.apple.console" (in its call to asl_open()) if
+ // it can't find the bundle id.
+ CFStringRef bundleID = NULL;
+ CFBundleRef mainBundle = CFBundleGetMainBundle();
+ if (mainBundle) {
+ bundleID = CFBundleGetIdentifier(mainBundle);
+ }
+ if (!bundleID) {
+ strcpy(gBundleID, "com.apple.console");
+ } else {
+ CFStringGetCString(bundleID, gBundleID, sizeof(gBundleID),
+ kCFStringEncodingUTF8);
+ }
+ }
+}
+
+static void GetThreadName(char* aName, size_t aSize)
+{
+ pthread_getname_np(pthread_self(), aName, aSize);
+}
+
+void
+nsCocoaDebugUtils::DebugLog(const char* aFormat, ...)
+{
+ va_list args;
+ va_start(args, aFormat);
+ CFStringRef formatCFSTR =
+ CFStringCreateWithCString(kCFAllocatorDefault, aFormat,
+ kCFStringEncodingUTF8);
+ DebugLogV(true, formatCFSTR, args);
+ CFRelease(formatCFSTR);
+ va_end(args);
+}
+
+void
+nsCocoaDebugUtils::DebugLogInt(bool aDecorate, const char* aFormat, ...)
+{
+ va_list args;
+ va_start(args, aFormat);
+ CFStringRef formatCFSTR =
+ CFStringCreateWithCString(kCFAllocatorDefault, aFormat,
+ kCFStringEncodingUTF8);
+ DebugLogV(aDecorate, formatCFSTR, args);
+ CFRelease(formatCFSTR);
+ va_end(args);
+}
+
+void
+nsCocoaDebugUtils::DebugLogV(bool aDecorate, CFStringRef aFormat,
+ va_list aArgs)
+{
+ MaybeGetPathAndID();
+
+ CFStringRef message =
+ CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL,
+ aFormat, aArgs);
+
+ int msgLength =
+ CFStringGetMaximumSizeForEncoding(CFStringGetLength(message),
+ kCFStringEncodingUTF8);
+ char* msgUTF8 = (char*) calloc(msgLength, 1);
+ CFStringGetCString(message, msgUTF8, msgLength, kCFStringEncodingUTF8);
+ CFRelease(message);
+
+ int finishedLength = msgLength + PROC_PIDPATHINFO_MAXSIZE;
+ char* finished = (char*) calloc(finishedLength, 1);
+ const time_t currentTime = time(NULL);
+ char timestamp[30] = {0};
+ ctime_r(&currentTime, timestamp);
+ if (aDecorate) {
+ char threadName[MAXPATHLEN] = {0};
+ GetThreadName(threadName, sizeof(threadName));
+ snprintf(finished, finishedLength, "(%s) %s[%u] %s[%p] %s\n",
+ timestamp, gProcPath, getpid(), threadName, pthread_self(), msgUTF8);
+ } else {
+ snprintf(finished, finishedLength, "%s\n", msgUTF8);
+ }
+ free(msgUTF8);
+
+ fputs(finished, stdout);
+
+ // Use the Apple System Log facility, as NSLog and CFLog do.
+ aslclient asl = asl_open(NULL, gBundleID, ASL_OPT_NO_DELAY);
+ aslmsg msg = asl_new(ASL_TYPE_MSG);
+ asl_set(msg, ASL_KEY_LEVEL, "4"); // kCFLogLevelWarning, used by NSLog()
+ asl_set(msg, ASL_KEY_MSG, finished);
+ asl_send(asl, msg);
+ asl_free(msg);
+ asl_close(asl);
+
+ free(finished);
+}
+
+CSTypeRef
+nsCocoaDebugUtils::sInitializer = {0};
+
+CSSymbolicatorRef
+nsCocoaDebugUtils::sSymbolicator = {0};
+
+#define STACK_MAX 256
+
+void
+nsCocoaDebugUtils::PrintStackTrace()
+{
+ void** addresses = (void**) calloc(STACK_MAX, sizeof(void*));
+ if (!addresses) {
+ return;
+ }
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ if (CSIsNull(symbolicator)) {
+ free(addresses);
+ return;
+ }
+
+ uint32_t count = backtrace(addresses, STACK_MAX);
+ for (uint32_t i = 0; i < count; ++i) {
+ PrintAddress(addresses[i]);
+ }
+
+ ReleaseSymbolicator();
+ free(addresses);
+}
+
+void
+nsCocoaDebugUtils::PrintAddress(void* aAddress)
+{
+ const char* ownerName = "unknown";
+ const char* addressString = "unknown + 0";
+
+ char* allocatedOwnerName = nullptr;
+ char* allocatedAddressString = nullptr;
+
+ CSSymbolOwnerRef owner = {0};
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+
+ if (!CSIsNull(symbolicator)) {
+ owner =
+ CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long) aAddress,
+ kCSNow);
+ }
+ if (!CSIsNull(owner)) {
+ ownerName = allocatedOwnerName = GetOwnerNameInt(aAddress, owner);
+ addressString = allocatedAddressString = GetAddressStringInt(aAddress, owner);
+ }
+ DebugLogInt(false, " (%s) %s", ownerName, addressString);
+
+ free(allocatedOwnerName);
+ free(allocatedAddressString);
+
+ ReleaseSymbolicator();
+}
+
+char*
+nsCocoaDebugUtils::GetOwnerName(void* aAddress)
+{
+ return GetOwnerNameInt(aAddress);
+}
+
+char*
+nsCocoaDebugUtils::GetOwnerNameInt(void* aAddress, CSTypeRef aOwner)
+{
+ char* retval = (char*) calloc(MAXPATHLEN, 1);
+
+ const char* ownerName = "unknown";
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ CSTypeRef owner = aOwner;
+
+ if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+ owner =
+ CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long) aAddress,
+ kCSNow);
+ }
+
+ if (!CSIsNull(owner)) {
+ ownerName = CSSymbolOwnerGetName(owner);
+ }
+
+ snprintf(retval, MAXPATHLEN, "%s", ownerName);
+ ReleaseSymbolicator();
+
+ return retval;
+}
+
+char*
+nsCocoaDebugUtils::GetAddressString(void* aAddress)
+{
+ return GetAddressStringInt(aAddress);
+}
+
+char*
+nsCocoaDebugUtils::GetAddressStringInt(void* aAddress, CSTypeRef aOwner)
+{
+ char* retval = (char*) calloc(MAXPATHLEN, 1);
+
+ const char* addressName = "unknown";
+ unsigned long long addressOffset = 0;
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ CSTypeRef owner = aOwner;
+
+ if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+ owner =
+ CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long) aAddress,
+ kCSNow);
+ }
+
+ if (!CSIsNull(owner)) {
+ CSSymbolRef symbol =
+ CSSymbolOwnerGetSymbolWithAddress(owner,
+ (unsigned long long) aAddress);
+ if (!CSIsNull(symbol)) {
+ addressName = CSSymbolGetName(symbol);
+ CSRange range = CSSymbolGetRange(symbol);
+ addressOffset = (unsigned long long) aAddress - range.location;
+ } else {
+ addressOffset = (unsigned long long)
+ aAddress - CSSymbolOwnerGetBaseAddress(owner);
+ }
+ }
+
+ snprintf(retval, MAXPATHLEN, "%s + 0x%llx",
+ addressName, addressOffset);
+ ReleaseSymbolicator();
+
+ return retval;
+}
+
+CSSymbolicatorRef
+nsCocoaDebugUtils::GetSymbolicatorRef()
+{
+ if (CSIsNull(sSymbolicator)) {
+ // 0x40e0000 is the value returned by
+ // uint32_t CSSymbolicatorGetFlagsForNListOnlyData(void). We don't use
+ // this method directly because it doesn't exist on OS X 10.6. Unless
+ // we limit ourselves to NList data, it will take too long to get a
+ // stack trace where Dwarf debugging info is available (about 15 seconds
+ // with Firefox). This means we won't be able to get a CSSourceInfoRef,
+ // or line number information. Oh well.
+ sSymbolicator =
+ CSSymbolicatorCreateWithPidFlagsAndNotification(getpid(),
+ 0x40e0000, 0);
+ }
+ // Retaining just after creation prevents crashes when calling symbolicator
+ // code (for example from PrintStackTrace()) as Firefox is quitting. Not
+ // sure why. Doing this may mean that we leak sSymbolicator on quitting
+ // (if we ever created it). No particular harm in that, though.
+ return CSRetain(sSymbolicator);
+}
+
+void
+nsCocoaDebugUtils::ReleaseSymbolicator()
+{
+ if (!CSIsNull(sSymbolicator)) {
+ CSRelease(sSymbolicator);
+ }
+}
diff --git a/widget/cocoa/nsCocoaFeatures.h b/widget/cocoa/nsCocoaFeatures.h
new file mode 100644
index 0000000000..597aff611b
--- /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 0000000000..5a5c16fa14
--- /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 0000000000..139e76b4ad
--- /dev/null
+++ b/widget/cocoa/nsCocoaUtils.h
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCocoaUtils_h_
+#define nsCocoaUtils_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsRect.h"
+#include "imgIContainer.h"
+#include "npapi.h"
+#include "nsTArray.h"
+#include "Units.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+#include "mozilla/EventForwards.h"
+
+// Declare the backingScaleFactor method that we want to call
+// on NSView/Window/Screen objects, if they recognize it.
+@interface NSObject (BackingScaleFactorCategory)
+- (CGFloat)backingScaleFactor;
+@end
+
+#if !defined(MAC_OS_X_VERSION_10_8) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
+enum {
+ NSEventPhaseMayBegin = 0x1 << 5
+};
+#endif
+
+class nsIWidget;
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+} // namespace gfx
+} // namespace mozilla
+
+// Used to retain a Cocoa object for the remainder of a method's execution.
+class nsAutoRetainCocoaObject {
+public:
+explicit nsAutoRetainCocoaObject(id anObject)
+{
+ mObject = NS_OBJC_TRY_EXPR_ABORT([anObject retain]);
+}
+~nsAutoRetainCocoaObject()
+{
+ NS_OBJC_TRY_ABORT([mObject release]);
+}
+private:
+ id mObject; // [STRONG]
+};
+
+// Provide a local autorelease pool for the remainder of a method's execution.
+class nsAutoreleasePool {
+public:
+ nsAutoreleasePool()
+ {
+ mLocalPool = [[NSAutoreleasePool alloc] init];
+ }
+ ~nsAutoreleasePool()
+ {
+ [mLocalPool release];
+ }
+private:
+ NSAutoreleasePool *mLocalPool;
+};
+
+@interface NSApplication (Undocumented)
+
+// Present in all versions of OS X from (at least) 10.2.8 through 10.5.
+- (BOOL)_isRunningModal;
+- (BOOL)_isRunningAppModal;
+
+// It's sometimes necessary to explicitly remove a window from the "window
+// cache" in order to deactivate it. The "window cache" is an undocumented
+// subsystem, all of whose methods are included in the NSWindowCache category
+// of the NSApplication class (in header files generated using class-dump).
+// Present in all versions of OS X from (at least) 10.2.8 through 10.5.
+- (void)_removeWindowFromCache:(NSWindow *)aWindow;
+
+// Send an event to the current Cocoa app-modal session. Present in all
+// versions of OS X from (at least) 10.2.8 through 10.5.
+- (void)_modalSession:(NSModalSession)aSession sendEvent:(NSEvent *)theEvent;
+
+@end
+
+struct KeyBindingsCommand
+{
+ SEL selector;
+ id data;
+};
+
+@interface NativeKeyBindingsRecorder : NSResponder
+{
+@private
+ nsTArray<KeyBindingsCommand>* mCommands;
+}
+
+- (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands;
+
+- (void)doCommandBySelector:(SEL)aSelector;
+
+- (void)insertText:(id)aString;
+
+@end // NativeKeyBindingsRecorder
+
+class nsCocoaUtils
+{
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+
+public:
+
+ // Get the backing scale factor from an object that supports this selector
+ // (NSView/Window/Screen, on 10.7 or later), returning 1.0 if not supported
+ static CGFloat
+ GetBackingScaleFactor(id aObject)
+ {
+ if (HiDPIEnabled() &&
+ [aObject respondsToSelector:@selector(backingScaleFactor)]) {
+ return [aObject backingScaleFactor];
+ }
+ return 1.0;
+ }
+
+ // Conversions between Cocoa points and device pixels, given the backing
+ // scale factor from a view/window/screen.
+ static int32_t
+ CocoaPointsToDevPixels(CGFloat aPts, CGFloat aBackingScale)
+ {
+ return NSToIntRound(aPts * aBackingScale);
+ }
+
+ static LayoutDeviceIntPoint
+ CocoaPointsToDevPixels(const NSPoint& aPt, CGFloat aBackingScale)
+ {
+ return LayoutDeviceIntPoint(NSToIntRound(aPt.x * aBackingScale),
+ NSToIntRound(aPt.y * aBackingScale));
+ }
+
+ static LayoutDeviceIntPoint
+ CocoaPointsToDevPixelsRoundDown(const NSPoint& aPt, CGFloat aBackingScale)
+ {
+ return LayoutDeviceIntPoint(NSToIntFloor(aPt.x * aBackingScale),
+ NSToIntFloor(aPt.y * aBackingScale));
+ }
+
+ static LayoutDeviceIntRect
+ CocoaPointsToDevPixels(const NSRect& aRect, CGFloat aBackingScale)
+ {
+ return LayoutDeviceIntRect(NSToIntRound(aRect.origin.x * aBackingScale),
+ NSToIntRound(aRect.origin.y * aBackingScale),
+ NSToIntRound(aRect.size.width * aBackingScale),
+ NSToIntRound(aRect.size.height * aBackingScale));
+ }
+
+ static CGFloat
+ DevPixelsToCocoaPoints(int32_t aPixels, CGFloat aBackingScale)
+ {
+ return (CGFloat)aPixels / aBackingScale;
+ }
+
+ static NSPoint
+ DevPixelsToCocoaPoints(const mozilla::LayoutDeviceIntPoint& aPt,
+ CGFloat aBackingScale)
+ {
+ return NSMakePoint((CGFloat)aPt.x / aBackingScale,
+ (CGFloat)aPt.y / aBackingScale);
+ }
+
+ // Implements an NSPoint equivalent of -[NSWindow convertRectFromScreen:].
+ static NSPoint
+ ConvertPointFromScreen(NSWindow* aWindow, const NSPoint& aPt)
+ {
+ return [aWindow convertRectFromScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin;
+ }
+
+ // Implements an NSPoint equivalent of -[NSWindow convertRectToScreen:].
+ static NSPoint
+ ConvertPointToScreen(NSWindow* aWindow, const NSPoint& aPt)
+ {
+ return [aWindow convertRectToScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin;
+ }
+
+ static NSRect
+ DevPixelsToCocoaPoints(const LayoutDeviceIntRect& aRect,
+ CGFloat aBackingScale)
+ {
+ return NSMakeRect((CGFloat)aRect.x / aBackingScale,
+ (CGFloat)aRect.y / aBackingScale,
+ (CGFloat)aRect.width / aBackingScale,
+ (CGFloat)aRect.height / aBackingScale);
+ }
+
+ // Returns the given y coordinate, which must be in screen coordinates,
+ // flipped from Gecko to Cocoa or Cocoa to Gecko.
+ static float FlippedScreenY(float y);
+
+ // The following functions come in "DevPix" variants that work with
+ // backing-store (device pixel) coordinates, as well as the original
+ // versions that expect coordinates in Cocoa points/CSS pixels.
+ // The difference becomes important in HiDPI display modes, where Cocoa
+ // points and backing-store pixels are no longer 1:1.
+
+ // Gecko rects (nsRect) contain an origin (x,y) in a coordinate
+ // system with (0,0) in the top-left of the primary screen. Cocoa rects
+ // (NSRect) contain an origin (x,y) in a coordinate system with (0,0)
+ // in the bottom-left of the primary screen. Both nsRect and NSRect
+ // contain width/height info, with no difference in their use.
+ // This function does no scaling, so the Gecko coordinates are
+ // expected to be desktop pixels, which are equal to Cocoa points
+ // (by definition).
+ static NSRect GeckoRectToCocoaRect(const mozilla::DesktopIntRect &geckoRect);
+
+ // Converts aGeckoRect in dev pixels to points in Cocoa coordinates
+ static NSRect
+ GeckoRectToCocoaRectDevPix(const mozilla::LayoutDeviceIntRect &aGeckoRect,
+ CGFloat aBackingScale);
+
+ // See explanation for geckoRectToCocoaRect, guess what this does...
+ static mozilla::DesktopIntRect CocoaRectToGeckoRect(const NSRect &cocoaRect);
+
+ static mozilla::LayoutDeviceIntRect CocoaRectToGeckoRectDevPix(
+ const NSRect& aCocoaRect, CGFloat aBackingScale);
+
+ // Gives the location for the event in screen coordinates. Do not call this
+ // unless the window the event was originally targeted at is still alive!
+ // anEvent may be nil -- in that case the current mouse location is returned.
+ static NSPoint ScreenLocationForEvent(NSEvent* anEvent);
+
+ // Determines if an event happened over a window, whether or not the event
+ // is for the window. Does not take window z-order into account.
+ static BOOL IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow);
+
+ // Events are set up so that their coordinates refer to the window to which they
+ // were originally sent. If we reroute the event somewhere else, we'll have
+ // to get the window coordinates this way. Do not call this unless the window
+ // the event was originally targeted at is still alive!
+ static NSPoint EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow);
+
+ // Compatibility wrappers for the -[NSEvent phase], -[NSEvent momentumPhase],
+ // -[NSEvent hasPreciseScrollingDeltas] and -[NSEvent scrollingDeltaX/Y] APIs
+ // that became availaible starting with the 10.7 SDK.
+ // All of these can be removed once we drop support for 10.6.
+ static NSEventPhase EventPhase(NSEvent* aEvent);
+ static NSEventPhase EventMomentumPhase(NSEvent* aEvent);
+ static BOOL IsMomentumScrollEvent(NSEvent* aEvent);
+ static BOOL HasPreciseScrollingDeltas(NSEvent* aEvent);
+ static void GetScrollingDeltas(NSEvent* aEvent, CGFloat* aOutDeltaX, CGFloat* aOutDeltaY);
+ static BOOL EventHasPhaseInformation(NSEvent* aEvent);
+
+ // Hides the Menu bar and the Dock. Multiple hide/show requests can be nested.
+ static void HideOSChromeOnScreen(bool aShouldHide);
+
+ static nsIWidget* GetHiddenWindowWidget();
+
+ static void PrepareForNativeAppModalDialog();
+ static void CleanUpAfterNativeAppModalDialog();
+
+ // 3 utility functions to go from a frame of imgIContainer to CGImage and then to NSImage
+ // Convert imgIContainer -> CGImageRef, caller owns result
+
+ /** Creates a <code>CGImageRef</code> from a frame contained in an <code>imgIContainer</code>.
+ Copies the pixel data from the indicated frame of the <code>imgIContainer</code> into a new <code>CGImageRef</code>.
+ The caller owns the <code>CGImageRef</code>.
+ @param aFrame the frame to convert
+ @param aResult the resulting CGImageRef
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateCGImageFromSurface(SourceSurface* aSurface,
+ CGImageRef* aResult);
+
+ /** Creates a Cocoa <code>NSImage</code> from a <code>CGImageRef</code>.
+ Copies the pixel data from the <code>CGImageRef</code> into a new <code>NSImage</code>.
+ The caller owns the <code>NSImage</code>.
+ @param aInputImage the image to convert
+ @param aResult the resulting NSImage
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult);
+
+ /** Creates a Cocoa <code>NSImage</code> from a frame of an <code>imgIContainer</code>.
+ Combines the two methods above. The caller owns the <code>NSImage</code>.
+ @param aImage the image to extract a frame from
+ @param aWhichFrame the frame to extract (see imgIContainer FRAME_*)
+ @param aResult the resulting NSImage
+ @param scaleFactor the desired scale factor of the NSImage (2 for a retina display)
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor);
+
+ /**
+ * Returns nsAString for aSrc.
+ */
+ static void GetStringForNSString(const NSString *aSrc, nsAString& aDist);
+
+ /**
+ * Makes NSString instance for aString.
+ */
+ static NSString* ToNSString(const nsAString& aString);
+
+ /**
+ * Returns NSRect for aGeckoRect.
+ * Just copies values between the two types; it does no coordinate-system
+ * conversion, so both rects must have the same coordinate origin/direction.
+ */
+ static void GeckoRectToNSRect(const nsIntRect& aGeckoRect,
+ NSRect& aOutCocoaRect);
+
+ /**
+ * Returns Gecko rect for aCocoaRect.
+ * Just copies values between the two types; it does no coordinate-system
+ * conversion, so both rects must have the same coordinate origin/direction.
+ */
+ static void NSRectToGeckoRect(const NSRect& aCocoaRect,
+ nsIntRect& aOutGeckoRect);
+
+ /**
+ * Makes NSEvent instance for aEventTytpe and aEvent.
+ */
+ static NSEvent* MakeNewCocoaEventWithType(NSEventType aEventType,
+ NSEvent *aEvent);
+
+ /**
+ * Initializes aNPCocoaEvent.
+ */
+ static void InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent);
+
+ /**
+ * Initializes WidgetInputEvent for aNativeEvent or aModifiers.
+ */
+ static void InitInputEvent(mozilla::WidgetInputEvent &aInputEvent,
+ NSEvent* aNativeEvent);
+
+ /**
+ * Converts the native modifiers from aNativeEvent into WidgetMouseEvent
+ * Modifiers. aNativeEvent can be null.
+ */
+ static mozilla::Modifiers ModifiersForEvent(NSEvent* aNativeEvent);
+
+ /**
+ * ConvertToCarbonModifier() returns carbon modifier flags for the cocoa
+ * modifier flags.
+ * NOTE: The result never includes right*Key.
+ */
+ static UInt32 ConvertToCarbonModifier(NSUInteger aCocoaModifier);
+
+ /**
+ * Whether to support HiDPI rendering. For testing purposes, to be removed
+ * once we're comfortable with the HiDPI behavior.
+ */
+ static bool HiDPIEnabled();
+
+ /**
+ * Keys can optionally be bound by system or user key bindings to one or more
+ * commands based on selectors. This collects any such commands in the
+ * provided array.
+ */
+ static void GetCommandsFromKeyEvent(NSEvent* aEvent,
+ nsTArray<KeyBindingsCommand>& aCommands);
+
+ /**
+ * Converts the string name of a Gecko key (like "VK_HOME") to the
+ * corresponding Cocoa Unicode character.
+ */
+ static uint32_t ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName);
+
+ /**
+ * Converts a Gecko key code (like NS_VK_HOME) to the corresponding Cocoa
+ * Unicode character.
+ */
+ static uint32_t ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode);
+
+ /**
+ * Convert string with font attribute to NSMutableAttributedString
+ */
+ static NSMutableAttributedString* GetNSMutableAttributedString(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRanges,
+ const bool aIsVertical,
+ const CGFloat aBackingScaleFactor);
+};
+
+#endif // nsCocoaUtils_h_
diff --git a/widget/cocoa/nsCocoaUtils.mm b/widget/cocoa/nsCocoaUtils.mm
new file mode 100644
index 0000000000..3138245aa7
--- /dev/null
+++ b/widget/cocoa/nsCocoaUtils.mm
@@ -0,0 +1,1022 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cmath>
+
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "ImageRegion.h"
+#include "nsCocoaUtils.h"
+#include "nsChildView.h"
+#include "nsMenuBarX.h"
+#include "nsCocoaWindow.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIAppShellService.h"
+#include "nsIXULWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsIServiceManager.h"
+#include "nsMenuUtilsX.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+#include "SVGImageContext.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+using mozilla::gfx::BackendType;
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::DrawTarget;
+using mozilla::gfx::Factory;
+using mozilla::gfx::SamplingFilter;
+using mozilla::gfx::IntPoint;
+using mozilla::gfx::IntRect;
+using mozilla::gfx::IntSize;
+using mozilla::gfx::SurfaceFormat;
+using mozilla::gfx::SourceSurface;
+using mozilla::image::ImageRegion;
+using std::ceil;
+
+static float
+MenuBarScreenHeight()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSArray* allScreens = [NSScreen screens];
+ if ([allScreens count]) {
+ return [[allScreens objectAtIndex:0] frame].size.height;
+ }
+
+ return 0.0;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0.0);
+}
+
+float
+nsCocoaUtils::FlippedScreenY(float y)
+{
+ return MenuBarScreenHeight() - y;
+}
+
+NSRect nsCocoaUtils::GeckoRectToCocoaRect(const DesktopIntRect &geckoRect)
+{
+ // We only need to change the Y coordinate by starting with the primary screen
+ // height and subtracting the gecko Y coordinate of the bottom of the rect.
+ return NSMakeRect(geckoRect.x,
+ MenuBarScreenHeight() - geckoRect.YMost(),
+ geckoRect.width,
+ geckoRect.height);
+}
+
+NSRect
+nsCocoaUtils::GeckoRectToCocoaRectDevPix(const LayoutDeviceIntRect &aGeckoRect,
+ CGFloat aBackingScale)
+{
+ return NSMakeRect(aGeckoRect.x / aBackingScale,
+ MenuBarScreenHeight() - aGeckoRect.YMost() / aBackingScale,
+ aGeckoRect.width / aBackingScale,
+ aGeckoRect.height / aBackingScale);
+}
+
+DesktopIntRect nsCocoaUtils::CocoaRectToGeckoRect(const NSRect &cocoaRect)
+{
+ // We only need to change the Y coordinate by starting with the primary screen
+ // height and subtracting both the cocoa y origin and the height of the
+ // cocoa rect.
+ DesktopIntRect rect;
+ rect.x = NSToIntRound(cocoaRect.origin.x);
+ rect.y = NSToIntRound(FlippedScreenY(cocoaRect.origin.y + cocoaRect.size.height));
+ rect.width = NSToIntRound(cocoaRect.origin.x + cocoaRect.size.width) - rect.x;
+ rect.height = NSToIntRound(FlippedScreenY(cocoaRect.origin.y)) - rect.y;
+ return rect;
+}
+
+LayoutDeviceIntRect nsCocoaUtils::CocoaRectToGeckoRectDevPix(
+ const NSRect& aCocoaRect, CGFloat aBackingScale)
+{
+ LayoutDeviceIntRect rect;
+ rect.x = NSToIntRound(aCocoaRect.origin.x * aBackingScale);
+ rect.y = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y + aCocoaRect.size.height) * aBackingScale);
+ rect.width = NSToIntRound((aCocoaRect.origin.x + aCocoaRect.size.width) * aBackingScale) - rect.x;
+ rect.height = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y) * aBackingScale) - rect.y;
+ return rect;
+}
+
+NSPoint nsCocoaUtils::ScreenLocationForEvent(NSEvent* anEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // Don't trust mouse locations of mouse move events, see bug 443178.
+ if (!anEvent || [anEvent type] == NSMouseMoved)
+ return [NSEvent mouseLocation];
+
+ // Pin momentum scroll events to the location of the last user-controlled
+ // scroll event.
+ if (IsMomentumScrollEvent(anEvent))
+ return ChildViewMouseTracker::sLastScrollEventScreenLocation;
+
+ return nsCocoaUtils::ConvertPointToScreen([anEvent window], [anEvent locationInWindow]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
+}
+
+BOOL nsCocoaUtils::IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return NSPointInRect(ScreenLocationForEvent(anEvent), [aWindow frame]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+NSPoint nsCocoaUtils::EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return nsCocoaUtils::ConvertPointFromScreen(aWindow, ScreenLocationForEvent(anEvent));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
+}
+
+@interface NSEvent (ScrollPhase)
+// 10.5 and 10.6
+- (long long)_scrollPhase;
+// 10.7 and above
+- (NSEventPhase)phase;
+- (NSEventPhase)momentumPhase;
+@end
+
+NSEventPhase nsCocoaUtils::EventPhase(NSEvent* aEvent)
+{
+ if ([aEvent respondsToSelector:@selector(phase)]) {
+ return [aEvent phase];
+ }
+ return NSEventPhaseNone;
+}
+
+NSEventPhase nsCocoaUtils::EventMomentumPhase(NSEvent* aEvent)
+{
+ if ([aEvent respondsToSelector:@selector(momentumPhase)]) {
+ return [aEvent momentumPhase];
+ }
+ if ([aEvent respondsToSelector:@selector(_scrollPhase)]) {
+ switch ([aEvent _scrollPhase]) {
+ case 1: return NSEventPhaseBegan;
+ case 2: return NSEventPhaseChanged;
+ case 3: return NSEventPhaseEnded;
+ default: return NSEventPhaseNone;
+ }
+ }
+ return NSEventPhaseNone;
+}
+
+BOOL nsCocoaUtils::IsMomentumScrollEvent(NSEvent* aEvent)
+{
+ return [aEvent type] == NSScrollWheel &&
+ EventMomentumPhase(aEvent) != NSEventPhaseNone;
+}
+
+@interface NSEvent (HasPreciseScrollingDeltas)
+// 10.7 and above
+- (BOOL)hasPreciseScrollingDeltas;
+// For 10.6 and below, see the comment in nsChildView.h about _eventRef
+- (EventRef)_eventRef;
+@end
+
+BOOL nsCocoaUtils::HasPreciseScrollingDeltas(NSEvent* aEvent)
+{
+ if ([aEvent respondsToSelector:@selector(hasPreciseScrollingDeltas)]) {
+ return [aEvent hasPreciseScrollingDeltas];
+ }
+
+ // For events that don't contain pixel scrolling information, the event
+ // kind of their underlaying carbon event is kEventMouseWheelMoved instead
+ // of kEventMouseScroll.
+ EventRef carbonEvent = [aEvent _eventRef];
+ return carbonEvent && ::GetEventKind(carbonEvent) == kEventMouseScroll;
+}
+
+@interface NSEvent (ScrollingDeltas)
+// 10.6 and below
+- (CGFloat)deviceDeltaX;
+- (CGFloat)deviceDeltaY;
+// 10.7 and above
+- (CGFloat)scrollingDeltaX;
+- (CGFloat)scrollingDeltaY;
+@end
+
+void nsCocoaUtils::GetScrollingDeltas(NSEvent* aEvent, CGFloat* aOutDeltaX, CGFloat* aOutDeltaY)
+{
+ if ([aEvent respondsToSelector:@selector(scrollingDeltaX)]) {
+ *aOutDeltaX = [aEvent scrollingDeltaX];
+ *aOutDeltaY = [aEvent scrollingDeltaY];
+ return;
+ }
+ if ([aEvent respondsToSelector:@selector(deviceDeltaX)] &&
+ HasPreciseScrollingDeltas(aEvent)) {
+ // Calling deviceDeltaX/Y on those events that do not contain pixel
+ // scrolling information triggers a Cocoa assertion and an
+ // Objective-C NSInternalInconsistencyException.
+ *aOutDeltaX = [aEvent deviceDeltaX];
+ *aOutDeltaY = [aEvent deviceDeltaY];
+ return;
+ }
+
+ // This is only hit pre-10.7 when we are called on a scroll event that does
+ // not contain pixel scrolling information.
+ CGFloat lineDeltaPixels = 12;
+ *aOutDeltaX = [aEvent deltaX] * lineDeltaPixels;
+ *aOutDeltaY = [aEvent deltaY] * lineDeltaPixels;
+}
+
+BOOL nsCocoaUtils::EventHasPhaseInformation(NSEvent* aEvent)
+{
+ if (![aEvent respondsToSelector:@selector(phase)]) {
+ return NO;
+ }
+ return EventPhase(aEvent) != NSEventPhaseNone ||
+ EventMomentumPhase(aEvent) != NSEventPhaseNone;
+}
+
+void nsCocoaUtils::HideOSChromeOnScreen(bool aShouldHide)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Keep track of how many hiding requests have been made, so that they can
+ // be nested.
+ static int sHiddenCount = 0;
+
+ sHiddenCount += aShouldHide ? 1 : -1;
+ NS_ASSERTION(sHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls");
+
+ NSApplicationPresentationOptions options =
+ sHiddenCount <= 0 ? NSApplicationPresentationDefault :
+ NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
+ [NSApp setPresentationOptions:options];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
+nsIWidget* nsCocoaUtils::GetHiddenWindowWidget()
+{
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ if (!appShell) {
+ NS_WARNING("Couldn't get AppShellService in order to get hidden window ref");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIXULWindow> hiddenWindow;
+ appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
+ if (!hiddenWindow) {
+ // Don't warn, this happens during shutdown, bug 358607.
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIBaseWindow> baseHiddenWindow;
+ baseHiddenWindow = do_GetInterface(hiddenWindow);
+ if (!baseHiddenWindow) {
+ NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIXULWindow)");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIWidget> hiddenWindowWidget;
+ if (NS_FAILED(baseHiddenWindow->GetMainWidget(getter_AddRefs(hiddenWindowWidget)))) {
+ NS_WARNING("Couldn't get nsIWidget from hidden window (nsIBaseWindow)");
+ return nullptr;
+ }
+
+ return hiddenWindowWidget;
+}
+
+void nsCocoaUtils::PrepareForNativeAppModalDialog()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Don't do anything if this is embedding. We'll assume that if there is no hidden
+ // window we shouldn't do anything, and that should cover the embedding case.
+ nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (!hiddenWindowMenuBar)
+ return;
+
+ // First put up the hidden window menu bar so that app menu event handling is correct.
+ hiddenWindowMenuBar->Paint();
+
+ NSMenu* mainMenu = [NSApp mainMenu];
+ NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+
+ // Create new menu bar for use with modal dialog
+ NSMenu* newMenuBar = [[NSMenu alloc] initWithTitle:@""];
+
+ // Swap in our app menu. Note that the event target is whatever window is up when
+ // the app modal dialog goes up.
+ NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
+ [mainMenu removeItemAtIndex:0];
+ [newMenuBar insertItem:firstMenuItem atIndex:0];
+ [firstMenuItem release];
+
+ // Add standard edit menu
+ [newMenuBar addItem:nsMenuUtilsX::GetStandardEditMenuItem()];
+
+ // Show the new menu bar
+ [NSApp setMainMenu:newMenuBar];
+ [newMenuBar release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaUtils::CleanUpAfterNativeAppModalDialog()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Don't do anything if this is embedding. We'll assume that if there is no hidden
+ // window we shouldn't do anything, and that should cover the embedding case.
+ nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (!hiddenWindowMenuBar)
+ return;
+
+ NSWindow* mainWindow = [NSApp mainWindow];
+ if (!mainWindow)
+ hiddenWindowMenuBar->Paint();
+ else
+ [WindowDelegate paintMenubarForWindow:mainWindow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void data_ss_release_callback(void *aDataSourceSurface,
+ const void *data,
+ size_t size)
+{
+ if (aDataSourceSurface) {
+ static_cast<DataSourceSurface*>(aDataSourceSurface)->Unmap();
+ static_cast<DataSourceSurface*>(aDataSourceSurface)->Release();
+ }
+}
+
+nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface,
+ CGImageRef* aResult)
+{
+ RefPtr<DataSourceSurface> dataSurface;
+
+ if (aSurface->GetFormat() == SurfaceFormat::B8G8R8A8) {
+ dataSurface = aSurface->GetDataSurface();
+ } else {
+ // CGImageCreate only supports 16- and 32-bit bit-depth
+ // Convert format to SurfaceFormat::B8G8R8A8
+ dataSurface = gfxUtils::
+ CopySurfaceToDataSourceSurfaceWithFormat(aSurface,
+ SurfaceFormat::B8G8R8A8);
+ }
+
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+ if (height < 1 || width < 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+ // The Unmap() call happens in data_ss_release_callback
+
+ // Create a CGImageRef with the bits from the image, taking into account
+ // the alpha ordering and endianness of the machine so we don't have to
+ // touch the bits ourselves.
+ CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(dataSurface.forget().take(),
+ map.mData,
+ map.mStride * height,
+ data_ss_release_callback);
+ CGColorSpaceRef colorSpace = ::CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
+ *aResult = ::CGImageCreate(width,
+ height,
+ 8,
+ 32,
+ map.mStride,
+ colorSpace,
+ kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst,
+ dataProvider,
+ NULL,
+ 0,
+ kCGRenderingIntentDefault);
+ ::CGColorSpaceRelease(colorSpace);
+ ::CGDataProviderRelease(dataProvider);
+ return *aResult ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsCocoaUtils::CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Be very careful when creating the NSImage that the backing NSImageRep is
+ // exactly 1:1 with the input image. On a retina display, both [NSImage
+ // lockFocus] and [NSImage initWithCGImage:size:] will create an image with a
+ // 2x backing NSImageRep. This prevents NSCursor from recognizing a retina
+ // cursor, which only occurs if pixelsWide and pixelsHigh are exactly 2x the
+ // size of the NSImage.
+ //
+ // For example, if a 32x32 SVG cursor is rendered on a retina display, then
+ // aInputImage will be 64x64. The resulting NSImage will be scaled back down
+ // to 32x32 so it stays the correct size on the screen by changing its size
+ // (resizing a NSImage only scales the image and doesn't resample the data).
+ // If aInputImage is converted using [NSImage initWithCGImage:size:] then the
+ // bitmap will be 128x128 and NSCursor won't recognize a retina cursor, since
+ // it will expect a 64x64 bitmap.
+
+ int32_t width = ::CGImageGetWidth(aInputImage);
+ int32_t height = ::CGImageGetHeight(aInputImage);
+ NSRect imageRect = ::NSMakeRect(0.0, 0.0, width, height);
+
+ NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:NULL
+ pixelsWide:width
+ pixelsHigh:height
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bitmapFormat:NSAlphaFirstBitmapFormat
+ bytesPerRow:0
+ bitsPerPixel:0];
+
+ NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:context];
+
+ // Get the Quartz context and draw.
+ CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ ::CGContextDrawImage(imageContext, *(CGRect*)&imageRect, aInputImage);
+
+ [NSGraphicsContext restoreGraphicsState];
+
+ *aResult = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
+ [*aResult addRepresentation:offscreenRep];
+ [offscreenRep release];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor)
+{
+ RefPtr<SourceSurface> surface;
+ int32_t width = 0, height = 0;
+ aImage->GetWidth(&width);
+ aImage->GetHeight(&height);
+
+ // Render a vector image at the correct resolution on a retina display
+ if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) {
+ IntSize scaledSize = IntSize::Ceil(width * scaleFactor, height * scaleFactor);
+
+ RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()->
+ CreateOffscreenContentDrawTarget(scaledSize, SurfaceFormat::B8G8R8A8);
+ if (!drawTarget || !drawTarget->IsValid()) {
+ NS_ERROR("Failed to create valid DrawTarget");
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(drawTarget);
+ MOZ_ASSERT(context);
+
+ mozilla::image::DrawResult res =
+ aImage->Draw(context, scaledSize, ImageRegion::Create(scaledSize),
+ aWhichFrame, SamplingFilter::POINT,
+ /* no SVGImageContext */ Nothing(),
+ imgIContainer::FLAG_SYNC_DECODE);
+
+ if (res != mozilla::image::DrawResult::SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ surface = drawTarget->Snapshot();
+ } else {
+ surface = aImage->GetFrame(aWhichFrame, imgIContainer::FLAG_SYNC_DECODE);
+ }
+
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ CGImageRef imageRef = NULL;
+ nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
+ if (NS_FAILED(rv) || !imageRef) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = nsCocoaUtils::CreateNSImageFromCGImage(imageRef, aResult);
+ if (NS_FAILED(rv) || !aResult) {
+ return NS_ERROR_FAILURE;
+ }
+ ::CGImageRelease(imageRef);
+
+ // Ensure the image will be rendered the correct size on a retina display
+ NSSize size = NSMakeSize(width, height);
+ [*aResult setSize:size];
+ [[[*aResult representations] objectAtIndex:0] setSize:size];
+ return NS_OK;
+}
+
+// static
+void
+nsCocoaUtils::GetStringForNSString(const NSString *aSrc, nsAString& aDist)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aSrc) {
+ aDist.Truncate();
+ return;
+ }
+
+ aDist.SetLength([aSrc length]);
+ [aSrc getCharacters: reinterpret_cast<unichar*>(aDist.BeginWriting())];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// static
+NSString*
+nsCocoaUtils::ToNSString(const nsAString& aString)
+{
+ if (aString.IsEmpty()) {
+ return [NSString string];
+ }
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aString.BeginReading())
+ length:aString.Length()];
+}
+
+// static
+void
+nsCocoaUtils::GeckoRectToNSRect(const nsIntRect& aGeckoRect,
+ NSRect& aOutCocoaRect)
+{
+ aOutCocoaRect.origin.x = aGeckoRect.x;
+ aOutCocoaRect.origin.y = aGeckoRect.y;
+ aOutCocoaRect.size.width = aGeckoRect.width;
+ aOutCocoaRect.size.height = aGeckoRect.height;
+}
+
+// static
+void
+nsCocoaUtils::NSRectToGeckoRect(const NSRect& aCocoaRect,
+ nsIntRect& aOutGeckoRect)
+{
+ aOutGeckoRect.x = NSToIntRound(aCocoaRect.origin.x);
+ aOutGeckoRect.y = NSToIntRound(aCocoaRect.origin.y);
+ aOutGeckoRect.width = NSToIntRound(aCocoaRect.origin.x + aCocoaRect.size.width) - aOutGeckoRect.x;
+ aOutGeckoRect.height = NSToIntRound(aCocoaRect.origin.y + aCocoaRect.size.height) - aOutGeckoRect.y;
+}
+
+// static
+NSEvent*
+nsCocoaUtils::MakeNewCocoaEventWithType(NSEventType aEventType, NSEvent *aEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSEvent* newEvent =
+ [NSEvent keyEventWithType:aEventType
+ location:[aEvent locationInWindow]
+ modifierFlags:[aEvent modifierFlags]
+ timestamp:[aEvent timestamp]
+ windowNumber:[aEvent windowNumber]
+ context:[aEvent context]
+ characters:[aEvent characters]
+ charactersIgnoringModifiers:[aEvent charactersIgnoringModifiers]
+ isARepeat:[aEvent isARepeat]
+ keyCode:[aEvent keyCode]];
+ return newEvent;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// static
+void
+nsCocoaUtils::InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent)
+{
+ memset(aNPCocoaEvent, 0, sizeof(NPCocoaEvent));
+}
+
+// static
+void
+nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent,
+ NSEvent* aNativeEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ aInputEvent.mModifiers = ModifiersForEvent(aNativeEvent);
+ aInputEvent.mTime = PR_IntervalNow();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// static
+Modifiers
+nsCocoaUtils::ModifiersForEvent(NSEvent* aNativeEvent)
+{
+ NSUInteger modifiers =
+ aNativeEvent ? [aNativeEvent modifierFlags] : [NSEvent modifierFlags];
+ Modifiers result = 0;
+ if (modifiers & NSShiftKeyMask) {
+ result |= MODIFIER_SHIFT;
+ }
+ if (modifiers & NSControlKeyMask) {
+ result |= MODIFIER_CONTROL;
+ }
+ if (modifiers & NSAlternateKeyMask) {
+ result |= MODIFIER_ALT;
+ // Mac's option key is similar to other platforms' AltGr key.
+ // Let's set AltGr flag when option key is pressed for consistency with
+ // other platforms.
+ result |= MODIFIER_ALTGRAPH;
+ }
+ if (modifiers & NSCommandKeyMask) {
+ result |= MODIFIER_META;
+ }
+
+ if (modifiers & NSAlphaShiftKeyMask) {
+ result |= MODIFIER_CAPSLOCK;
+ }
+ // Mac doesn't have NumLock key. We can assume that NumLock is always locked
+ // if user is using a keyboard which has numpad. Otherwise, if user is using
+ // a keyboard which doesn't have numpad, e.g., MacBook's keyboard, we can
+ // assume that NumLock is always unlocked.
+ // Unfortunately, we cannot know whether current keyboard has numpad or not.
+ // We should notify locked state only when keys in numpad are pressed.
+ // By this, web applications may not be confused by unexpected numpad key's
+ // key event with unlocked state.
+ if (modifiers & NSNumericPadKeyMask) {
+ result |= MODIFIER_NUMLOCK;
+ }
+
+ // Be aware, NSFunctionKeyMask is included when arrow keys, home key or some
+ // other keys are pressed. We cannot check whether 'fn' key is pressed or
+ // not by the flag.
+
+ return result;
+}
+
+// static
+UInt32
+nsCocoaUtils::ConvertToCarbonModifier(NSUInteger aCocoaModifier)
+{
+ UInt32 carbonModifier = 0;
+ if (aCocoaModifier & NSAlphaShiftKeyMask) {
+ carbonModifier |= alphaLock;
+ }
+ if (aCocoaModifier & NSControlKeyMask) {
+ carbonModifier |= controlKey;
+ }
+ if (aCocoaModifier & NSAlternateKeyMask) {
+ carbonModifier |= optionKey;
+ }
+ if (aCocoaModifier & NSShiftKeyMask) {
+ carbonModifier |= shiftKey;
+ }
+ if (aCocoaModifier & NSCommandKeyMask) {
+ carbonModifier |= cmdKey;
+ }
+ if (aCocoaModifier & NSNumericPadKeyMask) {
+ carbonModifier |= kEventKeyModifierNumLockMask;
+ }
+ if (aCocoaModifier & NSFunctionKeyMask) {
+ carbonModifier |= kEventKeyModifierFnMask;
+ }
+ return carbonModifier;
+}
+
+// While HiDPI support is not 100% complete and tested, we'll have a pref
+// to allow it to be turned off in case of problems (or for testing purposes).
+
+// gfx.hidpi.enabled is an integer with the meaning:
+// <= 0 : HiDPI support is disabled
+// 1 : HiDPI enabled provided all screens have the same backing resolution
+// > 1 : HiDPI enabled even if there are a mixture of screen modes
+
+// All the following code is to be removed once HiDPI work is more complete.
+
+static bool sHiDPIEnabled = false;
+static bool sHiDPIPrefInitialized = false;
+
+// static
+bool
+nsCocoaUtils::HiDPIEnabled()
+{
+ if (!sHiDPIPrefInitialized) {
+ sHiDPIPrefInitialized = true;
+
+ int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1);
+ if (prefSetting <= 0) {
+ return false;
+ }
+
+ // prefSetting is at least 1, need to check attached screens...
+
+ int scaleFactors = 0; // used as a bitset to track the screen types found
+ NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+ while (NSScreen *screen = [screenEnum nextObject]) {
+ NSDictionary *desc = [screen deviceDescription];
+ if ([desc objectForKey:NSDeviceIsScreen] == nil) {
+ continue;
+ }
+ CGFloat scale =
+ [screen respondsToSelector:@selector(backingScaleFactor)] ?
+ [screen backingScaleFactor] : 1.0;
+ // Currently, we only care about differentiating "1.0" and "2.0",
+ // so we set one of the two low bits to record which.
+ if (scale > 1.0) {
+ scaleFactors |= 2;
+ } else {
+ scaleFactors |= 1;
+ }
+ }
+
+ // Now scaleFactors will be:
+ // 0 if no screens (supporting backingScaleFactor) found
+ // 1 if only lo-DPI screens
+ // 2 if only hi-DPI screens
+ // 3 if both lo- and hi-DPI screens
+ // We'll enable HiDPI support if there's only a single screen type,
+ // OR if the pref setting is explicitly greater than 1.
+ sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1);
+ }
+
+ return sHiDPIEnabled;
+}
+
+void
+nsCocoaUtils::GetCommandsFromKeyEvent(NSEvent* aEvent,
+ nsTArray<KeyBindingsCommand>& aCommands)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aEvent);
+
+ static NativeKeyBindingsRecorder* sNativeKeyBindingsRecorder;
+ if (!sNativeKeyBindingsRecorder) {
+ sNativeKeyBindingsRecorder = [NativeKeyBindingsRecorder new];
+ }
+
+ [sNativeKeyBindingsRecorder startRecording:aCommands];
+
+ // This will trigger 0 - N calls to doCommandBySelector: and insertText:
+ [sNativeKeyBindingsRecorder
+ interpretKeyEvents:[NSArray arrayWithObject:aEvent]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@implementation NativeKeyBindingsRecorder
+
+- (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands
+{
+ mCommands = &aCommands;
+ mCommands->Clear();
+}
+
+- (void)doCommandBySelector:(SEL)aSelector
+{
+ KeyBindingsCommand command = {
+ aSelector,
+ nil
+ };
+
+ mCommands->AppendElement(command);
+}
+
+- (void)insertText:(id)aString
+{
+ KeyBindingsCommand command = {
+ @selector(insertText:),
+ aString
+ };
+
+ mCommands->AppendElement(command);
+}
+
+@end // NativeKeyBindingsRecorder
+
+struct KeyConversionData
+{
+ const char* str;
+ size_t strLength;
+ uint32_t geckoKeyCode;
+ uint32_t charCode;
+};
+
+static const KeyConversionData gKeyConversions[] = {
+
+#define KEYCODE_ENTRY(aStr, aCode) \
+ {#aStr, sizeof(#aStr) - 1, NS_##aStr, aCode}
+
+// Some keycodes may have different name in nsIDOMKeyEvent from its key name.
+#define KEYCODE_ENTRY2(aStr, aNSName, aCode) \
+ {#aStr, sizeof(#aStr) - 1, NS_##aNSName, aCode}
+
+ KEYCODE_ENTRY(VK_CANCEL, 0x001B),
+ KEYCODE_ENTRY(VK_DELETE, NSDeleteFunctionKey),
+ KEYCODE_ENTRY(VK_BACK, NSBackspaceCharacter),
+ KEYCODE_ENTRY2(VK_BACK_SPACE, VK_BACK, NSBackspaceCharacter),
+ KEYCODE_ENTRY(VK_TAB, NSTabCharacter),
+ KEYCODE_ENTRY(VK_CLEAR, NSClearLineFunctionKey),
+ KEYCODE_ENTRY(VK_RETURN, NSEnterCharacter),
+ KEYCODE_ENTRY(VK_SHIFT, 0),
+ KEYCODE_ENTRY(VK_CONTROL, 0),
+ KEYCODE_ENTRY(VK_ALT, 0),
+ KEYCODE_ENTRY(VK_PAUSE, NSPauseFunctionKey),
+ KEYCODE_ENTRY(VK_CAPS_LOCK, 0),
+ KEYCODE_ENTRY(VK_ESCAPE, 0),
+ KEYCODE_ENTRY(VK_SPACE, ' '),
+ KEYCODE_ENTRY(VK_PAGE_UP, NSPageUpFunctionKey),
+ KEYCODE_ENTRY(VK_PAGE_DOWN, NSPageDownFunctionKey),
+ KEYCODE_ENTRY(VK_END, NSEndFunctionKey),
+ KEYCODE_ENTRY(VK_HOME, NSHomeFunctionKey),
+ KEYCODE_ENTRY(VK_LEFT, NSLeftArrowFunctionKey),
+ KEYCODE_ENTRY(VK_UP, NSUpArrowFunctionKey),
+ KEYCODE_ENTRY(VK_RIGHT, NSRightArrowFunctionKey),
+ KEYCODE_ENTRY(VK_DOWN, NSDownArrowFunctionKey),
+ KEYCODE_ENTRY(VK_PRINTSCREEN, NSPrintScreenFunctionKey),
+ KEYCODE_ENTRY(VK_INSERT, NSInsertFunctionKey),
+ KEYCODE_ENTRY(VK_HELP, NSHelpFunctionKey),
+ KEYCODE_ENTRY(VK_0, '0'),
+ KEYCODE_ENTRY(VK_1, '1'),
+ KEYCODE_ENTRY(VK_2, '2'),
+ KEYCODE_ENTRY(VK_3, '3'),
+ KEYCODE_ENTRY(VK_4, '4'),
+ KEYCODE_ENTRY(VK_5, '5'),
+ KEYCODE_ENTRY(VK_6, '6'),
+ KEYCODE_ENTRY(VK_7, '7'),
+ KEYCODE_ENTRY(VK_8, '8'),
+ KEYCODE_ENTRY(VK_9, '9'),
+ KEYCODE_ENTRY(VK_SEMICOLON, ':'),
+ KEYCODE_ENTRY(VK_EQUALS, '='),
+ KEYCODE_ENTRY(VK_A, 'A'),
+ KEYCODE_ENTRY(VK_B, 'B'),
+ KEYCODE_ENTRY(VK_C, 'C'),
+ KEYCODE_ENTRY(VK_D, 'D'),
+ KEYCODE_ENTRY(VK_E, 'E'),
+ KEYCODE_ENTRY(VK_F, 'F'),
+ KEYCODE_ENTRY(VK_G, 'G'),
+ KEYCODE_ENTRY(VK_H, 'H'),
+ KEYCODE_ENTRY(VK_I, 'I'),
+ KEYCODE_ENTRY(VK_J, 'J'),
+ KEYCODE_ENTRY(VK_K, 'K'),
+ KEYCODE_ENTRY(VK_L, 'L'),
+ KEYCODE_ENTRY(VK_M, 'M'),
+ KEYCODE_ENTRY(VK_N, 'N'),
+ KEYCODE_ENTRY(VK_O, 'O'),
+ KEYCODE_ENTRY(VK_P, 'P'),
+ KEYCODE_ENTRY(VK_Q, 'Q'),
+ KEYCODE_ENTRY(VK_R, 'R'),
+ KEYCODE_ENTRY(VK_S, 'S'),
+ KEYCODE_ENTRY(VK_T, 'T'),
+ KEYCODE_ENTRY(VK_U, 'U'),
+ KEYCODE_ENTRY(VK_V, 'V'),
+ KEYCODE_ENTRY(VK_W, 'W'),
+ KEYCODE_ENTRY(VK_X, 'X'),
+ KEYCODE_ENTRY(VK_Y, 'Y'),
+ KEYCODE_ENTRY(VK_Z, 'Z'),
+ KEYCODE_ENTRY(VK_CONTEXT_MENU, NSMenuFunctionKey),
+ KEYCODE_ENTRY(VK_NUMPAD0, '0'),
+ KEYCODE_ENTRY(VK_NUMPAD1, '1'),
+ KEYCODE_ENTRY(VK_NUMPAD2, '2'),
+ KEYCODE_ENTRY(VK_NUMPAD3, '3'),
+ KEYCODE_ENTRY(VK_NUMPAD4, '4'),
+ KEYCODE_ENTRY(VK_NUMPAD5, '5'),
+ KEYCODE_ENTRY(VK_NUMPAD6, '6'),
+ KEYCODE_ENTRY(VK_NUMPAD7, '7'),
+ KEYCODE_ENTRY(VK_NUMPAD8, '8'),
+ KEYCODE_ENTRY(VK_NUMPAD9, '9'),
+ KEYCODE_ENTRY(VK_MULTIPLY, '*'),
+ KEYCODE_ENTRY(VK_ADD, '+'),
+ KEYCODE_ENTRY(VK_SEPARATOR, 0),
+ KEYCODE_ENTRY(VK_SUBTRACT, '-'),
+ KEYCODE_ENTRY(VK_DECIMAL, '.'),
+ KEYCODE_ENTRY(VK_DIVIDE, '/'),
+ KEYCODE_ENTRY(VK_F1, NSF1FunctionKey),
+ KEYCODE_ENTRY(VK_F2, NSF2FunctionKey),
+ KEYCODE_ENTRY(VK_F3, NSF3FunctionKey),
+ KEYCODE_ENTRY(VK_F4, NSF4FunctionKey),
+ KEYCODE_ENTRY(VK_F5, NSF5FunctionKey),
+ KEYCODE_ENTRY(VK_F6, NSF6FunctionKey),
+ KEYCODE_ENTRY(VK_F7, NSF7FunctionKey),
+ KEYCODE_ENTRY(VK_F8, NSF8FunctionKey),
+ KEYCODE_ENTRY(VK_F9, NSF9FunctionKey),
+ KEYCODE_ENTRY(VK_F10, NSF10FunctionKey),
+ KEYCODE_ENTRY(VK_F11, NSF11FunctionKey),
+ KEYCODE_ENTRY(VK_F12, NSF12FunctionKey),
+ KEYCODE_ENTRY(VK_F13, NSF13FunctionKey),
+ KEYCODE_ENTRY(VK_F14, NSF14FunctionKey),
+ KEYCODE_ENTRY(VK_F15, NSF15FunctionKey),
+ KEYCODE_ENTRY(VK_F16, NSF16FunctionKey),
+ KEYCODE_ENTRY(VK_F17, NSF17FunctionKey),
+ KEYCODE_ENTRY(VK_F18, NSF18FunctionKey),
+ KEYCODE_ENTRY(VK_F19, NSF19FunctionKey),
+ KEYCODE_ENTRY(VK_F20, NSF20FunctionKey),
+ KEYCODE_ENTRY(VK_F21, NSF21FunctionKey),
+ KEYCODE_ENTRY(VK_F22, NSF22FunctionKey),
+ KEYCODE_ENTRY(VK_F23, NSF23FunctionKey),
+ KEYCODE_ENTRY(VK_F24, NSF24FunctionKey),
+ KEYCODE_ENTRY(VK_NUM_LOCK, NSClearLineFunctionKey),
+ KEYCODE_ENTRY(VK_SCROLL_LOCK, NSScrollLockFunctionKey),
+ KEYCODE_ENTRY(VK_COMMA, ','),
+ KEYCODE_ENTRY(VK_PERIOD, '.'),
+ KEYCODE_ENTRY(VK_SLASH, '/'),
+ KEYCODE_ENTRY(VK_BACK_QUOTE, '`'),
+ KEYCODE_ENTRY(VK_OPEN_BRACKET, '['),
+ KEYCODE_ENTRY(VK_BACK_SLASH, '\\'),
+ KEYCODE_ENTRY(VK_CLOSE_BRACKET, ']'),
+ KEYCODE_ENTRY(VK_QUOTE, '\'')
+
+#undef KEYCODE_ENTRY
+
+};
+
+uint32_t
+nsCocoaUtils::ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName)
+{
+ if (aKeyCodeName.IsEmpty()) {
+ return 0;
+ }
+
+ nsAutoCString keyCodeName;
+ keyCodeName.AssignWithConversion(aKeyCodeName);
+ // We want case-insensitive comparison with data stored as uppercase.
+ ToUpperCase(keyCodeName);
+
+ uint32_t keyCodeNameLength = keyCodeName.Length();
+ const char* keyCodeNameStr = keyCodeName.get();
+ for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
+ if (keyCodeNameLength == gKeyConversions[i].strLength &&
+ nsCRT::strcmp(gKeyConversions[i].str, keyCodeNameStr) == 0) {
+ return gKeyConversions[i].charCode;
+ }
+ }
+
+ return 0;
+}
+
+uint32_t
+nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode)
+{
+ if (!aKeyCode) {
+ return 0;
+ }
+
+ for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
+ if (gKeyConversions[i].geckoKeyCode == aKeyCode) {
+ return gKeyConversions[i].charCode;
+ }
+ }
+
+ return 0;
+}
+
+NSMutableAttributedString*
+nsCocoaUtils::GetNSMutableAttributedString(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRanges,
+ const bool aIsVertical,
+ const CGFloat aBackingScaleFactor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL
+
+ NSString* nsstr = nsCocoaUtils::ToNSString(aText);
+ NSMutableAttributedString* attrStr =
+ [[[NSMutableAttributedString alloc] initWithString:nsstr
+ attributes:nil] autorelease];
+
+ int32_t lastOffset = aText.Length();
+ for (auto i = aFontRanges.Length(); i > 0; --i) {
+ const FontRange& fontRange = aFontRanges[i - 1];
+ NSString* fontName = nsCocoaUtils::ToNSString(fontRange.mFontName);
+ CGFloat fontSize = fontRange.mFontSize / aBackingScaleFactor;
+ NSFont* font = [NSFont fontWithName:fontName size:fontSize];
+ if (!font) {
+ font = [NSFont systemFontOfSize:fontSize];
+ }
+
+ NSDictionary* attrs = @{ NSFontAttributeName: font };
+ NSRange range = NSMakeRange(fontRange.mStartOffset,
+ lastOffset - fontRange.mStartOffset);
+ [attrStr setAttributes:attrs range:range];
+ lastOffset = fontRange.mStartOffset;
+ }
+
+ if (aIsVertical) {
+ [attrStr addAttribute:NSVerticalGlyphFormAttributeName
+ value:[NSNumber numberWithInt: 1]
+ range:NSMakeRange(0, [attrStr length])];
+ }
+
+ return attrStr;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL
+}
diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h
new file mode 100644
index 0000000000..6338f474df
--- /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 0000000000..db120fbddd
--- /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 0000000000..4b3e262188
--- /dev/null
+++ b/widget/cocoa/nsColorPicker.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsColorPicker_h_
+#define nsColorPicker_h_
+
+#include "nsIColorPicker.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsIColorPickerShownCallback;
+class mozIDOMWindowProxy;
+@class NSColorPanelWrapper;
+@class NSColor;
+
+class nsColorPicker final : public nsIColorPicker
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ const nsAString& aInitialColor) override;
+ NS_IMETHOD Open(nsIColorPickerShownCallback* aCallback) override;
+
+ // For NSColorPanelWrapper.
+ void Update(NSColor* aColor);
+ // Call this method if you are done with this input, but the color picker needs to
+ // stay open as it will be associated to another input
+ void DoneWithRetarget();
+ // Same as DoneWithRetarget + clean the static instance of sColorPanelWrapper,
+ // as it is not needed anymore for now
+ void Done();
+
+private:
+ ~nsColorPicker();
+
+ static NSColor* GetNSColorFromHexString(const nsAString& aColor);
+ static void GetHexStringFromNSColor(NSColor* aColor, nsAString& aResult);
+
+ static NSColorPanelWrapper* sColorPanelWrapper;
+
+ nsString mTitle;
+ nsString mColor;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+};
+
+#endif // nsColorPicker_h_
diff --git a/widget/cocoa/nsColorPicker.mm b/widget/cocoa/nsColorPicker.mm
new file mode 100644
index 0000000000..263ea349b0
--- /dev/null
+++ b/widget/cocoa/nsColorPicker.mm
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsColorPicker.h"
+#include "nsCocoaUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+static unsigned int
+HexStrToInt(NSString* str)
+{
+ unsigned int result = 0;
+
+ for (unsigned int i = 0; i < [str length]; ++i) {
+ char c = [str characterAtIndex:i];
+ result *= 16;
+ if (c >= '0' && c <= '9') {
+ result += c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ result += 10 + (c - 'A');
+ } else {
+ result += 10 + (c - 'a');
+ }
+ }
+
+ return result;
+}
+
+@interface NSColorPanelWrapper : NSObject <NSWindowDelegate>
+{
+ NSColorPanel* mColorPanel;
+ nsColorPicker* mColorPicker;
+}
+- (id)initWithPicker:(nsColorPicker*)aPicker;
+- (void)open:(NSColor*)aInitialColor title:(NSString*)aTitle;
+- (void)retarget:(nsColorPicker*)aPicker;
+- (void)colorChanged:(NSColorPanel*)aPanel;
+@end
+
+@implementation NSColorPanelWrapper
+- (id)initWithPicker:(nsColorPicker*)aPicker
+{
+ mColorPicker = aPicker;
+ mColorPanel = [NSColorPanel sharedColorPanel];
+
+ self = [super init];
+ return self;
+}
+
+- (void)open:(NSColor*)aInitialColor title:(NSString*)aTitle
+{
+ [mColorPanel setTarget:self];
+ [mColorPanel setAction:@selector(colorChanged:)];
+ [mColorPanel setDelegate:self];
+ [mColorPanel setTitle:aTitle];
+ [mColorPanel setColor:aInitialColor];
+ [mColorPanel makeKeyAndOrderFront:nil];
+}
+
+- (void)colorChanged:(NSColorPanel*)aPanel
+{
+ mColorPicker->Update([mColorPanel color]);
+}
+
+- (void)windowWillClose:(NSNotification*)aNotification
+{
+ mColorPicker->Done();
+}
+
+- (void)retarget:(nsColorPicker*)aPicker
+{
+ mColorPicker->DoneWithRetarget();
+ mColorPicker = aPicker;
+}
+
+- (void)dealloc
+{
+ [mColorPanel setTarget:nil];
+ [mColorPanel setAction:nil];
+ [mColorPanel setDelegate:nil];
+
+ mColorPanel = nil;
+ mColorPicker = nullptr;
+
+ [super dealloc];
+}
+@end
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+NSColorPanelWrapper* nsColorPicker::sColorPanelWrapper = nullptr;
+
+nsColorPicker::~nsColorPicker()
+{
+}
+
+NS_IMETHODIMP
+nsColorPicker::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ const nsAString& aInitialColor)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Color pickers can only be opened from main thread currently");
+ mTitle = aTitle;
+ mColor = aInitialColor;
+
+ if (sColorPanelWrapper) {
+ // Update current wrapper to target the new input instead
+ [sColorPanelWrapper retarget:this];
+ } else {
+ // Create a brand new color panel wrapper
+ sColorPanelWrapper = [[NSColorPanelWrapper alloc] initWithPicker:this];
+ }
+ return NS_OK;
+}
+
+/* static */ NSColor*
+nsColorPicker::GetNSColorFromHexString(const nsAString& aColor)
+{
+ NSString* str = nsCocoaUtils::ToNSString(aColor);
+
+ double red = HexStrToInt([str substringWithRange:NSMakeRange(1, 2)]) / 255.0;
+ double green = HexStrToInt([str substringWithRange:NSMakeRange(3, 2)]) / 255.0;
+ double blue = HexStrToInt([str substringWithRange:NSMakeRange(5, 2)]) / 255.0;
+
+ return [NSColor colorWithDeviceRed: red green: green blue: blue alpha: 1.0];
+}
+
+/* static */ void
+nsColorPicker::GetHexStringFromNSColor(NSColor* aColor, nsAString& aResult)
+{
+ CGFloat redFloat, greenFloat, blueFloat;
+
+ NSColor* color = aColor;
+ @try {
+ [color getRed:&redFloat green:&greenFloat blue:&blueFloat alpha: nil];
+ } @catch (NSException* e) {
+ color = [color colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
+ [color getRed:&redFloat green:&greenFloat blue:&blueFloat alpha: nil];
+ }
+
+ nsCocoaUtils::GetStringForNSString([NSString stringWithFormat:@"#%02x%02x%02x",
+ (int)(redFloat * 255),
+ (int)(greenFloat * 255),
+ (int)(blueFloat * 255)],
+ aResult);
+}
+
+NS_IMETHODIMP
+nsColorPicker::Open(nsIColorPickerShownCallback* aCallback)
+{
+ MOZ_ASSERT(aCallback);
+ mCallback = aCallback;
+
+ [sColorPanelWrapper open:GetNSColorFromHexString(mColor)
+ title:nsCocoaUtils::ToNSString(mTitle)];
+
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+}
+
+void
+nsColorPicker::Update(NSColor* aColor)
+{
+ GetHexStringFromNSColor(aColor, mColor);
+ mCallback->Update(mColor);
+}
+
+void
+nsColorPicker::DoneWithRetarget()
+{
+ mCallback->Done(EmptyString());
+ mCallback = nullptr;
+ NS_RELEASE_THIS();
+}
+
+void
+nsColorPicker::Done()
+{
+ [sColorPanelWrapper release];
+ sColorPanelWrapper = nullptr;
+ DoneWithRetarget();
+}
diff --git a/widget/cocoa/nsCursorManager.h b/widget/cocoa/nsCursorManager.h
new file mode 100644
index 0000000000..6dba8f9034
--- /dev/null
+++ b/widget/cocoa/nsCursorManager.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCursorManager_h_
+#define nsCursorManager_h_
+
+#import <Foundation/Foundation.h>
+#include "nsIWidget.h"
+#include "nsMacCursor.h"
+
+/*! @class nsCursorManager
+ @abstract Singleton service provides access to all cursors available in the application.
+ @discussion Use <code>nsCusorManager</code> to set the current cursor using an XP <code>nsCusor</code> enum value.
+ <code>nsCursorManager</code> encapsulates the details of setting different types of cursors, animating
+ cursors and cleaning up cursors when they are no longer in use.
+ */
+@interface nsCursorManager : NSObject
+{
+ @private
+ NSMutableDictionary *mCursors;
+ nsMacCursor *mCurrentMacCursor;
+}
+
+/*! @method setCursor:
+ @abstract Sets the current cursor.
+ @discussion Sets the current cursor to the cursor indicated by the XP cursor constant given as an argument.
+ Resources associated with the previous cursor are cleaned up.
+ @param aCursor the cursor to use
+*/
+- (nsresult) setCursor: (nsCursor) aCursor;
+
+/*! @method setCursorWithImage:hotSpotX:hotSpotY:
+ @abstract Sets the current cursor to a custom image
+ @discussion Sets the current cursor to the cursor given by the aCursorImage argument.
+ Resources associated with the previous cursor are cleaned up.
+ @param aCursorImage the cursor image to use
+ @param aHotSpotX the x coordinate of the cursor's hotspot
+ @param aHotSpotY the y coordinate of the cursor's hotspot
+ @param scaleFactor the scale factor of the target display (2 for a retina display)
+ */
+- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY scaleFactor: (CGFloat) scaleFactor;
+
+
+/*! @method sharedInstance
+ @abstract Get the Singleton instance of the cursor manager.
+ @discussion Use this method to obtain a reference to the cursor manager.
+ @result a reference to the cursor manager
+*/
++ (nsCursorManager *) sharedInstance;
+
+/*! @method dispose
+ @abstract Releases the shared instance of the cursor manager.
+ @discussion Use dispose to clean up the cursor manager and associated cursors.
+*/
++ (void) dispose;
+@end
+
+@interface NSCursor (Undocumented)
+// busyButClickableCursor is an undocumented NSCursor API, but has been in use since
+// at least OS X 10.4 and through 10.9.
++ (NSCursor*)busyButClickableCursor;
+@end
+
+#endif // nsCursorManager_h_
diff --git a/widget/cocoa/nsCursorManager.mm b/widget/cocoa/nsCursorManager.mm
new file mode 100644
index 0000000000..c4281a438a
--- /dev/null
+++ b/widget/cocoa/nsCursorManager.mm
@@ -0,0 +1,308 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+#include "nsCursorManager.h"
+#include "nsObjCExceptions.h"
+#include <math.h>
+
+static nsCursorManager *gInstance;
+static CGFloat sCursorScaleFactor = 0.0f;
+static imgIContainer *sCursorImgContainer = nullptr;
+static const nsCursor sCustomCursor = eCursorCount;
+
+/*! @category nsCursorManager(PrivateMethods)
+ Private methods for the cursor manager class.
+*/
+@interface nsCursorManager(PrivateMethods)
+/*! @method getCursor:
+ @abstract Get a reference to the native Mac representation of a cursor.
+ @discussion Gets a reference to the Mac native implementation of a cursor.
+ If the cursor has been requested before, it is retreived from the cursor cache,
+ otherwise it is created and cached.
+ @param aCursor the cursor to get
+ @result the Mac native implementation of the cursor
+*/
+- (nsMacCursor *) getCursor: (nsCursor) aCursor;
+
+/*! @method setMacCursor:
+ @abstract Set the current Mac native cursor
+ @discussion Sets the current cursor - this routine is what actually causes the cursor to change.
+ The argument is retained and the old cursor is released.
+ @param aMacCursor the cursor to set
+ @result NS_OK
+ */
+- (nsresult) setMacCursor: (nsMacCursor*) aMacCursor;
+
+/*! @method createCursor:
+ @abstract Create a Mac native representation of a cursor.
+ @discussion Creates a version of the Mac native representation of this cursor
+ @param aCursor the cursor to create
+ @result the Mac native implementation of the cursor
+*/
++ (nsMacCursor *) createCursor: (enum nsCursor) aCursor;
+
+@end
+
+@implementation nsCursorManager
+
++ (nsCursorManager *) sharedInstance
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!gInstance) {
+ gInstance = [[nsCursorManager alloc] init];
+ }
+ return gInstance;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (void) dispose
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [gInstance release];
+ gInstance = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
++ (nsMacCursor *) createCursor: (enum nsCursor) aCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ switch(aCursor)
+ {
+ SEL cursorSelector;
+ case eCursor_standard:
+ return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
+ case eCursor_wait:
+ case eCursor_spinning:
+ {
+ return [nsMacCursor cursorWithCursor:[NSCursor busyButClickableCursor] type:aCursor];
+ }
+ case eCursor_select:
+ return [nsMacCursor cursorWithCursor:[NSCursor IBeamCursor] type:aCursor];
+ case eCursor_hyperlink:
+ return [nsMacCursor cursorWithCursor:[NSCursor pointingHandCursor] type:aCursor];
+ case eCursor_crosshair:
+ return [nsMacCursor cursorWithCursor:[NSCursor crosshairCursor] type:aCursor];
+ case eCursor_move:
+ return [nsMacCursor cursorWithImageNamed:@"move" hotSpot:NSMakePoint(12,12) type:aCursor];
+ case eCursor_help:
+ return [nsMacCursor cursorWithImageNamed:@"help" hotSpot:NSMakePoint(12,12) type:aCursor];
+ case eCursor_copy:
+ cursorSelector = @selector(dragCopyCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ case eCursor_alias:
+ cursorSelector = @selector(dragLinkCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ case eCursor_context_menu:
+ cursorSelector = @selector(contextualMenuCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ case eCursor_cell:
+ return [nsMacCursor cursorWithImageNamed:@"cell" hotSpot:NSMakePoint(12,12) type:aCursor];
+ case eCursor_grab:
+ return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
+ case eCursor_grabbing:
+ return [nsMacCursor cursorWithCursor:[NSCursor closedHandCursor] type:aCursor];
+ case eCursor_zoom_in:
+ return [nsMacCursor cursorWithImageNamed:@"zoomIn" hotSpot:NSMakePoint(10,10) type:aCursor];
+ case eCursor_zoom_out:
+ return [nsMacCursor cursorWithImageNamed:@"zoomOut" hotSpot:NSMakePoint(10,10) type:aCursor];
+ case eCursor_vertical_text:
+ return [nsMacCursor cursorWithImageNamed:@"vtIBeam" hotSpot:NSMakePoint(12,11) type:aCursor];
+ case eCursor_all_scroll:
+ return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
+ case eCursor_not_allowed:
+ case eCursor_no_drop:
+ cursorSelector = @selector(operationNotAllowedCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ // Resize Cursors:
+ // North
+ case eCursor_n_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeUpCursor] type:aCursor];
+ // North East
+ case eCursor_ne_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNE" hotSpot:NSMakePoint(12,11) type:aCursor];
+ // East
+ case eCursor_e_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeRightCursor] type:aCursor];
+ // South East
+ case eCursor_se_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeSE" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // South
+ case eCursor_s_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeDownCursor] type:aCursor];
+ // South West
+ case eCursor_sw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeSW" hotSpot:NSMakePoint(10,12) type:aCursor];
+ // West
+ case eCursor_w_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftCursor] type:aCursor];
+ // North West
+ case eCursor_nw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNW" hotSpot:NSMakePoint(11,11) type:aCursor];
+ // North & South
+ case eCursor_ns_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeUpDownCursor] type:aCursor];
+ // East & West
+ case eCursor_ew_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftRightCursor] type:aCursor];
+ // North East & South West
+ case eCursor_nesw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNESW" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // North West & South East
+ case eCursor_nwse_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNWSE" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // Column Resize
+ case eCursor_col_resize:
+ return [nsMacCursor cursorWithImageNamed:@"colResize" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // Row Resize
+ case eCursor_row_resize:
+ return [nsMacCursor cursorWithImageNamed:@"rowResize" hotSpot:NSMakePoint(12,12) type:aCursor];
+ default:
+ return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id) init
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mCursors = [[NSMutableDictionary alloc] initWithCapacity:25];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (nsresult) setCursor: (enum nsCursor) aCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Some plugins mess with our cursors and set a cursor that even
+ // [NSCursor currentCursor] doesn't know about. In case that happens, just
+ // reset the state.
+ [[NSCursor currentCursor] set];
+
+ nsCursor oldType = [mCurrentMacCursor type];
+ if (oldType != aCursor) {
+ if (aCursor == eCursor_none) {
+ [NSCursor hide];
+ } else if (oldType == eCursor_none) {
+ [NSCursor unhide];
+ }
+ }
+ [self setMacCursor:[self getCursor:aCursor]];
+
+ // if a custom cursor was previously set, release sCursorImgContainer
+ if (oldType == sCustomCursor) {
+ NS_IF_RELEASE(sCursorImgContainer);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+- (nsresult) setMacCursor: (nsMacCursor*) aMacCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mCurrentMacCursor != aMacCursor || ![mCurrentMacCursor isSet]) {
+ [aMacCursor retain];
+ [mCurrentMacCursor unset];
+ [aMacCursor set];
+ [mCurrentMacCursor release];
+ mCurrentMacCursor = aMacCursor;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY scaleFactor: (CGFloat) scaleFactor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+ // As the user moves the mouse, this gets called repeatedly with the same aCursorImage
+ if (sCursorImgContainer == aCursorImage && sCursorScaleFactor == scaleFactor && mCurrentMacCursor) {
+ [self setMacCursor:mCurrentMacCursor];
+ return NS_OK;
+ }
+
+ [[NSCursor currentCursor] set];
+ int32_t width = 0, height = 0;
+ aCursorImage->GetWidth(&width);
+ aCursorImage->GetHeight(&height);
+ // prevent DoS attacks
+ if (width > 128 || height > 128) {
+ return NS_OK;
+ }
+
+ NSImage *cursorImage;
+ nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(aCursorImage, imgIContainer::FRAME_FIRST, &cursorImage, scaleFactor);
+ if (NS_FAILED(rv) || !cursorImage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // if the hotspot is nonsensical, make it 0,0
+ aHotspotX = (aHotspotX > (uint32_t)width - 1) ? 0 : aHotspotX;
+ aHotspotY = (aHotspotY > (uint32_t)height - 1) ? 0 : aHotspotY;
+
+ NSPoint hotSpot = ::NSMakePoint(aHotspotX, aHotspotY);
+ [self setMacCursor:[nsMacCursor cursorWithCursor:[[NSCursor alloc] initWithImage:cursorImage hotSpot:hotSpot] type:sCustomCursor]];
+ [cursorImage release];
+
+ NS_IF_RELEASE(sCursorImgContainer);
+ sCursorImgContainer = aCursorImage;
+ sCursorScaleFactor = scaleFactor;
+ NS_ADDREF(sCursorImgContainer);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+- (nsMacCursor *) getCursor: (enum nsCursor) aCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsMacCursor * result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]];
+ if (!result) {
+ result = [nsCursorManager createCursor:aCursor];
+ [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]];
+ }
+ return result;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void) dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mCurrentMacCursor unset];
+ [mCurrentMacCursor release];
+ [mCursors release];
+ NS_IF_RELEASE(sCursorImgContainer);
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/nsDeviceContextSpecX.h b/widget/cocoa/nsDeviceContextSpecX.h
new file mode 100644
index 0000000000..2df52418a7
--- /dev/null
+++ b/widget/cocoa/nsDeviceContextSpecX.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDeviceContextSpecX_h_
+#define nsDeviceContextSpecX_h_
+
+#include "nsIDeviceContextSpec.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+class nsDeviceContextSpecX : public nsIDeviceContextSpec
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsDeviceContextSpecX();
+
+ NS_IMETHOD Init(nsIWidget *aWidget, nsIPrintSettings* aPS, bool aIsPrintPreview) override;
+ virtual already_AddRefed<PrintTarget> MakePrintTarget() final;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage) override;
+ NS_IMETHOD EndDocument() override;
+ NS_IMETHOD BeginPage() override;
+ NS_IMETHOD EndPage() override;
+
+ void GetPaperRect(double* aTop, double* aLeft, double* aBottom, double* aRight);
+
+protected:
+ virtual ~nsDeviceContextSpecX();
+
+protected:
+ PMPrintSession mPrintSession; // printing context.
+ PMPageFormat mPageFormat; // page format.
+ PMPrintSettings mPrintSettings; // print settings.
+};
+
+#endif //nsDeviceContextSpecX_h_
diff --git a/widget/cocoa/nsDeviceContextSpecX.mm b/widget/cocoa/nsDeviceContextSpecX.mm
new file mode 100644
index 0000000000..d252adef69
--- /dev/null
+++ b/widget/cocoa/nsDeviceContextSpecX.mm
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeviceContextSpecX.h"
+
+#include "mozilla/gfx/PrintTargetCG.h"
+#include "mozilla/RefPtr.h"
+#include "nsCRT.h"
+#include <unistd.h>
+
+#include "nsQueryObject.h"
+#include "nsIServiceManager.h"
+#include "nsPrintSettingsX.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+nsDeviceContextSpecX::nsDeviceContextSpecX()
+: mPrintSession(NULL)
+, mPageFormat(kPMNoPageFormat)
+, mPrintSettings(kPMNoPrintSettings)
+{
+}
+
+nsDeviceContextSpecX::~nsDeviceContextSpecX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mPrintSession)
+ ::PMRelease(mPrintSession);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecX, nsIDeviceContextSpec)
+
+NS_IMETHODIMP nsDeviceContextSpecX::Init(nsIWidget *aWidget,
+ nsIPrintSettings* aPS,
+ bool aIsPrintPreview)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ RefPtr<nsPrintSettingsX> settings(do_QueryObject(aPS));
+ if (!settings)
+ return NS_ERROR_NO_INTERFACE;
+
+ mPrintSession = settings->GetPMPrintSession();
+ ::PMRetain(mPrintSession);
+ mPageFormat = settings->GetPMPageFormat();
+ mPrintSettings = settings->GetPMPrintSettings();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aTitle.IsEmpty()) {
+ CFStringRef cfString =
+ ::CFStringCreateWithCharacters(NULL, reinterpret_cast<const UniChar*>(aTitle.BeginReading()),
+ aTitle.Length());
+ if (cfString) {
+ ::PMPrintSettingsSetJobName(mPrintSettings, cfString);
+ ::CFRelease(cfString);
+ }
+ }
+
+ OSStatus status;
+ status = ::PMSetFirstPage(mPrintSettings, aStartPage, false);
+ NS_ASSERTION(status == noErr, "PMSetFirstPage failed");
+ status = ::PMSetLastPage(mPrintSettings, aEndPage, false);
+ NS_ASSERTION(status == noErr, "PMSetLastPage failed");
+
+ status = ::PMSessionBeginCGDocumentNoDialog(mPrintSession, mPrintSettings, mPageFormat);
+ if (status != noErr)
+ return NS_ERROR_ABORT;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::EndDocument()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ ::PMSessionEndDocumentNoDialog(mPrintSession);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::BeginPage()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMSessionError(mPrintSession);
+ OSStatus status = ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, NULL);
+ if (status != noErr) return NS_ERROR_ABORT;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::EndPage()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ OSStatus status = ::PMSessionEndPageNoDialog(mPrintSession);
+ if (status != noErr) return NS_ERROR_ABORT;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsDeviceContextSpecX::GetPaperRect(double* aTop, double* aLeft, double* aBottom, double* aRight)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ PMRect paperRect;
+ ::PMGetAdjustedPaperRect(mPageFormat, &paperRect);
+
+ *aTop = paperRect.top, *aLeft = paperRect.left;
+ *aBottom = paperRect.bottom, *aRight = paperRect.right;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecX::MakePrintTarget()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ double top, left, bottom, right;
+ GetPaperRect(&top, &left, &bottom, &right);
+ const double width = right - left;
+ const double height = bottom - top;
+ IntSize size = IntSize::Floor(width, height);
+
+ CGContextRef context;
+ ::PMSessionGetCGGraphicsContext(mPrintSession, &context);
+
+ if (context) {
+ // Initially, origin is at bottom-left corner of the paper.
+ // Here, we translate it to top-left corner of the paper.
+ CGContextTranslateCTM(context, 0, height);
+ CGContextScaleCTM(context, 1.0, -1.0);
+ return PrintTargetCG::CreateOrNull(context, size);
+ }
+
+ // Apparently we do need this branch - bug 368933.
+ return PrintTargetCG::CreateOrNull(size, SurfaceFormat::A8R8G8B8_UINT32);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
diff --git a/widget/cocoa/nsDragService.h b/widget/cocoa/nsDragService.h
new file mode 100644
index 0000000000..ea6702812a
--- /dev/null
+++ b/widget/cocoa/nsDragService.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDragService_h_
+#define nsDragService_h_
+
+#include "nsBaseDragService.h"
+
+#include <Cocoa/Cocoa.h>
+
+extern NSString* const kWildcardPboardType;
+extern NSString* const kCorePboardType_url;
+extern NSString* const kCorePboardType_urld;
+extern NSString* const kCorePboardType_urln;
+extern NSString* const kCustomTypesPboardType;
+
+class nsDragService : public nsBaseDragService
+{
+public:
+ nsDragService();
+
+ // nsBaseDragService
+ virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType);
+ // nsIDragService
+ NS_IMETHOD EndDragSession(bool aDoneDrag);
+
+ // nsIDragSession
+ NS_IMETHOD GetData(nsITransferable * aTransferable, uint32_t aItemIndex);
+ NS_IMETHOD IsDataFlavorSupported(const char *aDataFlavor, bool *_retval);
+ NS_IMETHOD GetNumDropItems(uint32_t * aNumItems);
+
+protected:
+ virtual ~nsDragService();
+
+private:
+
+ NSImage* ConstructDragImage(nsIDOMNode* aDOMNode,
+ mozilla::LayoutDeviceIntRect* aDragRect,
+ nsIScriptableRegion* aRegion);
+ bool IsValidType(NSString* availableType, bool allowFileURL);
+ NSString* GetStringForType(NSPasteboardItem* item, const NSString* type,
+ bool allowFileURL = false);
+ NSString* GetTitleForURL(NSPasteboardItem* item);
+ NSString* GetFilePath(NSPasteboardItem* item);
+
+ nsCOMPtr<nsIArray> mDataItems; // only valid for a drag started within gecko
+ NSView* mNativeDragView;
+ NSEvent* mNativeDragEvent;
+};
+
+#endif // nsDragService_h_
diff --git a/widget/cocoa/nsDragService.mm b/widget/cocoa/nsDragService.mm
new file mode 100644
index 0000000000..b92db1b2a9
--- /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 0000000000..1aeb22cc18
--- /dev/null
+++ b/widget/cocoa/nsFilePicker.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFilePicker_h_
+#define nsFilePicker_h_
+
+#include "nsBaseFilePicker.h"
+#include "nsString.h"
+#include "nsIFileChannel.h"
+#include "nsIFile.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+
+class nsILocalFileMac;
+@class NSArray;
+
+class nsFilePicker : public nsBaseFilePicker
+{
+public:
+ nsFilePicker();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIFilePicker (less what's in nsBaseFilePicker)
+ NS_IMETHOD GetDefaultString(nsAString& aDefaultString) override;
+ NS_IMETHOD SetDefaultString(const nsAString& aDefaultString) override;
+ NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension) override;
+ NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension) override;
+ NS_IMETHOD GetFile(nsIFile * *aFile) override;
+ NS_IMETHOD GetFileURL(nsIURI * *aFileURL) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles) override;
+ NS_IMETHOD Show(int16_t *_retval) override;
+ NS_IMETHOD AppendFilter(const nsAString& aTitle, const nsAString& aFilter) override;
+
+ /**
+ * Returns the current filter list in the format used by Cocoa's NSSavePanel
+ * and NSOpenPanel.
+ * Returns nil if no filter currently apply.
+ */
+ NSArray* GetFilterList();
+
+protected:
+ virtual ~nsFilePicker();
+
+ virtual void InitNative(nsIWidget *aParent, const nsAString& aTitle) override;
+
+ // actual implementations of get/put dialogs using NSOpenPanel & NSSavePanel
+ // aFile is an existing but unspecified file. These functions must specify it.
+ //
+ // will return |returnCancel| or |returnOK| as result.
+ int16_t GetLocalFiles(const nsString& inTitle, bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles);
+ int16_t GetLocalFolder(const nsString& inTitle, nsIFile** outFile);
+ int16_t PutLocalFile(const nsString& inTitle, const nsString& inDefaultName, nsIFile** outFile);
+
+ void SetDialogTitle(const nsString& inTitle, id aDialog);
+ NSString *PanelDefaultDirectory();
+ NSView* GetAccessoryView();
+
+ nsString mTitle;
+ nsCOMArray<nsIFile> mFiles;
+ nsString mDefault;
+
+ nsTArray<nsString> mFilters;
+ nsTArray<nsString> mTitles;
+
+ int32_t mSelectedTypeIndex;
+};
+
+#endif // nsFilePicker_h_
diff --git a/widget/cocoa/nsFilePicker.mm b/widget/cocoa/nsFilePicker.mm
new file mode 100644
index 0000000000..5213dee241
--- /dev/null
+++ b/widget/cocoa/nsFilePicker.mm
@@ -0,0 +1,676 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsFilePicker.h"
+#include "nsCOMPtr.h"
+#include "nsReadableUtils.h"
+#include "nsNetUtil.h"
+#include "nsIComponentManager.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsIURL.h"
+#include "nsArrayEnumerator.h"
+#include "nsIStringBundle.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/Preferences.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+const float kAccessoryViewPadding = 5;
+const int kSaveTypeControlTag = 1;
+
+static bool gCallSecretHiddenFileAPI = false;
+const char kShowHiddenFilesPref[] = "filepicker.showHiddenFiles";
+
+/**
+ * This class is an observer of NSPopUpButton selection change.
+ */
+@interface NSPopUpButtonObserver : NSObject
+{
+ NSPopUpButton* mPopUpButton;
+ NSOpenPanel* mOpenPanel;
+ nsFilePicker* mFilePicker;
+}
+- (void) setPopUpButton:(NSPopUpButton*)aPopUpButton;
+- (void) setOpenPanel:(NSOpenPanel*)aOpenPanel;
+- (void) setFilePicker:(nsFilePicker*)aFilePicker;
+- (void) menuChangedItem:(NSNotification*)aSender;
+@end
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+// We never want to call the secret show hidden files API unless the pref
+// has been set. Once the pref has been set we always need to call it even
+// if it disappears so that we stop showing hidden files if a user deletes
+// the pref. If the secret API was used once and things worked out it should
+// continue working for subsequent calls so the user is at no more risk.
+static void SetShowHiddenFileState(NSSavePanel* panel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ bool show = false;
+ if (NS_SUCCEEDED(Preferences::GetBool(kShowHiddenFilesPref, &show))) {
+ gCallSecretHiddenFileAPI = true;
+ }
+
+ if (gCallSecretHiddenFileAPI) {
+ // invoke a method to get a Cocoa-internal nav view
+ SEL navViewSelector = @selector(_navView);
+ NSMethodSignature* navViewSignature = [panel methodSignatureForSelector:navViewSelector];
+ if (!navViewSignature)
+ return;
+ NSInvocation* navViewInvocation = [NSInvocation invocationWithMethodSignature:navViewSignature];
+ [navViewInvocation setSelector:navViewSelector];
+ [navViewInvocation setTarget:panel];
+ [navViewInvocation invoke];
+
+ // get the returned nav view
+ id navView = nil;
+ [navViewInvocation getReturnValue:&navView];
+
+ // invoke the secret show hidden file state method on the nav view
+ SEL showHiddenFilesSelector = @selector(setShowsHiddenFiles:);
+ NSMethodSignature* showHiddenFilesSignature = [navView methodSignatureForSelector:showHiddenFilesSelector];
+ if (!showHiddenFilesSignature)
+ return;
+ NSInvocation* showHiddenFilesInvocation = [NSInvocation invocationWithMethodSignature:showHiddenFilesSignature];
+ [showHiddenFilesInvocation setSelector:showHiddenFilesSelector];
+ [showHiddenFilesInvocation setTarget:navView];
+ [showHiddenFilesInvocation setArgument:&show atIndex:2];
+ [showHiddenFilesInvocation invoke];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsFilePicker::nsFilePicker()
+: mSelectedTypeIndex(0)
+{
+}
+
+nsFilePicker::~nsFilePicker()
+{
+}
+
+void
+nsFilePicker::InitNative(nsIWidget *aParent, const nsAString& aTitle)
+{
+ mTitle = aTitle;
+}
+
+NSView* nsFilePicker::GetAccessoryView()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSView* accessoryView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)] autorelease];
+
+ // Set a label's default value.
+ NSString* label = @"Format:";
+
+ // Try to get the localized string.
+ nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = sbs->CreateBundle("chrome://global/locale/filepicker.properties", getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsXPIDLString locaLabel;
+ bundle->GetStringFromName(u"formatLabel", getter_Copies(locaLabel));
+ if (locaLabel) {
+ label = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(locaLabel.get())
+ length:locaLabel.Length()];
+ }
+ }
+
+ // set up label text field
+ NSTextField* textField = [[[NSTextField alloc] init] autorelease];
+ [textField setEditable:NO];
+ [textField setSelectable:NO];
+ [textField setDrawsBackground:NO];
+ [textField setBezeled:NO];
+ [textField setBordered:NO];
+ [textField setFont:[NSFont labelFontOfSize:13.0]];
+ [textField setStringValue:label];
+ [textField setTag:0];
+ [textField sizeToFit];
+
+ // set up popup button
+ NSPopUpButton* popupButton = [[[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 0, 0) pullsDown:NO] autorelease];
+ uint32_t numMenuItems = mTitles.Length();
+ for (uint32_t i = 0; i < numMenuItems; i++) {
+ const nsString& currentTitle = mTitles[i];
+ NSString *titleString;
+ if (currentTitle.IsEmpty()) {
+ const nsString& currentFilter = mFilters[i];
+ titleString = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentFilter.get())
+ length:currentFilter.Length()];
+ }
+ else {
+ titleString = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentTitle.get())
+ length:currentTitle.Length()];
+ }
+ [popupButton addItemWithTitle:titleString];
+ [titleString release];
+ }
+ if (mSelectedTypeIndex >= 0 && (uint32_t)mSelectedTypeIndex < numMenuItems)
+ [popupButton selectItemAtIndex:mSelectedTypeIndex];
+ [popupButton setTag:kSaveTypeControlTag];
+ [popupButton sizeToFit]; // we have to do sizeToFit to get the height calculated for us
+ // This is just a default width that works well, doesn't truncate the vast majority of
+ // things that might end up in the menu.
+ [popupButton setFrameSize:NSMakeSize(180, [popupButton frame].size.height)];
+
+ // position everything based on control sizes with kAccessoryViewPadding pix padding
+ // on each side kAccessoryViewPadding pix horizontal padding between controls
+ float greatestHeight = [textField frame].size.height;
+ if ([popupButton frame].size.height > greatestHeight)
+ greatestHeight = [popupButton frame].size.height;
+ float totalViewHeight = greatestHeight + kAccessoryViewPadding * 2;
+ float totalViewWidth = [textField frame].size.width + [popupButton frame].size.width + kAccessoryViewPadding * 3;
+ [accessoryView setFrameSize:NSMakeSize(totalViewWidth, totalViewHeight)];
+
+ float textFieldOriginY = ((greatestHeight - [textField frame].size.height) / 2 + 1) + kAccessoryViewPadding;
+ [textField setFrameOrigin:NSMakePoint(kAccessoryViewPadding, textFieldOriginY)];
+
+ float popupOriginX = [textField frame].size.width + kAccessoryViewPadding * 2;
+ float popupOriginY = ((greatestHeight - [popupButton frame].size.height) / 2) + kAccessoryViewPadding;
+ [popupButton setFrameOrigin:NSMakePoint(popupOriginX, popupOriginY)];
+
+ [accessoryView addSubview:textField];
+ [accessoryView addSubview:popupButton];
+ return accessoryView;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Display the file dialog
+NS_IMETHODIMP nsFilePicker::Show(int16_t *retval)
+{
+ NS_ENSURE_ARG_POINTER(retval);
+
+ *retval = returnCancel;
+
+ int16_t userClicksOK = returnCancel;
+
+// Random questions from DHH:
+//
+// Why do we pass mTitle, mDefault to the functions? Can GetLocalFile. PutLocalFile,
+// and GetLocalFolder get called someplace else? It generates a bunch of warnings
+// as it is right now.
+//
+// I think we could easily combine GetLocalFile and GetLocalFolder together, just
+// setting panel pick options based on mMode. I didn't do it here b/c I wanted to
+// make this look as much like Carbon nsFilePicker as possible.
+
+ mFiles.Clear();
+ nsCOMPtr<nsIFile> theFile;
+
+ switch (mMode)
+ {
+ case modeOpen:
+ userClicksOK = GetLocalFiles(mTitle, false, mFiles);
+ break;
+
+ case modeOpenMultiple:
+ userClicksOK = GetLocalFiles(mTitle, true, mFiles);
+ break;
+
+ case modeSave:
+ userClicksOK = PutLocalFile(mTitle, mDefault, getter_AddRefs(theFile));
+ break;
+
+ case modeGetFolder:
+ userClicksOK = GetLocalFolder(mTitle, getter_AddRefs(theFile));
+ break;
+
+ default:
+ NS_ERROR("Unknown file picker mode");
+ break;
+ }
+
+ if (theFile)
+ mFiles.AppendObject(theFile);
+
+ *retval = userClicksOK;
+ return NS_OK;
+}
+
+static
+void UpdatePanelFileTypes(NSOpenPanel* aPanel, NSArray* aFilters)
+{
+ // If we show all file types, also "expose" bundles' contents.
+ [aPanel setTreatsFilePackagesAsDirectories:!aFilters];
+
+ [aPanel setAllowedFileTypes:aFilters];
+}
+
+@implementation NSPopUpButtonObserver
+- (void) setPopUpButton:(NSPopUpButton*)aPopUpButton
+{
+ mPopUpButton = aPopUpButton;
+}
+
+- (void) setOpenPanel:(NSOpenPanel*)aOpenPanel
+{
+ mOpenPanel = aOpenPanel;
+}
+
+- (void) setFilePicker:(nsFilePicker*)aFilePicker
+{
+ mFilePicker = aFilePicker;
+}
+
+- (void) menuChangedItem:(NSNotification *)aSender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+ int32_t selectedItem = [mPopUpButton indexOfSelectedItem];
+ if (selectedItem < 0) {
+ return;
+ }
+
+ mFilePicker->SetFilterIndex(selectedItem);
+ UpdatePanelFileTypes(mOpenPanel, mFilePicker->GetFilterList());
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN();
+}
+@end
+
+// Use OpenPanel to do a GetFile. Returns |returnOK| if the user presses OK in the dialog.
+int16_t
+nsFilePicker::GetLocalFiles(const nsString& inTitle, bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ int16_t retVal = (int16_t)returnCancel;
+ NSOpenPanel *thePanel = [NSOpenPanel openPanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ // Set the options for how the get file dialog will appear
+ SetDialogTitle(inTitle, thePanel);
+ [thePanel setAllowsMultipleSelection:inAllowMultiple];
+ [thePanel setCanSelectHiddenExtension:YES];
+ [thePanel setCanChooseDirectories:NO];
+ [thePanel setCanChooseFiles:YES];
+ [thePanel setResolvesAliases:YES]; //this is default - probably doesn't need to be set
+
+ // Get filters
+ // filters may be null, if we should allow all file types.
+ NSArray *filters = GetFilterList();
+
+ // set up default directory
+ NSString *theDir = PanelDefaultDirectory();
+
+ // if this is the "Choose application..." dialog, and no other start
+ // dir has been set, then use the Applications folder.
+ if (!theDir) {
+ if (filters && [filters count] == 1 &&
+ [(NSString *)[filters objectAtIndex:0] isEqualToString:@"app"])
+ theDir = @"/Applications/";
+ else
+ theDir = @"";
+ }
+
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+
+ int result;
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ if (mFilters.Length() > 1) {
+ // [NSURL initWithString:] (below) throws an exception if URLString is nil.
+
+ NSPopUpButtonObserver* observer = [[NSPopUpButtonObserver alloc] init];
+
+ NSView* accessoryView = GetAccessoryView();
+ [thePanel setAccessoryView:accessoryView];
+
+ [observer setPopUpButton:[accessoryView viewWithTag:kSaveTypeControlTag]];
+ [observer setOpenPanel:thePanel];
+ [observer setFilePicker:this];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:observer
+ selector:@selector(menuChangedItem:)
+ name:NSMenuWillSendActionNotification object:nil];
+
+ UpdatePanelFileTypes(thePanel, filters);
+ result = [thePanel runModal];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:observer];
+ [observer release];
+ } else {
+ // If we show all file types, also "expose" bundles' contents.
+ if (!filters) {
+ [thePanel setTreatsFilePackagesAsDirectories:YES];
+ }
+ [thePanel setAllowedFileTypes:filters];
+ result = [thePanel runModal];
+ }
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ if (result == NSFileHandlingPanelCancelButton)
+ return retVal;
+
+ // Converts data from a NSArray of NSURL to the returned format.
+ // We should be careful to not call [thePanel URLs] more than once given that
+ // it creates a new array each time.
+ // We are using Fast Enumeration, thus the NSURL array is created once then
+ // iterated.
+ for (NSURL* url in [thePanel URLs]) {
+ if (!url) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)url))) {
+ outFiles.AppendObject(localFile);
+ }
+ }
+
+ if (outFiles.Count() > 0)
+ retVal = returnOK;
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+// Use OpenPanel to do a GetFolder. Returns |returnOK| if the user presses OK in the dialog.
+int16_t
+nsFilePicker::GetLocalFolder(const nsString& inTitle, nsIFile** outFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+ NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
+
+ int16_t retVal = (int16_t)returnCancel;
+ NSOpenPanel *thePanel = [NSOpenPanel openPanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ // Set the options for how the get file dialog will appear
+ SetDialogTitle(inTitle, thePanel);
+ [thePanel setAllowsMultipleSelection:NO]; //this is default -probably doesn't need to be set
+ [thePanel setCanSelectHiddenExtension:YES];
+ [thePanel setCanChooseDirectories:YES];
+ [thePanel setCanChooseFiles:NO];
+ [thePanel setResolvesAliases:YES]; //this is default - probably doesn't need to be set
+ [thePanel setCanCreateDirectories:YES];
+
+ // packages != folders
+ [thePanel setTreatsFilePackagesAsDirectories:NO];
+
+ // set up default directory
+ NSString *theDir = PanelDefaultDirectory();
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int result = [thePanel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ if (result == NSFileHandlingPanelCancelButton)
+ return retVal;
+
+ // get the path for the folder (we allow just 1, so that's all we get)
+ NSURL *theURL = [[thePanel URLs] objectAtIndex:0];
+ if (theURL) {
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)theURL))) {
+ *outFile = localFile;
+ NS_ADDREF(*outFile);
+ retVal = returnOK;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+// Returns |returnOK| if the user presses OK in the dialog.
+int16_t
+nsFilePicker::PutLocalFile(const nsString& inTitle, const nsString& inDefaultName, nsIFile** outFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+ NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
+
+ int16_t retVal = returnCancel;
+ NSSavePanel *thePanel = [NSSavePanel savePanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ SetDialogTitle(inTitle, thePanel);
+
+ // set up accessory view for file format options
+ NSView* accessoryView = GetAccessoryView();
+ [thePanel setAccessoryView:accessoryView];
+
+ // set up default file name
+ NSString* defaultFilename = [NSString stringWithCharacters:(const unichar*)inDefaultName.get() length:inDefaultName.Length()];
+
+ // set up allowed types; this prevents the extension from being selected
+ // use the UTI for the file type to allow alternate extensions (e.g., jpg vs. jpeg)
+ NSString* extension = defaultFilename.pathExtension;
+ if (extension.length != 0) {
+ CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, NULL);
+
+ if (type) {
+ thePanel.allowedFileTypes = @[(NSString*)type];
+ CFRelease(type);
+ } else {
+ // if there's no UTI for the file extension, use the extension itself.
+ thePanel.allowedFileTypes = @[extension];
+ }
+ }
+ // Allow users to change the extension.
+ thePanel.allowsOtherFileTypes = YES;
+
+ // set up default directory
+ NSString *theDir = PanelDefaultDirectory();
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+
+ // load the panel
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ [thePanel setNameFieldStringValue:defaultFilename];
+ int result = [thePanel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+ if (result == NSFileHandlingPanelCancelButton)
+ return retVal;
+
+ // get the save type
+ NSPopUpButton* popupButton = [accessoryView viewWithTag:kSaveTypeControlTag];
+ if (popupButton) {
+ mSelectedTypeIndex = [popupButton indexOfSelectedItem];
+ }
+
+ NSURL* fileURL = [thePanel URL];
+ if (fileURL) {
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)fileURL))) {
+ *outFile = localFile;
+ NS_ADDREF(*outFile);
+ // We tell if we are replacing or not by just looking to see if the file exists.
+ // The user could not have hit OK and not meant to replace the file.
+ if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
+ retVal = returnReplace;
+ else
+ retVal = returnOK;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+NSArray *
+nsFilePicker::GetFilterList()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mFilters.Length()) {
+ return nil;
+ }
+
+ if (mFilters.Length() <= (uint32_t)mSelectedTypeIndex) {
+ NS_WARNING("An out of range index has been selected. Using the first index instead.");
+ mSelectedTypeIndex = 0;
+ }
+
+ const nsString& filterWide = mFilters[mSelectedTypeIndex];
+ if (!filterWide.Length()) {
+ return nil;
+ }
+
+ if (filterWide.Equals(NS_LITERAL_STRING("*"))) {
+ return nil;
+ }
+
+ // The extensions in filterWide are in the format "*.ext" but are expected
+ // in the format "ext" by NSOpenPanel. So we need to filter some characters.
+ NSMutableString* filterString = [[[NSMutableString alloc] initWithString:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(filterWide.get())
+ length:filterWide.Length()]] autorelease];
+ NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@". *"];
+ NSRange range = [filterString rangeOfCharacterFromSet:set];
+ while (range.length) {
+ [filterString replaceCharactersInRange:range withString:@""];
+ range = [filterString rangeOfCharacterFromSet:set];
+ }
+
+ return [[[NSArray alloc] initWithArray:
+ [filterString componentsSeparatedByString:@";"]] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Sets the dialog title to whatever it should be. If it fails, eh,
+// the OS will provide a sensible default.
+void
+nsFilePicker::SetDialogTitle(const nsString& inTitle, id aPanel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [aPanel setTitle:[NSString stringWithCharacters:(const unichar*)inTitle.get() length:inTitle.Length()]];
+
+ if (!mOkButtonLabel.IsEmpty()) {
+ [aPanel setPrompt:[NSString stringWithCharacters:(const unichar*)mOkButtonLabel.get() length:mOkButtonLabel.Length()]];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Converts path from an nsIFile into a NSString path
+// If it fails, returns an empty string.
+NSString *
+nsFilePicker::PanelDefaultDirectory()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString *directory = nil;
+ if (mDisplayDirectory) {
+ nsAutoString pathStr;
+ mDisplayDirectory->GetPath(pathStr);
+ directory = [[[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(pathStr.get())
+ length:pathStr.Length()] autorelease];
+ }
+ return directory;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+NS_IMETHODIMP nsFilePicker::GetFile(nsIFile **aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = nullptr;
+
+ // just return the first file
+ if (mFiles.Count() > 0) {
+ *aFile = mFiles.ObjectAt(0);
+ NS_IF_ADDREF(*aFile);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI **aFileURL)
+{
+ NS_ENSURE_ARG_POINTER(aFileURL);
+ *aFileURL = nullptr;
+
+ if (mFiles.Count() == 0)
+ return NS_OK;
+
+ return NS_NewFileURI(aFileURL, mFiles.ObjectAt(0));
+}
+
+NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
+{
+ return NS_NewArrayEnumerator(aFiles, mFiles);
+}
+
+NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString)
+{
+ mDefault = aString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::GetDefaultString(nsAString& aString)
+{
+ return NS_ERROR_FAILURE;
+}
+
+// The default extension to use for files
+NS_IMETHODIMP nsFilePicker::GetDefaultExtension(nsAString& aExtension)
+{
+ aExtension.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension)
+{
+ return NS_OK;
+}
+
+// Append an entry to the filters array
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
+{
+ // "..apps" has to be translated with native executable extensions.
+ if (aFilter.EqualsLiteral("..apps")) {
+ mFilters.AppendElement(NS_LITERAL_STRING("*.app"));
+ } else {
+ mFilters.AppendElement(aFilter);
+ }
+ mTitles.AppendElement(aTitle);
+
+ return NS_OK;
+}
+
+// Get the filter index - do we still need this?
+NS_IMETHODIMP nsFilePicker::GetFilterIndex(int32_t *aFilterIndex)
+{
+ *aFilterIndex = mSelectedTypeIndex;
+ return NS_OK;
+}
+
+// Set the filter index - do we still need this?
+NS_IMETHODIMP nsFilePicker::SetFilterIndex(int32_t aFilterIndex)
+{
+ mSelectedTypeIndex = aFilterIndex;
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsIdleServiceX.h b/widget/cocoa/nsIdleServiceX.h
new file mode 100644
index 0000000000..f0b3d92ed5
--- /dev/null
+++ b/widget/cocoa/nsIdleServiceX.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIdleServiceX_h_
+#define nsIdleServiceX_h_
+
+#include "nsIdleService.h"
+
+class nsIdleServiceX : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsIdleServiceX> GetInstance()
+ {
+ RefPtr<nsIdleService> idleService = nsIdleService::GetInstance();
+ if (!idleService) {
+ idleService = new nsIdleServiceX();
+ }
+
+ return idleService.forget().downcast<nsIdleServiceX>();
+ }
+
+protected:
+ nsIdleServiceX() { }
+ virtual ~nsIdleServiceX() { }
+ bool UsePollMode() override;
+};
+
+#endif // nsIdleServiceX_h_
diff --git a/widget/cocoa/nsIdleServiceX.mm b/widget/cocoa/nsIdleServiceX.mm
new file mode 100644
index 0000000000..234a154146
--- /dev/null
+++ b/widget/cocoa/nsIdleServiceX.mm
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIdleServiceX.h"
+#include "nsObjCExceptions.h"
+#include "nsIServiceManager.h"
+#import <Foundation/Foundation.h>
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceX, nsIdleService)
+
+bool
+nsIdleServiceX::PollIdleTime(uint32_t *aIdleTime)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ kern_return_t rval;
+ mach_port_t masterPort;
+
+ rval = IOMasterPort(kIOMasterPortDefault, &masterPort);
+ if (rval != KERN_SUCCESS)
+ return false;
+
+ io_iterator_t hidItr;
+ rval = IOServiceGetMatchingServices(masterPort,
+ IOServiceMatching("IOHIDSystem"),
+ &hidItr);
+
+ if (rval != KERN_SUCCESS)
+ return false;
+ NS_ASSERTION(hidItr, "Our iterator is null, but it ought not to be!");
+
+ io_registry_entry_t entry = IOIteratorNext(hidItr);
+ NS_ASSERTION(entry, "Our IO Registry Entry is null, but it shouldn't be!");
+
+ IOObjectRelease(hidItr);
+
+ NSMutableDictionary *hidProps;
+ rval = IORegistryEntryCreateCFProperties(entry,
+ (CFMutableDictionaryRef*)&hidProps,
+ kCFAllocatorDefault, 0);
+ if (rval != KERN_SUCCESS)
+ return false;
+ NS_ASSERTION(hidProps, "HIDProperties is null, but no error was returned.");
+ [hidProps autorelease];
+
+ id idleObj = [hidProps objectForKey:@"HIDIdleTime"];
+ NS_ASSERTION([idleObj isKindOfClass: [NSData class]] ||
+ [idleObj isKindOfClass: [NSNumber class]],
+ "What we got for the idle object is not what we expect!");
+
+ uint64_t time;
+ if ([idleObj isKindOfClass: [NSData class]])
+ [idleObj getBytes: &time];
+ else
+ time = [idleObj unsignedLongLongValue];
+
+ IOObjectRelease(entry);
+
+ // convert to ms from ns
+ time /= 1000000;
+ if (time > UINT32_MAX) // Overflow will occur
+ return false;
+
+ *aIdleTime = static_cast<uint32_t>(time);
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool
+nsIdleServiceX::UsePollMode()
+{
+ return true;
+}
+
diff --git a/widget/cocoa/nsLookAndFeel.h b/widget/cocoa/nsLookAndFeel.h
new file mode 100644
index 0000000000..2ad31a2aa1
--- /dev/null
+++ b/widget/cocoa/nsLookAndFeel.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLookAndFeel_h_
+#define nsLookAndFeel_h_
+#include "nsXPLookAndFeel.h"
+
+class nsLookAndFeel: public nsXPLookAndFeel {
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel);
+ virtual char16_t GetPasswordCharacterImpl()
+ {
+ // unicode value for the bullet character, used for password textfields.
+ return 0x2022;
+ }
+
+ static bool UseOverlayScrollbars();
+
+ virtual nsTArray<LookAndFeelInt> GetIntCacheImpl();
+ virtual void SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache);
+
+ virtual void RefreshImpl();
+
+protected:
+ static bool SystemWantsOverlayScrollbars();
+ static bool AllowOverlayScrollbarsOverlap();
+
+private:
+ int32_t mUseOverlayScrollbars;
+ bool mUseOverlayScrollbarsCached;
+
+ int32_t mAllowOverlayScrollbarsOverlap;
+ bool mAllowOverlayScrollbarsOverlapCached;
+};
+
+#endif // nsLookAndFeel_h_
diff --git a/widget/cocoa/nsLookAndFeel.mm b/widget/cocoa/nsLookAndFeel.mm
new file mode 100644
index 0000000000..cbee90f581
--- /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 0000000000..cf9c84c7ea
--- /dev/null
+++ b/widget/cocoa/nsMacCursor.h
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMacCursor_h_
+#define nsMacCursor_h_
+
+#import <Cocoa/Cocoa.h>
+#import "nsIWidget.h"
+
+/*! @class nsMacCursor
+ @abstract Represents a native Mac cursor.
+ @discussion <code>nsMacCursor</code> provides a simple API for creating and working with native Macintosh cursors.
+ Cursors can be created used without needing to be aware of the way different cursors are implemented,
+ in particular the details of managing an animated cursor are hidden.
+*/
+@interface nsMacCursor : NSObject
+{
+ @private
+ NSTimer *mTimer;
+ @protected
+ nsCursor mType;
+ int mFrameCounter;
+}
+
+/*! @method cursorWithCursor:
+ @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>.
+ @discussion Creates a cursor representing the given Cocoa built-in cursor.
+ @param aCursor the <code>NSCursor</code> to use
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> representing the given <code>NSCursor</code>
+ */
++ (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType;
+
+/*! @method cursorWithImageNamed:hotSpot:type:
+ @abstract Create a cursor by specifying the name of an image resource to use for the cursor and a hotspot.
+ @discussion Creates a cursor by loading the named image using the <code>+[NSImage imageNamed:]</code> method.
+ <p>The image must be compatible with any restrictions laid down by <code>NSCursor</code>. These vary
+ by operating system version.</p>
+ <p>The hotspot precisely determines the point where the user clicks when using the cursor.</p>
+ @param aCursor the name of the image to use for the cursor
+ @param aPoint the point within the cursor to use as the hotspot
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> that uses the given image and hotspot
+ */
++ (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType;
+
+/*! @method cursorWithFrames:type:
+ @abstract Create an animated cursor by specifying the frames to use for the animation.
+ @discussion Creates a cursor that will animate by cycling through the given frames. Each element of the array
+ must be an instance of <code>NSCursor</code>
+ @param aCursorFrames an array of <code>NSCursor</code>, representing the frames of an animated cursor, in the
+ order they should be played.
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> that will animate the given cursor frames
+ */
++ (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType;
+
+/*! @method cocoaCursorWithImageNamed:hotSpot:
+ @abstract Create a Cocoa NSCursor object with a Gecko image resource name and a hotspot point.
+ @discussion Create a Cocoa NSCursor object with a Gecko image resource name and a hotspot point.
+ @param imageName the name of the gecko image resource, "tiff" extension is assumed, do not append.
+ @param aPoint the point within the cursor to use as the hotspot
+ @result an autoreleased instance of <code>nsMacCursor</code> that will animate the given cursor frames
+ */
++ (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint;
+
+/*! @method isSet
+ @abstract Determines whether this cursor is currently active.
+ @discussion This can be helpful when the Cocoa NSCursor state can be influenced without going
+ through nsCursorManager.
+ @result whether the cursor is currently set
+ */
+- (BOOL) isSet;
+
+/*! @method set
+ @abstract Set the cursor.
+ @discussion Makes this cursor the current cursor. If the cursor is animated, the animation is started.
+ */
+- (void) set;
+
+/*! @method unset
+ @abstract Unset the cursor. The cursor will return to the default (usually the arrow cursor).
+ @discussion Unsets the cursor. If the cursor is animated, the animation is stopped.
+ */
+- (void) unset;
+
+/*! @method isAnimated
+ @abstract Tests whether this cursor is animated.
+ @discussion Use this method to determine whether a cursor is animated
+ @result YES if the cursor is animated (has more than one frame), NO if it is a simple static cursor.
+ */
+- (BOOL) isAnimated;
+
+/** @method cursorType
+ @abstract Get the cursor type for this cursor
+ @discussion This method returns the <code>nsCursor</code> constant that corresponds to this cursor, which is
+ equivalent to the CSS name for the cursor.
+ @result The nsCursor constant corresponding to this cursor, or nsCursor's 'eCursorCount' if the cursor
+ is a custom cursor loaded from a URI
+ */
+- (nsCursor) type;
+@end
+
+#endif // nsMacCursor_h_
diff --git a/widget/cocoa/nsMacCursor.mm b/widget/cocoa/nsMacCursor.mm
new file mode 100644
index 0000000000..4fcdfd3e5d
--- /dev/null
+++ b/widget/cocoa/nsMacCursor.mm
@@ -0,0 +1,382 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMacCursor.h"
+#include "nsObjCExceptions.h"
+#include "nsDebug.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+/*! @category nsMacCursor (PrivateMethods)
+ @abstract Private methods internal to the nsMacCursor class.
+ @discussion <code>nsMacCursor</code> is effectively an abstract class. It does not define complete
+ behaviour in and of itself, the subclasses defined in this file provide the useful implementations.
+*/
+@interface nsMacCursor (PrivateMethods)
+
+/*! @method getNextCursorFrame
+ @abstract get the index of the next cursor frame to display.
+ @discussion Increments and returns the frame counter of an animated cursor.
+ @result The index of the next frame to display in the cursor animation
+*/
+- (int) getNextCursorFrame;
+
+/*! @method numFrames
+ @abstract Query the number of frames in this cursor's animation.
+ @discussion Returns the number of frames in this cursor's animation. Static cursors return 1.
+*/
+- (int) numFrames;
+
+/*! @method createTimer
+ @abstract Create a Timer to use to animate the cursor.
+ @discussion Creates an instance of <code>NSTimer</code> which is used to drive the cursor animation.
+ This method should only be called for cursors that are animated.
+*/
+- (void) createTimer;
+
+/*! @method destroyTimer
+ @abstract Destroy any timer instance associated with this cursor.
+ @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor.
+ */
+- (void) destroyTimer;
+/*! @method destroyTimer
+ @abstract Destroy any timer instance associated with this cursor.
+ @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor.
+*/
+
+/*! @method advanceAnimatedCursor:
+ @abstract Method called by animation timer to perform animation.
+ @discussion Called by an animated cursor's associated timer to advance the animation to the next frame.
+ Determines which frame should occur next and sets the cursor to that frame.
+ @param aTimer the timer causing the animation
+*/
+- (void) advanceAnimatedCursor: (NSTimer *) aTimer;
+
+/*! @method setFrame:
+ @abstract Sets the current cursor, using an index to determine which frame in the animation to display.
+ @discussion Sets the current cursor. The frame index determines which frame is shown if the cursor is animated.
+ Frames and numbered from <code>0</code> to <code>-[nsMacCursor numFrames] - 1</code>. A static cursor
+ has a single frame, numbered 0.
+ @param aFrameIndex the index indicating which frame from the animation to display
+*/
+- (void) setFrame: (int) aFrameIndex;
+
+@end
+
+/*! @class nsCocoaCursor
+ @abstract Implementation of <code>nsMacCursor</code> that uses Cocoa <code>NSCursor</code> instances.
+ @discussion Displays a static or animated cursor, using Cocoa <code>NSCursor</code> instances. These can be either
+ built-in <code>NSCursor</code> instances, or custom <code>NSCursor</code>s created from images.
+ When more than one <code>NSCursor</code> is provided, the cursor will use these as animation frames.
+*/
+@interface nsCocoaCursor : nsMacCursor
+{
+ @private
+ NSArray *mFrames;
+ NSCursor *mLastSetCocoaCursor;
+}
+
+/*! @method initWithFrames:
+ @abstract Create an animated cursor by specifying the frames to use for the animation.
+ @discussion Creates a cursor that will animate by cycling through the given frames. Each element of the array
+ must be an instance of <code>NSCursor</code>
+ @param aCursorFrames an array of <code>NSCursor</code>, representing the frames of an animated cursor, in the
+ order they should be played.
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> that will animate the given cursor frames
+ */
+- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType;
+
+/*! @method initWithCursor:
+ @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>.
+ @discussion Creates a cursor representing the given Cocoa built-in cursor.
+ @param aCursor the <code>NSCursor</code> to use
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> representing the given <code>NSCursor</code>
+*/
+- (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType;
+
+/*! @method initWithImageNamed:hotSpot:
+ @abstract Create a cursor by specifying the name of an image resource to use for the cursor and a hotspot.
+ @discussion Creates a cursor by loading the named image using the <code>+[NSImage imageNamed:]</code> method.
+ <p>The image must be compatible with any restrictions laid down by <code>NSCursor</code>. These vary
+ by operating system version.</p>
+ <p>The hotspot precisely determines the point where the user clicks when using the cursor.</p>
+ @param aCursor the name of the image to use for the cursor
+ @param aPoint the point within the cursor to use as the hotspot
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> that uses the given image and hotspot
+*/
+- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType;
+
+@end
+
+@implementation nsMacCursor
+
++ (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsCOMPtr<nsIFile> resDir;
+ nsAutoCString resPath;
+ NSString* pathToImage, *pathToHiDpiImage;
+ NSImage* cursorImage, *hiDpiCursorImage;
+
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
+ if (NS_FAILED(rv))
+ goto INIT_FAILURE;
+ resDir->AppendNative(NS_LITERAL_CSTRING("res"));
+ resDir->AppendNative(NS_LITERAL_CSTRING("cursors"));
+
+ rv = resDir->GetNativePath(resPath);
+ if (NS_FAILED(rv))
+ goto INIT_FAILURE;
+
+ pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()];
+ if (!pathToImage)
+ goto INIT_FAILURE;
+ pathToImage = [pathToImage stringByAppendingPathComponent:imageName];
+ pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"];
+ // Add same extension to both image paths.
+ pathToImage = [pathToImage stringByAppendingPathExtension:@"png"];
+ pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"];
+
+ cursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
+ if (!cursorImage)
+ goto INIT_FAILURE;
+
+ // Note 1: There are a few different ways to get a hidpi image via
+ // initWithContentsOfFile. We let the OS handle this here: when the
+ // file basename ends in "@2x", it will be displayed at native resolution
+ // instead of being pixel-doubled. See bug 784909 comment 7 for alternates ways.
+ //
+ // Note 2: The OS is picky, and will ignore the hidpi representation
+ // unless it is exactly twice the size of the lowdpi image.
+ hiDpiCursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
+ if (hiDpiCursorImage) {
+ NSImageRep *imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
+ [cursorImage addRepresentation: imageRep];
+ }
+ return [[[NSCursor alloc] initWithImage:cursorImage hotSpot:aPoint] autorelease];
+
+INIT_FAILURE:
+ NS_WARNING("Problem getting path to cursor image file!");
+ [self release];
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL) isSet
+{
+ // implemented by subclasses
+ return NO;
+}
+
+- (void) set
+{
+ if ([self isAnimated]) {
+ [self createTimer];
+ }
+ // if the cursor isn't animated or the timer creation fails for any reason...
+ if (!mTimer) {
+ [self setFrame:0];
+ }
+}
+
+- (void) unset
+{
+ [self destroyTimer];
+}
+
+- (BOOL) isAnimated
+{
+ return [self numFrames] > 1;
+}
+
+- (int) numFrames
+{
+ // subclasses need to override this to support animation
+ return 1;
+}
+
+- (int) getNextCursorFrame
+{
+ mFrameCounter = (mFrameCounter + 1) % [self numFrames];
+ return mFrameCounter;
+}
+
+- (void) createTimer
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mTimer) {
+ mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25
+ target:self
+ selector:@selector(advanceAnimatedCursor:)
+ userInfo:nil
+ repeats:YES] retain];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void) destroyTimer
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTimer) {
+ [mTimer invalidate];
+ [mTimer release];
+ mTimer = nil;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void) advanceAnimatedCursor: (NSTimer *) aTimer
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([aTimer isValid]) {
+ [self setFrame:[self getNextCursorFrame]];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void) setFrame: (int) aFrameIndex
+{
+ // subclasses need to do something useful here
+}
+
+- (nsCursor) type {
+ return mType;
+}
+
+- (void) dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self destroyTimer];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+@implementation nsCocoaCursor
+
+- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ self = [super init];
+ NSEnumerator *it = [aCursorFrames objectEnumerator];
+ NSObject *frame = nil;
+ while ((frame = [it nextObject])) {
+ NS_ASSERTION([frame isKindOfClass:[NSCursor class]], "Invalid argument: All frames must be of type NSCursor");
+ }
+ mFrames = [aCursorFrames retain];
+ mFrameCounter = 0;
+ mType = aType;
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSArray *frame = [NSArray arrayWithObjects:aCursor, nil];
+ return [self initWithFrames:frame type:aType];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint] type:aType];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL) isSet
+{
+ return [NSCursor currentCursor] == mLastSetCocoaCursor;
+}
+
+- (void) setFrame: (int) aFrameIndex
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
+ [newCursor set];
+ mLastSetCocoaCursor = newCursor;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (int) numFrames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [mFrames count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (NSString *) description
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [mFrames description];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void) dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mFrames release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/nsMacDockSupport.h b/widget/cocoa/nsMacDockSupport.h
new file mode 100644
index 0000000000..a638b89e03
--- /dev/null
+++ b/widget/cocoa/nsMacDockSupport.h
@@ -0,0 +1,41 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMacDockSupport.h"
+#include "nsIStandaloneNativeMenu.h"
+#include "nsITaskbarProgress.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsNativeThemeCocoa.h"
+
+class nsMacDockSupport : public nsIMacDockSupport, public nsITaskbarProgress
+{
+public:
+ nsMacDockSupport();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACDOCKSUPPORT
+ NS_DECL_NSITASKBARPROGRESS
+
+protected:
+ virtual ~nsMacDockSupport();
+
+ nsCOMPtr<nsIStandaloneNativeMenu> mDockMenu;
+ nsString mBadgeText;
+
+ NSImage *mAppIcon, *mProgressBackground;
+
+ HIRect mProgressBounds;
+ nsTaskbarProgressState mProgressState;
+ double mProgressFraction;
+ nsCOMPtr<nsITimer> mProgressTimer;
+ RefPtr<nsNativeThemeCocoa> mTheme;
+
+ static void RedrawIconCallback(nsITimer* aTimer, void* aClosure);
+
+ bool InitProgress();
+ nsresult RedrawIcon();
+};
diff --git a/widget/cocoa/nsMacDockSupport.mm b/widget/cocoa/nsMacDockSupport.mm
new file mode 100644
index 0000000000..56b37822bd
--- /dev/null
+++ b/widget/cocoa/nsMacDockSupport.mm
@@ -0,0 +1,174 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsMacDockSupport.h"
+#include "nsObjCExceptions.h"
+
+NS_IMPL_ISUPPORTS(nsMacDockSupport, nsIMacDockSupport, nsITaskbarProgress)
+
+nsMacDockSupport::nsMacDockSupport()
+: mAppIcon(nil)
+, mProgressBackground(nil)
+, mProgressState(STATE_NO_PROGRESS)
+, mProgressFraction(0.0)
+{
+ mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+}
+
+nsMacDockSupport::~nsMacDockSupport()
+{
+ if (mAppIcon) {
+ [mAppIcon release];
+ mAppIcon = nil;
+ }
+ if (mProgressBackground) {
+ [mProgressBackground release];
+ mProgressBackground = nil;
+ }
+ if (mProgressTimer) {
+ mProgressTimer->Cancel();
+ mProgressTimer = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::GetDockMenu(nsIStandaloneNativeMenu ** aDockMenu)
+{
+ nsCOMPtr<nsIStandaloneNativeMenu> dockMenu(mDockMenu);
+ dockMenu.forget(aDockMenu);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetDockMenu(nsIStandaloneNativeMenu * aDockMenu)
+{
+ mDockMenu = aDockMenu;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::ActivateApplication(bool aIgnoreOtherApplications)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [[NSApplication sharedApplication] activateIgnoringOtherApps:aIgnoreOtherApplications];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetBadgeText(const nsAString& aBadgeText)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSDockTile *tile = [[NSApplication sharedApplication] dockTile];
+ mBadgeText = aBadgeText;
+ if (aBadgeText.IsEmpty())
+ [tile setBadgeLabel: nil];
+ else
+ [tile setBadgeLabel:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(mBadgeText.get())
+ length:mBadgeText.Length()]];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::GetBadgeText(nsAString& aBadgeText)
+{
+ aBadgeText = mBadgeText;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetProgressState(nsTaskbarProgressState aState,
+ uint64_t aCurrentValue,
+ uint64_t aMaxValue)
+{
+ NS_ENSURE_ARG_RANGE(aState, 0, STATE_PAUSED);
+ if (aState == STATE_NO_PROGRESS || aState == STATE_INDETERMINATE) {
+ NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
+ }
+ if (aCurrentValue > aMaxValue) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mProgressState = aState;
+ if (aMaxValue == 0) {
+ mProgressFraction = 0;
+ } else {
+ mProgressFraction = (double)aCurrentValue / aMaxValue;
+ }
+
+ if (mProgressState == STATE_NORMAL || mProgressState == STATE_INDETERMINATE) {
+ int perSecond = 8; // Empirically determined, see bug 848792
+ mProgressTimer->InitWithFuncCallback(RedrawIconCallback, this, 1000 / perSecond,
+ nsITimer::TYPE_REPEATING_SLACK);
+ return NS_OK;
+ } else {
+ mProgressTimer->Cancel();
+ return RedrawIcon();
+ }
+}
+
+// static
+void nsMacDockSupport::RedrawIconCallback(nsITimer* aTimer, void* aClosure)
+{
+ static_cast<nsMacDockSupport*>(aClosure)->RedrawIcon();
+}
+
+// Return whether to draw progress
+bool nsMacDockSupport::InitProgress()
+{
+ if (mProgressState != STATE_NORMAL && mProgressState != STATE_INDETERMINATE) {
+ return false;
+ }
+
+ if (!mAppIcon) {
+ mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ mAppIcon = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
+ mProgressBackground = [mAppIcon copyWithZone:nil];
+ mTheme = new nsNativeThemeCocoa();
+
+ NSSize sz = [mProgressBackground size];
+ mProgressBounds = CGRectMake(sz.width * 1/32, sz.height * 3/32,
+ sz.width * 30/32, sz.height * 4/32);
+ [mProgressBackground lockFocus];
+ [[NSColor whiteColor] set];
+ NSRectFill(NSRectFromCGRect(mProgressBounds));
+ [mProgressBackground unlockFocus];
+ }
+ return true;
+}
+
+nsresult
+nsMacDockSupport::RedrawIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (InitProgress()) {
+ // TODO: - Implement ERROR and PAUSED states?
+ NSImage *icon = [mProgressBackground copyWithZone:nil];
+ bool isIndeterminate = (mProgressState != STATE_NORMAL);
+
+ [icon lockFocus];
+ CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ mTheme->DrawProgress(ctx, mProgressBounds, isIndeterminate,
+ true, mProgressFraction, 1.0, NULL);
+ [icon unlockFocus];
+ [NSApp setApplicationIconImage:icon];
+ [icon release];
+ } else {
+ [NSApp setApplicationIconImage:mAppIcon];
+ }
+
+ return NS_OK;
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMacWebAppUtils.h b/widget/cocoa/nsMacWebAppUtils.h
new file mode 100644
index 0000000000..98ef235615
--- /dev/null
+++ b/widget/cocoa/nsMacWebAppUtils.h
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _MAC_WEB_APP_UTILS_H_
+#define _MAC_WEB_APP_UTILS_H_
+
+#include "nsIMacWebAppUtils.h"
+
+#define NS_MACWEBAPPUTILS_CONTRACTID "@mozilla.org/widget/mac-web-app-utils;1"
+
+class nsMacWebAppUtils : public nsIMacWebAppUtils {
+public:
+ nsMacWebAppUtils() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACWEBAPPUTILS
+
+protected:
+ virtual ~nsMacWebAppUtils() {}
+};
+
+#endif //_MAC_WEB_APP_UTILS_H_
diff --git a/widget/cocoa/nsMacWebAppUtils.mm b/widget/cocoa/nsMacWebAppUtils.mm
new file mode 100644
index 0000000000..1b98cef7cc
--- /dev/null
+++ b/widget/cocoa/nsMacWebAppUtils.mm
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMacWebAppUtils.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+#include "nsString.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+// Find the path to the app with the given bundleIdentifier, if any.
+// Note that the OS will return the path to the newest binary, if there is more than one.
+// The determination of 'newest' is complex and beyond the scope of this comment.
+
+NS_IMPL_ISUPPORTS(nsMacWebAppUtils, nsIMacWebAppUtils)
+
+NS_IMETHODIMP nsMacWebAppUtils::PathForAppWithIdentifier(const nsAString& bundleIdentifier, nsAString& outPath) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ outPath.Truncate();
+
+ nsAutoreleasePool localPool;
+
+ //note that the result of this expression might be nil, meaning no matching app was found.
+ NSString* temp = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)bundleIdentifier).get())
+ length:((nsString)bundleIdentifier).Length()]];
+
+ if (temp) {
+ // Copy out the resultant absolute path into outPath if non-nil.
+ nsCocoaUtils::GetStringForNSString(temp, outPath);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsMacWebAppUtils::LaunchAppWithIdentifier(const nsAString& bundleIdentifier) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsAutoreleasePool localPool;
+
+ // Note this might return false, meaning the app wasnt launched for some reason.
+ BOOL success = [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)bundleIdentifier).get())
+ length:((nsString)bundleIdentifier).Length()]
+ options: (NSWorkspaceLaunchOptions)0
+ additionalEventParamDescriptor: nil
+ launchIdentifier: NULL];
+
+ return success ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsMacWebAppUtils::TrashApp(const nsAString& path, nsITrashAppCallback* aCallback) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!aCallback)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsITrashAppCallback> callback = aCallback;
+
+ NSString* tempString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)path).get())
+ length:path.Length()];
+
+ [[NSWorkspace sharedWorkspace] recycleURLs: [NSArray arrayWithObject:[NSURL fileURLWithPath:tempString]]
+ completionHandler: ^(NSDictionary *newURLs, NSError *error) {
+ nsresult rv = (error == nil) ? NS_OK : NS_ERROR_FAILURE;
+ callback->TrashAppFinished(rv);
+ }];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMenuBarX.h b/widget/cocoa/nsMenuBarX.h
new file mode 100644
index 0000000000..7cbb8ce62a
--- /dev/null
+++ b/widget/cocoa/nsMenuBarX.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuBarX_h_
+#define nsMenuBarX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/UniquePtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsChangeObserver.h"
+#include "nsINativeMenuService.h"
+#include "nsString.h"
+
+class nsMenuX;
+class nsIWidget;
+class nsIContent;
+
+// The native menu service for creating native menu bars.
+class nsNativeMenuServiceX : public nsINativeMenuService
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsNativeMenuServiceX() {}
+
+ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode) override;
+
+protected:
+ virtual ~nsNativeMenuServiceX() {}
+};
+
+// Objective-C class used to allow us to intervene with keyboard event handling.
+// We allow mouse actions to work normally.
+@interface GeckoNSMenu : NSMenu
+{
+}
+@end
+
+// Objective-C class used as action target for menu items
+@interface NativeMenuItemTarget : NSObject
+{
+}
+-(IBAction)menuItemHit:(id)sender;
+@end
+
+// Objective-C class used for menu items on the Services menu to allow Gecko
+// to override their standard behavior in order to stop key equivalents from
+// firing in certain instances.
+@interface GeckoServicesNSMenuItem : NSMenuItem
+{
+}
+- (id) target;
+- (SEL) action;
+- (void) _doNothing:(id)sender;
+@end
+
+// Objective-C class used as the Services menu so that Gecko can override the
+// standard behavior of the Services menu in order to stop key equivalents
+// from firing in certain instances.
+@interface GeckoServicesNSMenu : NSMenu
+{
+}
+- (void)addItem:(NSMenuItem *)newItem;
+- (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv;
+- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index;
+- (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index;
+- (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem;
+@end
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuBarX : public nsMenuGroupOwnerX, public nsChangeObserver
+{
+public:
+ nsMenuBarX();
+ virtual ~nsMenuBarX();
+
+ static NativeMenuItemTarget* sNativeEventTarget;
+ static nsMenuBarX* sLastGeckoMenuBarPainted;
+
+ // The following content nodes have been removed from the menu system.
+ // We save them here for use in command handling.
+ nsCOMPtr<nsIContent> mAboutItemContent;
+ nsCOMPtr<nsIContent> mPrefItemContent;
+ nsCOMPtr<nsIContent> mQuitItemContent;
+
+ // nsChangeObserver
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override {return (void*)mNativeMenu;}
+ nsMenuObjectTypeX MenuObjectType() override {return eMenuBarObjectType;}
+
+ // nsMenuBarX
+ nsresult Create(nsIWidget* aParent, nsIContent* aContent);
+ void SetParent(nsIWidget* aParent);
+ uint32_t GetMenuCount();
+ bool MenuContainsAppMenu();
+ nsMenuX* GetMenuAt(uint32_t aIndex);
+ nsMenuX* GetXULHelpMenu();
+ void SetSystemHelpMenu();
+ nsresult Paint();
+ void ForceUpdateNativeMenuAt(const nsAString& indexString);
+ void ForceNativeMenuReload(); // used for testing
+ static char GetLocalizedAccelKey(const char *shortcutID);
+ static void ResetNativeApplicationMenu();
+
+protected:
+ void ConstructNativeMenus();
+ void ConstructFallbackNativeMenus();
+ nsresult InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex);
+ void RemoveMenuAtIndex(uint32_t aIndex);
+ void HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode);
+ void AquifyMenuBar();
+ NSMenuItem* CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
+ int tag, NativeMenuItemTarget* target);
+ nsresult CreateApplicationMenu(nsMenuX* inMenu);
+
+ nsTArray<mozilla::UniquePtr<nsMenuX>> mMenuArray;
+ nsIWidget* mParentWindow; // [weak]
+ GeckoNSMenu* mNativeMenu; // root menu, representing entire menu bar
+};
+
+#endif // nsMenuBarX_h_
diff --git a/widget/cocoa/nsMenuBarX.mm b/widget/cocoa/nsMenuBarX.mm
new file mode 100644
index 0000000000..ff25eb81fc
--- /dev/null
+++ b/widget/cocoa/nsMenuBarX.mm
@@ -0,0 +1,979 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <objc/objc-runtime.h>
+
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+#include "nsChildView.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsGkAtoms.h"
+#include "nsObjCExceptions.h"
+#include "nsThreadUtils.h"
+
+#include "nsIContent.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIAppStartup.h"
+#include "nsIStringBundle.h"
+#include "nsToolkitCompsCID.h"
+
+NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil;
+nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
+NSMenu* sApplicationMenu = nil;
+BOOL sApplicationMenuIsFallback = NO;
+BOOL gSomeMenuBarPainted = NO;
+
+// We keep references to the first quit and pref item content nodes we find, which
+// will be from the hidden window. We use these when the document for the current
+// window does not have a quit or pref item. We don't need strong refs here because
+// these items are always strong ref'd by their owning menu bar (instance variable).
+static nsIContent* sAboutItemContent = nullptr;
+static nsIContent* sPrefItemContent = nullptr;
+static nsIContent* sQuitItemContent = nullptr;
+
+NS_IMPL_ISUPPORTS(nsNativeMenuServiceX, nsINativeMenuService)
+
+NS_IMETHODIMP nsNativeMenuServiceX::CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!");
+
+ RefPtr<nsMenuBarX> mb = new nsMenuBarX();
+ if (!mb)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return mb->Create(aParent, aMenuBarNode);
+}
+
+nsMenuBarX::nsMenuBarX()
+: nsMenuGroupOwnerX(), mParentWindow(nullptr)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsMenuBarX::~nsMenuBarX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (nsMenuBarX::sLastGeckoMenuBarPainted == this)
+ nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
+
+ // the quit/pref items of a random window might have been used if there was no
+ // hidden window, thus we need to invalidate the weak references.
+ if (sAboutItemContent == mAboutItemContent)
+ sAboutItemContent = nullptr;
+ if (sQuitItemContent == mQuitItemContent)
+ sQuitItemContent = nullptr;
+ if (sPrefItemContent == mPrefItemContent)
+ sPrefItemContent = nullptr;
+
+ // make sure we unregister ourselves as a content observer
+ if (mContent) {
+ UnregisterForContentChanges(mContent);
+ }
+
+ // We have to manually clear the array here because clearing causes menu items
+ // to call back into the menu bar to unregister themselves. We don't want to
+ // depend on member variable ordering to ensure that the array gets cleared
+ // before the registration hash table is destroyed.
+ mMenuArray.Clear();
+
+ [mNativeMenu release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent)
+{
+ if (!aParent)
+ return NS_ERROR_INVALID_ARG;
+
+ mParentWindow = aParent;
+ mContent = aContent;
+
+ if (mContent) {
+ AquifyMenuBar();
+
+ nsresult rv = nsMenuGroupOwnerX::Create(mContent);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RegisterForContentChanges(mContent, this);
+ ConstructNativeMenus();
+ } else {
+ ConstructFallbackNativeMenus();
+ }
+
+ // Give this to the parent window. The parent takes ownership.
+ static_cast<nsCocoaWindow*>(mParentWindow)->SetMenuBar(this);
+
+ return NS_OK;
+}
+
+void nsMenuBarX::ConstructNativeMenus()
+{
+ uint32_t count = mContent->GetChildCount();
+ for (uint32_t i = 0; i < count; i++) {
+ nsIContent *menuContent = mContent->GetChildAt(i);
+ if (menuContent &&
+ menuContent->IsXULElement(nsGkAtoms::menu)) {
+ nsMenuX* newMenu = new nsMenuX();
+ if (newMenu) {
+ nsresult rv = newMenu->Create(this, this, menuContent);
+ if (NS_SUCCEEDED(rv))
+ InsertMenuAtIndex(newMenu, GetMenuCount());
+ else
+ delete newMenu;
+ }
+ }
+ }
+}
+
+void nsMenuBarX::ConstructFallbackNativeMenus()
+{
+ if (sApplicationMenu) {
+ // Menu has already been built.
+ return;
+ }
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/fallbackMenubar.properties", getter_AddRefs(stringBundle));
+
+ if (!stringBundle) {
+ return;
+ }
+
+ nsXPIDLString labelUTF16;
+ nsXPIDLString keyUTF16;
+
+ const char16_t* labelProp = u"quitMenuitem.label";
+ const char16_t* keyProp = u"quitMenuitem.key";
+
+ stringBundle->GetStringFromName(labelProp, getter_Copies(labelUTF16));
+ stringBundle->GetStringFromName(keyProp, getter_Copies(keyUTF16));
+
+ NSString* labelStr = [NSString stringWithUTF8String:
+ NS_ConvertUTF16toUTF8(labelUTF16).get()];
+ NSString* keyStr= [NSString stringWithUTF8String:
+ NS_ConvertUTF16toUTF8(keyUTF16).get()];
+
+ if (!nsMenuBarX::sNativeEventTarget) {
+ nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
+ }
+
+ sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
+ NSMenuItem* quitMenuItem = [[[NSMenuItem alloc] initWithTitle:labelStr
+ action:@selector(menuItemHit:)
+ keyEquivalent:keyStr] autorelease];
+ [quitMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [quitMenuItem setTag:eCommand_ID_Quit];
+ [sApplicationMenu addItem:quitMenuItem];
+ sApplicationMenuIsFallback = YES;
+}
+
+uint32_t nsMenuBarX::GetMenuCount()
+{
+ return mMenuArray.Length();
+}
+
+bool nsMenuBarX::MenuContainsAppMenu()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return ([mNativeMenu numberOfItems] > 0 &&
+ [[mNativeMenu itemAtIndex:0] submenu] == sApplicationMenu);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+nsresult nsMenuBarX::InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // If we've only yet created a fallback global Application menu (using
+ // ContructFallbackNativeMenus()), destroy it before recreating it properly.
+ if (sApplicationMenu && sApplicationMenuIsFallback) {
+ ResetNativeApplicationMenu();
+ }
+ // If we haven't created a global Application menu yet, do it.
+ if (!sApplicationMenu) {
+ nsresult rv = NS_OK; // avoid warning about rv being unused
+ rv = CreateApplicationMenu(aMenu);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't create Application menu");
+
+ // Hook the new Application menu up to the menu bar.
+ NSMenu* mainMenu = [NSApp mainMenu];
+ NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+ [[mainMenu itemAtIndex:0] setSubmenu:sApplicationMenu];
+ }
+
+ // add menu to array that owns our menus
+ mMenuArray.InsertElementAt(aIndex, aMenu);
+
+ // hook up submenus
+ nsIContent* menuContent = aMenu->Content();
+ if (menuContent->GetChildCount() > 0 &&
+ !nsMenuUtilsX::NodeIsHiddenOrCollapsed(menuContent)) {
+ int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(this, aMenu);
+ if (MenuContainsAppMenu())
+ insertionIndex++;
+ [mNativeMenu insertItem:aMenu->NativeMenuItem() atIndex:insertionIndex];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsMenuBarX::RemoveMenuAtIndex(uint32_t aIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mMenuArray.Length() <= aIndex) {
+ NS_ERROR("Attempting submenu removal with bad index!");
+ return;
+ }
+
+ // Our native menu and our internal menu object array might be out of sync.
+ // This happens, for example, when a submenu is hidden. Because of this we
+ // should not assume that a native submenu is hooked up.
+ NSMenuItem* nativeMenuItem = mMenuArray[aIndex]->NativeMenuItem();
+ int nativeMenuItemIndex = [mNativeMenu indexOfItem:nativeMenuItem];
+ if (nativeMenuItemIndex != -1)
+ [mNativeMenu removeItemAtIndex:nativeMenuItemIndex];
+
+ mMenuArray.RemoveElementAt(aIndex);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuBarX::ObserveAttributeChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ nsIAtom* aAttribute)
+{
+}
+
+void nsMenuBarX::ObserveContentRemoved(nsIDocument* aDocument,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ RemoveMenuAtIndex(aIndexInContainer);
+}
+
+void nsMenuBarX::ObserveContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild)
+{
+ nsMenuX* newMenu = new nsMenuX();
+ if (newMenu) {
+ nsresult rv = newMenu->Create(this, this, aChild);
+ if (NS_SUCCEEDED(rv))
+ InsertMenuAtIndex(newMenu, aContainer->IndexOf(aChild));
+ else
+ delete newMenu;
+ }
+}
+
+void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+ NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0)
+ return;
+
+ nsMenuX* currentMenu = NULL;
+ int targetIndex = [[indexes objectAtIndex:0] intValue];
+ int visible = 0;
+ uint32_t length = mMenuArray.Length();
+ // first find a menu in the menu bar
+ for (unsigned int i = 0; i < length; i++) {
+ nsMenuX* menu = mMenuArray[i].get();
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
+ visible++;
+ if (visible == (targetIndex + 1)) {
+ currentMenu = menu;
+ break;
+ }
+ }
+ }
+
+ if (!currentMenu)
+ return;
+
+ // fake open/close to cause lazy update to happen so submenus populate
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+
+ // now find the correct submenu
+ for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
+ targetIndex = [[indexes objectAtIndex:i] intValue];
+ visible = 0;
+ length = currentMenu->GetItemCount();
+ for (unsigned int j = 0; j < length; j++) {
+ nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
+ if (!targetMenu)
+ return;
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
+ visible++;
+ if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
+ currentMenu = static_cast<nsMenuX*>(targetMenu);
+ // fake open/close to cause lazy update to happen
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+ break;
+ }
+ }
+ }
+ }
+}
+
+// Calling this forces a full reload of the menu system, reloading all native
+// menus and their items.
+// Without this testing is hard because changes to the DOM affect the native
+// menu system lazily.
+void nsMenuBarX::ForceNativeMenuReload()
+{
+ // tear down everything
+ while (GetMenuCount() > 0)
+ RemoveMenuAtIndex(0);
+
+ // construct everything
+ ConstructNativeMenus();
+}
+
+nsMenuX* nsMenuBarX::GetMenuAt(uint32_t aIndex)
+{
+ if (mMenuArray.Length() <= aIndex) {
+ NS_ERROR("Requesting menu at invalid index!");
+ return NULL;
+ }
+ return mMenuArray[aIndex].get();
+}
+
+nsMenuX* nsMenuBarX::GetXULHelpMenu()
+{
+ // The Help menu is usually (always?) the last one, so we start there and
+ // count back.
+ for (int32_t i = GetMenuCount() - 1; i >= 0; --i) {
+ nsMenuX* aMenu = GetMenuAt(i);
+ if (aMenu && nsMenuX::IsXULHelpMenu(aMenu->Content()))
+ return aMenu;
+ }
+ return nil;
+}
+
+// On SnowLeopard and later we must tell the OS which is our Help menu.
+// Otherwise it will only add Spotlight for Help (the Search item) to our
+// Help menu if its label/title is "Help" -- i.e. if the menu is in English.
+// This resolves bugs 489196 and 539317.
+void nsMenuBarX::SetSystemHelpMenu()
+{
+ nsMenuX* xulHelpMenu = GetXULHelpMenu();
+ if (xulHelpMenu) {
+ NSMenu* helpMenu = (NSMenu*)xulHelpMenu->NativeData();
+ if (helpMenu)
+ [NSApp setHelpMenu:helpMenu];
+ }
+}
+
+nsresult nsMenuBarX::Paint()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Don't try to optimize anything in this painting by checking
+ // sLastGeckoMenuBarPainted because the menubar can be manipulated by
+ // native dialogs and sheet code and other things besides this paint method.
+
+ // We have to keep the same menu item for the Application menu so we keep
+ // passing it along.
+ NSMenu* outgoingMenu = [NSApp mainMenu];
+ NS_ASSERTION([outgoingMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+
+ NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain];
+ [outgoingMenu removeItemAtIndex:0];
+ [mNativeMenu insertItem:appMenuItem atIndex:0];
+ [appMenuItem release];
+
+ // Set menu bar and event target.
+ [NSApp setMainMenu:mNativeMenu];
+ SetSystemHelpMenu();
+ nsMenuBarX::sLastGeckoMenuBarPainted = this;
+
+ gSomeMenuBarPainted = YES;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Returns the 'key' attribute of the 'shortcutID' object (if any) in the
+// currently active menubar's DOM document. 'shortcutID' should be the id
+// (i.e. the name) of a component that defines a commonly used (and
+// localized) cmd+key shortcut, and belongs to a keyset containing similar
+// objects. For example "key_selectAll". Returns a value that can be
+// compared to the first character of [NSEvent charactersIgnoringModifiers]
+// when [NSEvent modifierFlags] == NSCommandKeyMask.
+char nsMenuBarX::GetLocalizedAccelKey(const char *shortcutID)
+{
+ if (!sLastGeckoMenuBarPainted)
+ return 0;
+
+ nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(sLastGeckoMenuBarPainted->mContent->OwnerDoc()));
+ if (!domDoc)
+ return 0;
+
+ NS_ConvertASCIItoUTF16 shortcutIDStr((const char *)shortcutID);
+ nsCOMPtr<nsIDOMElement> shortcutElement;
+ domDoc->GetElementById(shortcutIDStr, getter_AddRefs(shortcutElement));
+ nsCOMPtr<nsIContent> shortcutContent = do_QueryInterface(shortcutElement);
+ if (!shortcutContent)
+ return 0;
+
+ nsAutoString key;
+ shortcutContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
+ NS_LossyConvertUTF16toASCII keyASC(key.get());
+ const char *keyASCPtr = keyASC.get();
+ if (!keyASCPtr)
+ return 0;
+ // If keyID's 'key' attribute isn't exactly one character long, it's not
+ // what we're looking for.
+ if (strlen(keyASCPtr) != sizeof(char))
+ return 0;
+ // Make sure retval is lower case.
+ char retval = tolower(keyASCPtr[0]);
+
+ return retval;
+}
+
+/* static */
+void nsMenuBarX::ResetNativeApplicationMenu()
+{
+ [sApplicationMenu removeAllItems];
+ [sApplicationMenu release];
+ sApplicationMenu = nil;
+ sApplicationMenuIsFallback = NO;
+}
+
+// Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so
+// the caller can hang onto it if they so choose. It is acceptable to pass nsull
+// for |outHiddenNode| if the caller doesn't care about the hidden node.
+void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode)
+{
+ nsCOMPtr<nsIDOMElement> menuItem;
+ inDoc->GetElementById(inID, getter_AddRefs(menuItem));
+ nsCOMPtr<nsIContent> menuContent(do_QueryInterface(menuItem));
+ if (menuContent) {
+ menuContent->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, NS_LITERAL_STRING("true"), false);
+ if (outHiddenNode) {
+ *outHiddenNode = menuContent.get();
+ NS_IF_ADDREF(*outHiddenNode);
+ }
+ }
+}
+
+// Do what is necessary to conform to the Aqua guidelines for menus.
+void nsMenuBarX::AquifyMenuBar()
+{
+ nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mContent->GetComposedDoc()));
+ if (domDoc) {
+ // remove the "About..." item and its separator
+ HideItem(domDoc, NS_LITERAL_STRING("aboutSeparator"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("aboutName"), getter_AddRefs(mAboutItemContent));
+ if (!sAboutItemContent)
+ sAboutItemContent = mAboutItemContent;
+
+ // remove quit item and its separator
+ HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitSeparator"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitItem"), getter_AddRefs(mQuitItemContent));
+ if (!sQuitItemContent)
+ sQuitItemContent = mQuitItemContent;
+
+ // remove prefs item and its separator, but save off the pref content node
+ // so we can invoke its command later.
+ HideItem(domDoc, NS_LITERAL_STRING("menu_PrefsSeparator"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_preferences"), getter_AddRefs(mPrefItemContent));
+ if (!sPrefItemContent)
+ sPrefItemContent = mPrefItemContent;
+
+ // hide items that we use for the Application menu
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_services"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_app"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_others"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_show_all"), nullptr);
+ }
+}
+
+// for creating menu items destined for the Application menu
+NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
+ int tag, NativeMenuItemTarget* target)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsCOMPtr<nsIDocument> doc = inMenu->Content()->GetUncomposedDoc();
+ if (!doc) {
+ return nil;
+ }
+
+ nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc));
+ if (!domdoc) {
+ return nil;
+ }
+
+ // Get information from the gecko menu item
+ nsAutoString label;
+ nsAutoString modifiers;
+ nsAutoString key;
+ nsCOMPtr<nsIDOMElement> menuItem;
+ domdoc->GetElementById(nodeID, getter_AddRefs(menuItem));
+ if (menuItem) {
+ menuItem->GetAttribute(NS_LITERAL_STRING("label"), label);
+ menuItem->GetAttribute(NS_LITERAL_STRING("modifiers"), modifiers);
+ menuItem->GetAttribute(NS_LITERAL_STRING("key"), key);
+ }
+ else {
+ return nil;
+ }
+
+ // Get more information about the key equivalent. Start by
+ // finding the key node we need.
+ NSString* keyEquiv = nil;
+ unsigned int macKeyModifiers = 0;
+ if (!key.IsEmpty()) {
+ nsCOMPtr<nsIDOMElement> keyElement;
+ domdoc->GetElementById(key, getter_AddRefs(keyElement));
+ if (keyElement) {
+ nsCOMPtr<nsIContent> keyContent (do_QueryInterface(keyElement));
+ // first grab the key equivalent character
+ nsAutoString keyChar(NS_LITERAL_STRING(" "));
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
+ if (!keyChar.EqualsLiteral(" ")) {
+ keyEquiv = [[NSString stringWithCharacters:reinterpret_cast<const unichar*>(keyChar.get())
+ length:keyChar.Length()] lowercaseString];
+ }
+ // now grab the key equivalent modifiers
+ nsAutoString modifiersStr;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
+ uint8_t geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
+ macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers);
+ }
+ }
+ // get the label into NSString-form
+ NSString* labelString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(label.get())
+ length:label.Length()];
+
+ if (!labelString)
+ labelString = @"";
+ if (!keyEquiv)
+ keyEquiv = @"";
+
+ // put together the actual NSMenuItem
+ NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:keyEquiv];
+
+ [newMenuItem setTag:tag];
+ [newMenuItem setTarget:target];
+ [newMenuItem setKeyEquivalentModifierMask:macKeyModifiers];
+
+ MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:this];
+ [newMenuItem setRepresentedObject:info];
+ [info release];
+
+ return newMenuItem;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// build the Application menu shared by all menu bars
+nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // At this point, the application menu is the application menu from
+ // the nib in cocoa widgets. We do not have a way to create an application
+ // menu manually, so we grab the one from the nib and use that.
+ sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
+
+/*
+ We support the following menu items here:
+
+ Menu Item DOM Node ID Notes
+
+ ========================
+ = About This App = <- aboutName
+ ========================
+ = Preferences... = <- menu_preferences
+ ========================
+ = Services > = <- menu_mac_services <- (do not define key equivalent)
+ ========================
+ = Hide App = <- menu_mac_hide_app
+ = Hide Others = <- menu_mac_hide_others
+ = Show All = <- menu_mac_show_all
+ ========================
+ = Quit = <- menu_FileQuitItem
+ ========================
+
+ If any of them are ommitted from the application's DOM, we just don't add
+ them. We always add a "Quit" item, but if an app developer does not provide a
+ DOM node with the right ID for the Quit item, we add it in English. App
+ developers need only add each node with a label and a key equivalent (if they
+ want one). Other attributes are optional. Like so:
+
+ <menuitem id="menu_preferences"
+ label="&preferencesCmdMac.label;"
+ key="open_prefs_key"/>
+
+ We need to use this system for localization purposes, until we have a better way
+ to define the Application menu to be used on Mac OS X.
+*/
+
+ if (sApplicationMenu) {
+ // This code reads attributes we are going to care about from the DOM elements
+
+ NSMenuItem *itemBeingAdded = nil;
+ BOOL addAboutSeparator = FALSE;
+
+ // Add the About menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:),
+ eCommand_ID_About, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addAboutSeparator = TRUE;
+ }
+
+ // Add separator if either the About item or software update item exists
+ if (addAboutSeparator)
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add the Preferences menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:),
+ eCommand_ID_Prefs, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ // Add separator after Preferences menu
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+ }
+
+ // Add Services menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_services"), nil,
+ 0, nil);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+
+ // set this menu item up as the Mac OS X Services menu
+ NSMenu* servicesMenu = [[GeckoServicesNSMenu alloc] initWithTitle:@""];
+ [itemBeingAdded setSubmenu:servicesMenu];
+ [NSApp setServicesMenu:servicesMenu];
+
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ // Add separator after Services menu
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+ }
+
+ BOOL addHideShowSeparator = FALSE;
+
+ // Add menu item to hide this application
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_app"), @selector(menuItemHit:),
+ eCommand_ID_HideApp, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addHideShowSeparator = TRUE;
+ }
+
+ // Add menu item to hide other applications
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(menuItemHit:),
+ eCommand_ID_HideOthers, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addHideShowSeparator = TRUE;
+ }
+
+ // Add menu item to show all applications
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(menuItemHit:),
+ eCommand_ID_ShowAll, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addHideShowSeparator = TRUE;
+ }
+
+ // Add a separator after the hide/show menus if at least one exists
+ if (addHideShowSeparator)
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add quit menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_FileQuitItem"), @selector(menuItemHit:),
+ eCommand_ID_Quit, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+ }
+ else {
+ // the current application does not have a DOM node for "Quit". Add one
+ // anyway, in English.
+ NSMenuItem* defaultQuitItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(menuItemHit:)
+ keyEquivalent:@"q"] autorelease];
+ [defaultQuitItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [defaultQuitItem setTag:eCommand_ID_Quit];
+ [sApplicationMenu addItem:defaultQuitItem];
+ }
+ }
+
+ return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsMenuBarX::SetParent(nsIWidget* aParent)
+{
+ mParentWindow = aParent;
+}
+
+//
+// Objective-C class used to allow us to have keyboard commands
+// look like they are doing something but actually do nothing.
+// We allow mouse actions to work normally.
+//
+
+// Controls whether or not native menu items should invoke their commands.
+static BOOL gMenuItemsExecuteCommands = YES;
+
+@implementation GeckoNSMenu
+
+// Keyboard commands should not cause menu items to invoke their
+// commands when there is a key window because we'd rather send
+// the keyboard command to the window. We still have the menus
+// go through the mechanics so they'll give the proper visual
+// feedback.
+- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
+{
+ // We've noticed that Mac OS X expects this check in subclasses before
+ // calling NSMenu's "performKeyEquivalent:".
+ //
+ // There is no case in which we'd need to do anything or return YES
+ // when we have no items so we can just do this check first.
+ if ([self numberOfItems] <= 0) {
+ return NO;
+ }
+
+ NSWindow *keyWindow = [NSApp keyWindow];
+
+ // If there is no key window then just behave normally. This
+ // probably means that this menu is associated with Gecko's
+ // hidden window.
+ if (!keyWindow) {
+ return [super performKeyEquivalent:theEvent];
+ }
+
+ NSResponder *firstResponder = [keyWindow firstResponder];
+
+ gMenuItemsExecuteCommands = NO;
+ [super performKeyEquivalent:theEvent];
+ gMenuItemsExecuteCommands = YES; // return to default
+
+ // Return YES if we invoked a command and there is now no key window or we changed
+ // the first responder. In this case we do not want to propagate the event because
+ // we don't want it handled again.
+ if (![NSApp keyWindow] || [[NSApp keyWindow] firstResponder] != firstResponder) {
+ return YES;
+ }
+
+ // Return NO so that we can handle the event via NSView's "keyDown:".
+ return NO;
+}
+
+@end
+
+//
+// Objective-C class used as action target for menu items
+//
+
+@implementation NativeMenuItemTarget
+
+// called when some menu item in this menu gets hit
+-(IBAction)menuItemHit:(id)sender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!gMenuItemsExecuteCommands) {
+ return;
+ }
+
+ int tag = [sender tag];
+
+ nsMenuGroupOwnerX* menuGroupOwner = nullptr;
+ nsMenuBarX* menuBar = nullptr;
+ MenuItemInfo* info = [sender representedObject];
+
+ if (info) {
+ menuGroupOwner = [info menuGroupOwner];
+ if (!menuGroupOwner) {
+ return;
+ }
+ if (menuGroupOwner->MenuObjectType() == eMenuBarObjectType) {
+ menuBar = static_cast<nsMenuBarX*>(menuGroupOwner);
+ }
+ }
+
+ // Do special processing if this is for an app-global command.
+ if (tag == eCommand_ID_About) {
+ nsIContent* mostSpecificContent = sAboutItemContent;
+ if (menuBar && menuBar->mAboutItemContent)
+ mostSpecificContent = menuBar->mAboutItemContent;
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ return;
+ }
+ else if (tag == eCommand_ID_Prefs) {
+ nsIContent* mostSpecificContent = sPrefItemContent;
+ if (menuBar && menuBar->mPrefItemContent)
+ mostSpecificContent = menuBar->mPrefItemContent;
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ return;
+ }
+ else if (tag == eCommand_ID_HideApp) {
+ [NSApp hide:sender];
+ return;
+ }
+ else if (tag == eCommand_ID_HideOthers) {
+ [NSApp hideOtherApplications:sender];
+ return;
+ }
+ else if (tag == eCommand_ID_ShowAll) {
+ [NSApp unhideAllApplications:sender];
+ return;
+ }
+ else if (tag == eCommand_ID_Quit) {
+ nsIContent* mostSpecificContent = sQuitItemContent;
+ if (menuBar && menuBar->mQuitItemContent)
+ mostSpecificContent = menuBar->mQuitItemContent;
+ // If we have some content for quit we execute it. Otherwise we send a native app terminate
+ // message. If you want to stop a quit from happening, provide quit content and return
+ // the event as unhandled.
+ if (mostSpecificContent) {
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ }
+ else {
+ nsCOMPtr<nsIAppStartup> appStartup = do_GetService(NS_APPSTARTUP_CONTRACTID);
+ if (appStartup) {
+ appStartup->Quit(nsIAppStartup::eAttemptQuit);
+ }
+ }
+ return;
+ }
+
+ // given the commandID, look it up in our hashtable and dispatch to
+ // that menu item.
+ if (menuGroupOwner) {
+ nsMenuItemX* menuItem = menuGroupOwner->GetMenuItemForCommandID(static_cast<uint32_t>(tag));
+ if (menuItem)
+ menuItem->DoCommand();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// Objective-C class used for menu items on the Services menu to allow Gecko
+// to override their standard behavior in order to stop key equivalents from
+// firing in certain instances. When gMenuItemsExecuteCommands is NO, we return
+// a dummy target and action instead of the actual target and action.
+
+@implementation GeckoServicesNSMenuItem
+
+- (id) target
+{
+ id realTarget = [super target];
+ if (gMenuItemsExecuteCommands)
+ return realTarget;
+ else
+ return realTarget ? self : nil;
+}
+
+- (SEL) action
+{
+ SEL realAction = [super action];
+ if (gMenuItemsExecuteCommands)
+ return realAction;
+ else
+ return realAction ? @selector(_doNothing:) : NULL;
+}
+
+- (void) _doNothing:(id)sender
+{
+}
+
+@end
+
+// Objective-C class used as the Services menu so that Gecko can override the
+// standard behavior of the Services menu in order to stop key equivalents
+// from firing in certain instances.
+
+@implementation GeckoServicesNSMenu
+
+- (void)addItem:(NSMenuItem *)newItem
+{
+ [self _overrideClassOfMenuItem:newItem];
+ [super addItem:newItem];
+}
+
+- (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv
+{
+ NSMenuItem * newItem = [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv];
+ [self _overrideClassOfMenuItem:newItem];
+ return newItem;
+}
+
+- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index
+{
+ [self _overrideClassOfMenuItem:newItem];
+ [super insertItem:newItem atIndex:index];
+}
+
+- (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index
+{
+ NSMenuItem * newItem = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index];
+ [self _overrideClassOfMenuItem:newItem];
+ return newItem;
+}
+
+- (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem
+{
+ if ([menuItem class] == [NSMenuItem class])
+ object_setClass(menuItem, [GeckoServicesNSMenuItem class]);
+}
+
+@end
diff --git a/widget/cocoa/nsMenuBaseX.h b/widget/cocoa/nsMenuBaseX.h
new file mode 100644
index 0000000000..5b9f89c560
--- /dev/null
+++ b/widget/cocoa/nsMenuBaseX.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuBaseX_h_
+#define nsMenuBaseX_h_
+
+#import <Foundation/Foundation.h>
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+
+enum nsMenuObjectTypeX {
+ eMenuBarObjectType,
+ eSubmenuObjectType,
+ eMenuItemObjectType,
+ eStandaloneNativeMenuObjectType,
+};
+
+// All menu objects subclass this.
+// Menu bars are owned by their top-level nsIWidgets.
+// All other objects are memory-managed based on the DOM.
+// Content removal deletes them immediately and nothing else should.
+// Do not attempt to hold strong references to them or delete them.
+class nsMenuObjectX
+{
+public:
+ virtual ~nsMenuObjectX() { }
+ virtual nsMenuObjectTypeX MenuObjectType()=0;
+ virtual void* NativeData()=0;
+ nsIContent* Content() { return mContent; }
+
+ /**
+ * Called when an icon of a menu item somewhere in this menu has updated.
+ * Menu objects with parents need to propagate the notification to their
+ * parent.
+ */
+ virtual void IconUpdated() {}
+
+protected:
+ nsCOMPtr<nsIContent> mContent;
+};
+
+
+//
+// Object stored as "representedObject" for all menu items
+//
+
+class nsMenuGroupOwnerX;
+
+@interface MenuItemInfo : NSObject
+{
+ nsMenuGroupOwnerX * mMenuGroupOwner;
+}
+
+- (id) initWithMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner;
+- (nsMenuGroupOwnerX *) menuGroupOwner;
+- (void) setMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner;
+
+@end
+
+
+// Special command IDs that we know Mac OS X does not use for anything else.
+// We use these in place of carbon's IDs for these commands in order to stop
+// Carbon from messing with our event handlers. See bug 346883.
+
+enum {
+ eCommand_ID_About = 1,
+ eCommand_ID_Prefs = 2,
+ eCommand_ID_Quit = 3,
+ eCommand_ID_HideApp = 4,
+ eCommand_ID_HideOthers = 5,
+ eCommand_ID_ShowAll = 6,
+ eCommand_ID_Update = 7,
+ eCommand_ID_Last = 8
+};
+
+#endif // nsMenuBaseX_h_
diff --git a/widget/cocoa/nsMenuGroupOwnerX.h b/widget/cocoa/nsMenuGroupOwnerX.h
new file mode 100644
index 0000000000..657f420b56
--- /dev/null
+++ b/widget/cocoa/nsMenuGroupOwnerX.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuGroupOwnerX_h_
+#define nsMenuGroupOwnerX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMenuBaseX.h"
+#include "nsIMutationObserver.h"
+#include "nsHashKeys.h"
+#include "nsDataHashtable.h"
+#include "nsString.h"
+
+class nsMenuItemX;
+class nsChangeObserver;
+class nsIWidget;
+class nsIContent;
+
+class nsMenuGroupOwnerX : public nsMenuObjectX, public nsIMutationObserver
+{
+public:
+ nsMenuGroupOwnerX();
+
+ nsresult Create(nsIContent * aContent);
+
+ void RegisterForContentChanges(nsIContent* aContent,
+ nsChangeObserver* aMenuObject);
+ void UnregisterForContentChanges(nsIContent* aContent);
+ uint32_t RegisterForCommand(nsMenuItemX* aItem);
+ void UnregisterCommand(uint32_t aCommandID);
+ nsMenuItemX* GetMenuItemForCommandID(uint32_t inCommandID);
+ void AddMenuItemInfoToSet(MenuItemInfo* info);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMUTATIONOBSERVER
+
+protected:
+ virtual ~nsMenuGroupOwnerX();
+
+ nsChangeObserver* LookupContentChangeObserver(nsIContent* aContent);
+
+ uint32_t mCurrentCommandID; // unique command id (per menu-bar) to
+ // give to next item that asks
+
+ // stores observers for content change notification
+ nsDataHashtable<nsPtrHashKey<nsIContent>, nsChangeObserver *> mContentToObserverTable;
+
+ // stores mapping of command IDs to menu objects
+ nsDataHashtable<nsUint32HashKey, nsMenuItemX *> mCommandToMenuObjectTable;
+
+ // Stores references to all the MenuItemInfo objects created with weak
+ // references to us. They may live longer than we do, so when we're
+ // destroyed we need to clear all their weak references. This avoids
+ // crashes in -[NativeMenuItemTarget menuItemHit:]. See bug 1131473.
+ NSMutableSet* mInfoSet;
+};
+
+#endif // nsMenuGroupOwner_h_
diff --git a/widget/cocoa/nsMenuGroupOwnerX.mm b/widget/cocoa/nsMenuGroupOwnerX.mm
new file mode 100644
index 0000000000..661a52bd80
--- /dev/null
+++ b/widget/cocoa/nsMenuGroupOwnerX.mm
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMenuGroupOwnerX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsObjCExceptions.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/dom/Element.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+
+#include "nsINode.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsMenuGroupOwnerX, nsIMutationObserver)
+
+
+nsMenuGroupOwnerX::nsMenuGroupOwnerX()
+: mCurrentCommandID(eCommand_ID_Last)
+{
+ mInfoSet = [[NSMutableSet setWithCapacity:10] retain];
+}
+
+
+nsMenuGroupOwnerX::~nsMenuGroupOwnerX()
+{
+ MOZ_ASSERT(mContentToObserverTable.Count() == 0, "have outstanding mutation observers!\n");
+
+ // The MenuItemInfo objects in mInfoSet may live longer than we do. So when
+ // we get destroyed we need to invalidate all their mMenuGroupOwner pointers.
+ NSEnumerator* counter = [mInfoSet objectEnumerator];
+ MenuItemInfo* info;
+ while ((info = (MenuItemInfo*) [counter nextObject])) {
+ [info setMenuGroupOwner:nil];
+ }
+ [mInfoSet release];
+}
+
+
+nsresult nsMenuGroupOwnerX::Create(nsIContent* aContent)
+{
+ if (!aContent)
+ return NS_ERROR_INVALID_ARG;
+
+ mContent = aContent;
+
+ return NS_OK;
+}
+
+
+//
+// nsIMutationObserver
+//
+
+
+void nsMenuGroupOwnerX::CharacterDataWillChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+
+void nsMenuGroupOwnerX::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+
+void nsMenuGroupOwnerX::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ ContentInserted(aDocument, aContainer, cur, 0);
+ }
+}
+
+
+void nsMenuGroupOwnerX::NodeWillBeDestroyed(const nsINode * aNode)
+{
+}
+
+
+void nsMenuGroupOwnerX::AttributeWillChange(nsIDocument* aDocument,
+ dom::Element* aContent,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+}
+
+void nsMenuGroupOwnerX::NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove)
+{
+}
+
+void nsMenuGroupOwnerX::AttributeChanged(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aElement);
+ if (obs)
+ obs->ObserveAttributeChanged(aDocument, aElement, aAttribute);
+}
+
+
+void nsMenuGroupOwnerX::ContentRemoved(nsIDocument * aDocument,
+ nsIContent * aContainer,
+ nsIContent * aChild,
+ int32_t aIndexInContainer,
+ nsIContent * aPreviousSibling)
+{
+ if (!aContainer) {
+ return;
+ }
+
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aContainer);
+ if (obs)
+ obs->ObserveContentRemoved(aDocument, aChild, aIndexInContainer);
+ else if (aContainer != mContent) {
+ // We do a lookup on the parent container in case things were removed
+ // under a "menupopup" item. That is basically a wrapper for the contents
+ // of a "menu" node.
+ nsCOMPtr<nsIContent> parent = aContainer->GetParent();
+ if (parent) {
+ obs = LookupContentChangeObserver(parent);
+ if (obs)
+ obs->ObserveContentRemoved(aDocument, aChild, aIndexInContainer);
+ }
+ }
+}
+
+
+void nsMenuGroupOwnerX::ContentInserted(nsIDocument * aDocument,
+ nsIContent * aContainer,
+ nsIContent * aChild,
+ int32_t /* unused */)
+{
+ if (!aContainer) {
+ return;
+ }
+
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aContainer);
+ if (obs)
+ obs->ObserveContentInserted(aDocument, aContainer, aChild);
+ else if (aContainer != mContent) {
+ // We do a lookup on the parent container in case things were removed
+ // under a "menupopup" item. That is basically a wrapper for the contents
+ // of a "menu" node.
+ nsCOMPtr<nsIContent> parent = aContainer->GetParent();
+ if (parent) {
+ obs = LookupContentChangeObserver(parent);
+ if (obs)
+ obs->ObserveContentInserted(aDocument, aContainer, aChild);
+ }
+ }
+}
+
+
+void nsMenuGroupOwnerX::ParentChainChanged(nsIContent *aContent)
+{
+}
+
+
+// For change management, we don't use a |nsSupportsHashtable| because
+// we know that the lifetime of all these items is bounded by the
+// lifetime of the menubar. No need to add any more strong refs to the
+// picture because the containment hierarchy already uses strong refs.
+void nsMenuGroupOwnerX::RegisterForContentChanges(nsIContent *aContent,
+ nsChangeObserver *aMenuObject)
+{
+ if (!mContentToObserverTable.Contains(aContent)) {
+ aContent->AddMutationObserver(this);
+ }
+ mContentToObserverTable.Put(aContent, aMenuObject);
+}
+
+
+void nsMenuGroupOwnerX::UnregisterForContentChanges(nsIContent *aContent)
+{
+ if (mContentToObserverTable.Contains(aContent)) {
+ aContent->RemoveMutationObserver(this);
+ }
+ mContentToObserverTable.Remove(aContent);
+}
+
+
+nsChangeObserver* nsMenuGroupOwnerX::LookupContentChangeObserver(nsIContent* aContent)
+{
+ nsChangeObserver * result;
+ if (mContentToObserverTable.Get(aContent, &result))
+ return result;
+ else
+ return nullptr;
+}
+
+
+// Given a menu item, creates a unique 4-character command ID and
+// maps it to the item. Returns the id for use by the client.
+uint32_t nsMenuGroupOwnerX::RegisterForCommand(nsMenuItemX* inMenuItem)
+{
+ // no real need to check for uniqueness. We always start afresh with each
+ // window at 1. Even if we did get close to the reserved Apple command id's,
+ // those don't start until at least ' ', which is integer 538976288. If
+ // we have that many menu items in one window, I think we have other
+ // problems.
+
+ // make id unique
+ ++mCurrentCommandID;
+
+ mCommandToMenuObjectTable.Put(mCurrentCommandID, inMenuItem);
+
+ return mCurrentCommandID;
+}
+
+
+// Removes the mapping between the given 4-character command ID
+// and its associated menu item.
+void nsMenuGroupOwnerX::UnregisterCommand(uint32_t inCommandID)
+{
+ mCommandToMenuObjectTable.Remove(inCommandID);
+}
+
+
+nsMenuItemX* nsMenuGroupOwnerX::GetMenuItemForCommandID(uint32_t inCommandID)
+{
+ nsMenuItemX * result;
+ if (mCommandToMenuObjectTable.Get(inCommandID, &result))
+ return result;
+ else
+ return nullptr;
+}
+
+void nsMenuGroupOwnerX::AddMenuItemInfoToSet(MenuItemInfo* info)
+{
+ [mInfoSet addObject:info];
+}
diff --git a/widget/cocoa/nsMenuItemIconX.h b/widget/cocoa/nsMenuItemIconX.h
new file mode 100644
index 0000000000..7352a94e2a
--- /dev/null
+++ b/widget/cocoa/nsMenuItemIconX.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Retrieves and displays icons in native menu items on Mac OS X.
+ */
+
+#ifndef nsMenuItemIconX_h_
+#define nsMenuItemIconX_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "imgINotificationObserver.h"
+
+class nsIURI;
+class nsIContent;
+class imgRequestProxy;
+class nsMenuObjectX;
+
+#import <Cocoa/Cocoa.h>
+
+class nsMenuItemIconX : public imgINotificationObserver
+{
+public:
+ nsMenuItemIconX(nsMenuObjectX* aMenuItem,
+ nsIContent* aContent,
+ NSMenuItem* aNativeMenuItem);
+private:
+ virtual ~nsMenuItemIconX();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ // SetupIcon succeeds if it was able to set up the icon, or if there should
+ // be no icon, in which case it clears any existing icon but still succeeds.
+ nsresult SetupIcon();
+
+ // GetIconURI fails if the item should not have any icon.
+ nsresult GetIconURI(nsIURI** aIconURI);
+
+ // LoadIcon will set a placeholder image and start a load request for the
+ // icon. The request may not complete until after LoadIcon returns.
+ nsresult LoadIcon(nsIURI* aIconURI);
+
+ // Unless we take precautions, we may outlive the object that created us
+ // (mMenuObject, which owns our native menu item (mNativeMenuItem)).
+ // Destroy() should be called from mMenuObject's destructor to prevent
+ // this from happening. See bug 499600.
+ void Destroy();
+
+protected:
+ nsresult OnFrameComplete(imgIRequest* aRequest);
+
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<imgRequestProxy> mIconRequest;
+ nsMenuObjectX* mMenuObject; // [weak]
+ nsIntRect mImageRegionRect;
+ bool mLoadedIcon;
+ bool mSetIcon;
+ NSMenuItem* mNativeMenuItem; // [weak]
+};
+
+#endif // nsMenuItemIconX_h_
diff --git a/widget/cocoa/nsMenuItemIconX.mm b/widget/cocoa/nsMenuItemIconX.mm
new file mode 100644
index 0000000000..7589c279e5
--- /dev/null
+++ b/widget/cocoa/nsMenuItemIconX.mm
@@ -0,0 +1,466 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Retrieves and displays icons in native menu items on Mac OS X.
+ */
+
+/* exception_defines.h defines 'try' to 'if (true)' which breaks objective-c
+ exceptions and produces errors like: error: unexpected '@' in program'.
+ If we define __EXCEPTIONS exception_defines.h will avoid doing this.
+
+ See bug 666609 for more information.
+
+ We use <limits> to get the libstdc++ version. */
+#include <limits>
+#if __GLIBCXX__ <= 20070719
+#ifndef __EXCEPTIONS
+#define __EXCEPTIONS
+#endif
+#endif
+
+#include "nsMenuItemIconX.h"
+#include "nsObjCExceptions.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMElement.h"
+#include "nsICSSDeclaration.h"
+#include "nsIDOMCSSValue.h"
+#include "nsIDOMCSSPrimitiveValue.h"
+#include "nsIDOMRect.h"
+#include "nsThreadUtils.h"
+#include "nsToolkit.h"
+#include "nsNetUtil.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "nsMenuItemX.h"
+#include "gfxPlatform.h"
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+#include "nsContentUtils.h"
+#include "nsIContentPolicy.h"
+
+using mozilla::dom::Element;
+using mozilla::gfx::SourceSurface;
+
+static const uint32_t kIconWidth = 16;
+static const uint32_t kIconHeight = 16;
+
+typedef NS_STDCALL_FUNCPROTO(nsresult, GetRectSideMethod, nsIDOMRect,
+ GetBottom, (nsIDOMCSSPrimitiveValue**));
+
+NS_IMPL_ISUPPORTS(nsMenuItemIconX, imgINotificationObserver)
+
+nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem,
+ nsIContent* aContent,
+ NSMenuItem* aNativeMenuItem)
+: mContent(aContent)
+, mMenuObject(aMenuItem)
+, mLoadedIcon(false)
+, mSetIcon(false)
+, mNativeMenuItem(aNativeMenuItem)
+{
+ // printf("Creating icon for menu item %d, menu %d, native item is %d\n", aMenuItem, aMenu, aNativeMenuItem);
+}
+
+nsMenuItemIconX::~nsMenuItemIconX()
+{
+ if (mIconRequest)
+ mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+}
+
+// Called from mMenuObjectX's destructor, to prevent us from outliving it
+// (as might otherwise happen if calls to our imgINotificationObserver methods
+// are still outstanding). mMenuObjectX owns our nNativeMenuItem.
+void nsMenuItemIconX::Destroy()
+{
+ if (mIconRequest) {
+ mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+ mMenuObject = nullptr;
+ mNativeMenuItem = nil;
+}
+
+nsresult
+nsMenuItemIconX::SetupIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Still don't have one, then something is wrong, get out of here.
+ if (!mNativeMenuItem) {
+ NS_ERROR("No native menu item");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> iconURI;
+ nsresult rv = GetIconURI(getter_AddRefs(iconURI));
+ if (NS_FAILED(rv)) {
+ // There is no icon for this menu item. An icon might have been set
+ // earlier. Clear it.
+ [mNativeMenuItem setImage:nil];
+
+ return NS_OK;
+ }
+
+ rv = LoadIcon(iconURI);
+ if (NS_FAILED(rv)) {
+ // There is no icon for this menu item, as an error occurred while loading it.
+ // An icon might have been set earlier or the place holder icon may have
+ // been set. Clear it.
+ [mNativeMenuItem setImage:nil];
+ }
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+static int32_t
+GetDOMRectSide(nsIDOMRect* aRect, GetRectSideMethod aMethod)
+{
+ nsCOMPtr<nsIDOMCSSPrimitiveValue> dimensionValue;
+ (aRect->*aMethod)(getter_AddRefs(dimensionValue));
+ if (!dimensionValue)
+ return -1;
+
+ uint16_t primitiveType;
+ nsresult rv = dimensionValue->GetPrimitiveType(&primitiveType);
+ if (NS_FAILED(rv) || primitiveType != nsIDOMCSSPrimitiveValue::CSS_PX)
+ return -1;
+
+ float dimension = 0;
+ rv = dimensionValue->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX,
+ &dimension);
+ if (NS_FAILED(rv))
+ return -1;
+
+ return NSToIntRound(dimension);
+}
+
+nsresult
+nsMenuItemIconX::GetIconURI(nsIURI** aIconURI)
+{
+ if (!mMenuObject)
+ return NS_ERROR_FAILURE;
+
+ // Mac native menu items support having both a checkmark and an icon
+ // simultaneously, but this is unheard of in the cross-platform toolkit,
+ // seemingly because the win32 theme is unable to cope with both at once.
+ // The downside is that it's possible to get a menu item marked with a
+ // native checkmark and a checkmark for an icon. Head off that possibility
+ // by pretending that no icon exists if this is a checkable menu item.
+ if (mMenuObject->MenuObjectType() == eMenuItemObjectType) {
+ nsMenuItemX* menuItem = static_cast<nsMenuItemX*>(mMenuObject);
+ if (menuItem->GetMenuItemType() != eRegularMenuItemType)
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mContent)
+ return NS_ERROR_FAILURE;
+
+ // First, look at the content node's "image" attribute.
+ nsAutoString imageURIString;
+ bool hasImageAttr = mContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::image,
+ imageURIString);
+
+ nsresult rv;
+ nsCOMPtr<nsIDOMCSSValue> cssValue;
+ nsCOMPtr<nsICSSDeclaration> cssStyleDecl;
+ nsCOMPtr<nsIDOMCSSPrimitiveValue> primitiveValue;
+ uint16_t primitiveType;
+ if (!hasImageAttr) {
+ // If the content node has no "image" attribute, get the
+ // "list-style-image" property from CSS.
+ nsCOMPtr<nsIDocument> document = mContent->GetComposedDoc();
+ if (!document)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = document->GetInnerWindow();
+ if (!window)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<Element> domElement = do_QueryInterface(mContent);
+ if (!domElement)
+ return NS_ERROR_FAILURE;
+
+ ErrorResult dummy;
+ cssStyleDecl = window->GetComputedStyle(*domElement, EmptyString(), dummy);
+ dummy.SuppressException();
+ if (!cssStyleDecl)
+ return NS_ERROR_FAILURE;
+
+ NS_NAMED_LITERAL_STRING(listStyleImage, "list-style-image");
+ rv = cssStyleDecl->GetPropertyCSSValue(listStyleImage,
+ getter_AddRefs(cssValue));
+ if (NS_FAILED(rv)) return rv;
+
+ primitiveValue = do_QueryInterface(cssValue);
+ if (!primitiveValue) return NS_ERROR_FAILURE;
+
+ rv = primitiveValue->GetPrimitiveType(&primitiveType);
+ if (NS_FAILED(rv)) return rv;
+ if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_URI)
+ return NS_ERROR_FAILURE;
+
+ rv = primitiveValue->GetStringValue(imageURIString);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Empty the mImageRegionRect initially as the image region CSS could
+ // have been changed and now have an error or have been removed since the
+ // last GetIconURI call.
+ mImageRegionRect.SetEmpty();
+
+ // If this menu item shouldn't have an icon, the string will be empty,
+ // and NS_NewURI will fail.
+ nsCOMPtr<nsIURI> iconURI;
+ rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
+ if (NS_FAILED(rv)) return rv;
+
+ *aIconURI = iconURI;
+ NS_ADDREF(*aIconURI);
+
+ if (!hasImageAttr) {
+ // Check if the icon has a specified image region so that it can be
+ // cropped appropriately before being displayed.
+ NS_NAMED_LITERAL_STRING(imageRegion, "-moz-image-region");
+ rv = cssStyleDecl->GetPropertyCSSValue(imageRegion,
+ getter_AddRefs(cssValue));
+ // Just return NS_OK if there if there is a failure due to no
+ // moz-image region specified so the whole icon will be drawn anyway.
+ if (NS_FAILED(rv)) return NS_OK;
+
+ primitiveValue = do_QueryInterface(cssValue);
+ if (!primitiveValue) return NS_OK;
+
+ rv = primitiveValue->GetPrimitiveType(&primitiveType);
+ if (NS_FAILED(rv)) return NS_OK;
+ if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_RECT)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMRect> imageRegionRect;
+ rv = primitiveValue->GetRectValue(getter_AddRefs(imageRegionRect));
+ if (NS_FAILED(rv)) return NS_OK;
+
+ if (imageRegionRect) {
+ // Return NS_ERROR_FAILURE if the image region is invalid so the image
+ // is not drawn, and behavior is similar to XUL menus.
+ int32_t bottom = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetBottom);
+ int32_t right = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetRight);
+ int32_t top = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetTop);
+ int32_t left = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetLeft);
+
+ if (top < 0 || left < 0 || bottom <= top || right <= left)
+ return NS_ERROR_FAILURE;
+
+ mImageRegionRect.SetRect(left, top, right - left, bottom - top);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuItemIconX::LoadIcon(nsIURI* aIconURI)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mIconRequest) {
+ // Another icon request is already in flight. Kill it.
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+
+ mLoadedIcon = false;
+
+ if (!mContent) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDocument> document = mContent->OwnerDoc();
+
+ nsCOMPtr<nsILoadGroup> loadGroup = document->GetDocumentLoadGroup();
+ if (!loadGroup) return NS_ERROR_FAILURE;
+
+ RefPtr<imgLoader> loader = nsContentUtils::GetImgLoaderForDocument(document);
+ if (!loader) return NS_ERROR_FAILURE;
+
+ if (!mSetIcon) {
+ // Set a completely transparent 16x16 image as the icon on this menu item
+ // as a placeholder. This keeps the menu item text displayed in the same
+ // position that it will be displayed when the real icon is loaded, and
+ // prevents it from jumping around or looking misaligned.
+
+ static bool sInitializedPlaceholder;
+ static NSImage* sPlaceholderIconImage;
+ if (!sInitializedPlaceholder) {
+ sInitializedPlaceholder = true;
+
+ // Note that we only create the one and reuse it forever, so this is not a leak.
+ sPlaceholderIconImage = [[NSImage alloc] initWithSize:NSMakeSize(kIconWidth, kIconHeight)];
+ }
+
+ if (!sPlaceholderIconImage) return NS_ERROR_FAILURE;
+
+ if (mNativeMenuItem)
+ [mNativeMenuItem setImage:sPlaceholderIconImage];
+ }
+
+ nsresult rv = loader->LoadImage(aIconURI, nullptr, nullptr,
+ mozilla::net::RP_Default,
+ nullptr, loadGroup, this,
+ nullptr, nullptr, nsIRequest::LOAD_NORMAL, nullptr,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE, EmptyString(),
+ getter_AddRefs(mIconRequest));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+//
+// imgINotificationObserver
+//
+
+NS_IMETHODIMP
+nsMenuItemIconX::Notify(imgIRequest* aRequest,
+ int32_t aType,
+ const nsIntRect* aData)
+{
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ // Make sure the image loaded successfully.
+ uint32_t status = imgIRequest::STATUS_ERROR;
+ if (NS_FAILED(aRequest->GetImageStatus(&status)) ||
+ (status & imgIRequest::STATUS_ERROR)) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ MOZ_ASSERT(image);
+
+ // Ask the image to decode at its intrinsic size.
+ int32_t width = 0, height = 0;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+ return OnFrameComplete(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ if (mIconRequest && mIconRequest == aRequest) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuItemIconX::OnFrameComplete(imgIRequest* aRequest)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (aRequest != mIconRequest)
+ return NS_ERROR_FAILURE;
+
+ // Only support one frame.
+ if (mLoadedIcon)
+ return NS_OK;
+
+ if (!mNativeMenuItem)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<imgIContainer> imageContainer;
+ aRequest->GetImage(getter_AddRefs(imageContainer));
+ if (!imageContainer) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t origWidth = 0, origHeight = 0;
+ imageContainer->GetWidth(&origWidth);
+ imageContainer->GetHeight(&origHeight);
+
+ // If the image region is invalid, don't draw the image to almost match
+ // the behavior of other platforms.
+ if (!mImageRegionRect.IsEmpty() &&
+ (mImageRegionRect.XMost() > origWidth ||
+ mImageRegionRect.YMost() > origHeight)) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mImageRegionRect.IsEmpty()) {
+ mImageRegionRect.SetRect(0, 0, origWidth, origHeight);
+ }
+
+ RefPtr<SourceSurface> surface =
+ imageContainer->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ CGImageRef origImage = NULL;
+ nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &origImage);
+ if (NS_FAILED(rv) || !origImage) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ bool createSubImage = !(mImageRegionRect.x == 0 && mImageRegionRect.y == 0 &&
+ mImageRegionRect.width == origWidth && mImageRegionRect.height == origHeight);
+
+ CGImageRef finalImage = origImage;
+ if (createSubImage) {
+ // if mImageRegionRect is set using CSS, we need to slice a piece out of the overall
+ // image to use as the icon
+ finalImage = ::CGImageCreateWithImageInRect(origImage,
+ ::CGRectMake(mImageRegionRect.x,
+ mImageRegionRect.y,
+ mImageRegionRect.width,
+ mImageRegionRect.height));
+ ::CGImageRelease(origImage);
+ if (!finalImage) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ NSImage *newImage = nil;
+ rv = nsCocoaUtils::CreateNSImageFromCGImage(finalImage, &newImage);
+ if (NS_FAILED(rv) || !newImage) {
+ [mNativeMenuItem setImage:nil];
+ ::CGImageRelease(finalImage);
+ return NS_ERROR_FAILURE;
+ }
+
+ [newImage setSize:NSMakeSize(kIconWidth, kIconHeight)];
+ [mNativeMenuItem setImage:newImage];
+
+ [newImage release];
+ ::CGImageRelease(finalImage);
+
+ mLoadedIcon = true;
+ mSetIcon = true;
+
+ if (mMenuObject) {
+ mMenuObject->IconUpdated();
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMenuItemX.h b/widget/cocoa/nsMenuItemX.h
new file mode 100644
index 0000000000..67ae32c99c
--- /dev/null
+++ b/widget/cocoa/nsMenuItemX.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuItemX_h_
+#define nsMenuItemX_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsChangeObserver.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsString;
+class nsMenuItemIconX;
+class nsMenuX;
+
+enum {
+ knsMenuItemNoModifier = 0,
+ knsMenuItemShiftModifier = (1 << 0),
+ knsMenuItemAltModifier = (1 << 1),
+ knsMenuItemControlModifier = (1 << 2),
+ knsMenuItemCommandModifier = (1 << 3)
+};
+
+enum EMenuItemType {
+ eRegularMenuItemType = 0,
+ eCheckboxMenuItemType,
+ eRadioMenuItemType,
+ eSeparatorMenuItemType
+};
+
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuItemX : public nsMenuObjectX,
+ public nsChangeObserver
+{
+public:
+ nsMenuItemX();
+ virtual ~nsMenuItemX();
+
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override {return (void*)mNativeMenuItem;}
+ nsMenuObjectTypeX MenuObjectType() override {return eMenuItemObjectType;}
+
+ // nsMenuItemX
+ nsresult Create(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
+ nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode);
+ nsresult SetChecked(bool aIsChecked);
+ EMenuItemType GetMenuItemType();
+ void DoCommand();
+ nsresult DispatchDOMEvent(const nsString &eventName, bool* preventDefaultCalled);
+ void SetupIcon();
+
+protected:
+ void UncheckRadioSiblings(nsIContent* inCheckedElement);
+ void SetKeyEquiv();
+
+ EMenuItemType mType;
+ // nsMenuItemX objects should always have a valid native menu item.
+ NSMenuItem* mNativeMenuItem; // [strong]
+ nsMenuX* mMenuParent; // [weak]
+ nsMenuGroupOwnerX* mMenuGroupOwner; // [weak]
+ nsCOMPtr<nsIContent> mCommandContent;
+ // The icon object should never outlive its creating nsMenuItemX object.
+ RefPtr<nsMenuItemIconX> mIcon;
+ bool mIsChecked;
+};
+
+#endif // nsMenuItemX_h_
diff --git a/widget/cocoa/nsMenuItemX.mm b/widget/cocoa/nsMenuItemX.mm
new file mode 100644
index 0000000000..114b69f430
--- /dev/null
+++ b/widget/cocoa/nsMenuItemX.mm
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMenuItemX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemIconX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+
+#include "nsObjCExceptions.h"
+
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+
+#include "mozilla/dom/Element.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMEvent.h"
+
+nsMenuItemX::nsMenuItemX()
+{
+ mType = eRegularMenuItemType;
+ mNativeMenuItem = nil;
+ mMenuParent = nullptr;
+ mMenuGroupOwner = nullptr;
+ mIsChecked = false;
+
+ MOZ_COUNT_CTOR(nsMenuItemX);
+}
+
+nsMenuItemX::~nsMenuItemX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Prevent the icon object from outliving us.
+ if (mIcon)
+ mIcon->Destroy();
+
+ // autorelease the native menu item so that anything else happening to this
+ // object happens before the native menu item actually dies
+ [mNativeMenuItem autorelease];
+
+ if (mContent)
+ mMenuGroupOwner->UnregisterForContentChanges(mContent);
+ if (mCommandContent)
+ mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
+
+ MOZ_COUNT_DTOR(nsMenuItemX);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuItemX::Create(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
+ nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mType = aItemType;
+ mMenuParent = aParent;
+ mContent = aNode;
+
+ mMenuGroupOwner = aMenuGroupOwner;
+ NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one!");
+
+ mMenuGroupOwner->RegisterForContentChanges(mContent, this);
+
+ nsIDocument *doc = mContent->GetUncomposedDoc();
+
+ // if we have a command associated with this menu item, register for changes
+ // to the command DOM node
+ if (doc) {
+ nsAutoString ourCommand;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::command, ourCommand);
+
+ if (!ourCommand.IsEmpty()) {
+ nsIContent *commandElement = doc->GetElementById(ourCommand);
+
+ if (commandElement) {
+ mCommandContent = commandElement;
+ // register to observe the command DOM element
+ mMenuGroupOwner->RegisterForContentChanges(mCommandContent, this);
+ }
+ }
+ }
+
+ // decide enabled state based on command content if it exists, otherwise do it based
+ // on our own content
+ bool isEnabled;
+ if (mCommandContent)
+ isEnabled = !mCommandContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
+ else
+ isEnabled = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
+
+ // set up the native menu item
+ if (mType == eSeparatorMenuItemType) {
+ mNativeMenuItem = [[NSMenuItem separatorItem] retain];
+ }
+ else {
+ NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(aLabel);
+ mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
+
+ [mNativeMenuItem setEnabled:(BOOL)isEnabled];
+
+ SetChecked(mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters));
+ SetKeyEquiv();
+ }
+
+ mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
+ if (!mIcon)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsMenuItemX::SetChecked(bool aIsChecked)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mIsChecked = aIsChecked;
+
+ // update the content model. This will also handle unchecking our siblings
+ // if we are a radiomenu
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
+ mIsChecked ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"), true);
+
+ // update native menu item
+ if (mIsChecked)
+ [mNativeMenuItem setState:NSOnState];
+ else
+ [mNativeMenuItem setState:NSOffState];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+EMenuItemType nsMenuItemX::GetMenuItemType()
+{
+ return mType;
+}
+
+// Executes the "cached" javaScript command.
+// Returns NS_OK if the command was executed properly, otherwise an error code.
+void nsMenuItemX::DoCommand()
+{
+ // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
+ if (mType == eCheckboxMenuItemType ||
+ (mType == eRadioMenuItemType && !mIsChecked)) {
+ if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
+ nsGkAtoms::_false, eCaseMatters))
+ SetChecked(!mIsChecked);
+ /* the AttributeChanged code will update all the internal state */
+ }
+
+ nsMenuUtilsX::DispatchCommandTo(mContent);
+}
+
+nsresult nsMenuItemX::DispatchDOMEvent(const nsString &eventName, bool *preventDefaultCalled)
+{
+ if (!mContent)
+ return NS_ERROR_FAILURE;
+
+ // get owner document for content
+ nsCOMPtr<nsIDocument> parentDoc = mContent->OwnerDoc();
+
+ // get interface for creating DOM events from content owner document
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(parentDoc);
+ if (!domDoc) {
+ NS_WARNING("Failed to QI parent nsIDocument to nsIDOMDocument");
+ return NS_ERROR_FAILURE;
+ }
+
+ // create DOM event
+ nsCOMPtr<nsIDOMEvent> event;
+ nsresult rv = domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create nsIDOMEvent");
+ return rv;
+ }
+ event->InitEvent(eventName, true, true);
+
+ // mark DOM event as trusted
+ event->SetTrusted(true);
+
+ // send DOM event
+ rv = mContent->DispatchEvent(event, preventDefaultCalled);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to send DOM event via EventTarget");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// Walk the sibling list looking for nodes with the same name and
+// uncheck them all.
+void nsMenuItemX::UncheckRadioSiblings(nsIContent* inCheckedContent)
+{
+ nsAutoString myGroupName;
+ inCheckedContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, myGroupName);
+ if (!myGroupName.Length()) // no groupname, nothing to do
+ return;
+
+ nsCOMPtr<nsIContent> parent = inCheckedContent->GetParent();
+ if (!parent)
+ return;
+
+ // loop over siblings
+ uint32_t count = parent->GetChildCount();
+ for (uint32_t i = 0; i < count; i++) {
+ nsIContent *sibling = parent->GetChildAt(i);
+ if (sibling) {
+ if (sibling != inCheckedContent) { // skip this node
+ // if the current sibling is in the same group, clear it
+ if (sibling->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ myGroupName, eCaseMatters))
+ sibling->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, NS_LITERAL_STRING("false"), true);
+ }
+ }
+ }
+}
+
+void nsMenuItemX::SetKeyEquiv()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Set key shortcut and modifiers
+ nsAutoString keyValue;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
+ if (!keyValue.IsEmpty() && mContent->GetUncomposedDoc()) {
+ nsIContent *keyContent = mContent->GetUncomposedDoc()->GetElementById(keyValue);
+ if (keyContent) {
+ nsAutoString keyChar;
+ bool hasKey = keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
+
+ if (!hasKey || keyChar.IsEmpty()) {
+ nsAutoString keyCodeName;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeName);
+ uint32_t charCode =
+ nsCocoaUtils::ConvertGeckoNameToMacCharCode(keyCodeName);
+ if (charCode) {
+ keyChar.Assign(charCode);
+ }
+ else {
+ keyChar.Assign(NS_LITERAL_STRING(" "));
+ }
+ }
+
+ nsAutoString modifiersStr;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
+ uint8_t modifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
+
+ unsigned int macModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(modifiers);
+ [mNativeMenuItem setKeyEquivalentModifierMask:macModifiers];
+
+ NSString *keyEquivalent = [[NSString stringWithCharacters:(unichar*)keyChar.get()
+ length:keyChar.Length()] lowercaseString];
+ if ([keyEquivalent isEqualToString:@" "])
+ [mNativeMenuItem setKeyEquivalent:@""];
+ else
+ [mNativeMenuItem setKeyEquivalent:keyEquivalent];
+
+ return;
+ }
+ }
+
+ // if the key was removed, clear the key
+ [mNativeMenuItem setKeyEquivalent:@""];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+//
+// nsChangeObserver
+//
+
+void
+nsMenuItemX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aContent)
+ return;
+
+ if (aContent == mContent) { // our own content node changed
+ if (aAttribute == nsGkAtoms::checked) {
+ // if we're a radio menu, uncheck our sibling radio items. No need to
+ // do any of this if we're just a normal check menu.
+ if (mType == eRadioMenuItemType) {
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters))
+ UncheckRadioSiblings(mContent);
+ }
+ mMenuParent->SetRebuild(true);
+ }
+ else if (aAttribute == nsGkAtoms::hidden ||
+ aAttribute == nsGkAtoms::collapsed ||
+ aAttribute == nsGkAtoms::label) {
+ mMenuParent->SetRebuild(true);
+ }
+ else if (aAttribute == nsGkAtoms::key) {
+ SetKeyEquiv();
+ }
+ else if (aAttribute == nsGkAtoms::image) {
+ SetupIcon();
+ }
+ else if (aAttribute == nsGkAtoms::disabled) {
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
+ [mNativeMenuItem setEnabled:NO];
+ else
+ [mNativeMenuItem setEnabled:YES];
+ }
+ }
+ else if (aContent == mCommandContent) {
+ // the only thing that really matters when the menu isn't showing is the
+ // enabled state since it enables/disables keyboard commands
+ if (aAttribute == nsGkAtoms::disabled) {
+ // first we sync our menu item DOM node with the command DOM node
+ nsAutoString commandDisabled;
+ nsAutoString menuDisabled;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled);
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, menuDisabled);
+ if (!commandDisabled.Equals(menuDisabled)) {
+ // The menu's disabled state needs to be updated to match the command.
+ if (commandDisabled.IsEmpty())
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+ else
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled, true);
+ }
+ // now we sync our native menu item with the command DOM node
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
+ [mNativeMenuItem setEnabled:NO];
+ else
+ [mNativeMenuItem setEnabled:YES];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuItemX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, int32_t aIndexInContainer)
+{
+ if (aChild == mCommandContent) {
+ mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
+ mCommandContent = nullptr;
+ }
+
+ mMenuParent->SetRebuild(true);
+}
+
+void nsMenuItemX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
+ nsIContent *aChild)
+{
+ mMenuParent->SetRebuild(true);
+}
+
+void nsMenuItemX::SetupIcon()
+{
+ if (mIcon)
+ mIcon->SetupIcon();
+}
diff --git a/widget/cocoa/nsMenuUtilsX.h b/widget/cocoa/nsMenuUtilsX.h
new file mode 100644
index 0000000000..1571cdfb0a
--- /dev/null
+++ b/widget/cocoa/nsMenuUtilsX.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuUtilsX_h_
+#define nsMenuUtilsX_h_
+
+#include "nscore.h"
+#include "nsMenuBaseX.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsIContent;
+class nsString;
+class nsMenuBarX;
+
+// Namespace containing utility functions used in our native menu implementation.
+namespace nsMenuUtilsX
+{
+ void DispatchCommandTo(nsIContent* aTargetContent);
+ NSString* GetTruncatedCocoaLabel(const nsString& itemLabel);
+ uint8_t GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute);
+ unsigned int MacModifiersForGeckoModifiers(uint8_t geckoModifiers);
+ nsMenuBarX* GetHiddenWindowMenuBar(); // returned object is not retained
+ NSMenuItem* GetStandardEditMenuItem(); // returned object is not retained
+ bool NodeIsHiddenOrCollapsed(nsIContent* inContent);
+ int CalculateNativeInsertionPoint(nsMenuObjectX* aParent, nsMenuObjectX* aChild);
+} // namespace nsMenuUtilsX
+
+#endif // nsMenuUtilsX_h_
diff --git a/widget/cocoa/nsMenuUtilsX.mm b/widget/cocoa/nsMenuUtilsX.mm
new file mode 100644
index 0000000000..db64717127
--- /dev/null
+++ b/widget/cocoa/nsMenuUtilsX.mm
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Event.h"
+#include "nsMenuUtilsX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsStandaloneNativeMenu.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMXULCommandEvent.h"
+#include "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+
+using namespace mozilla;
+
+void nsMenuUtilsX::DispatchCommandTo(nsIContent* aTargetContent)
+{
+ NS_PRECONDITION(aTargetContent, "null ptr");
+
+ nsIDocument* doc = aTargetContent->OwnerDoc();
+ if (doc) {
+ ErrorResult rv;
+ RefPtr<dom::Event> event =
+ doc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"), rv);
+ nsCOMPtr<nsIDOMXULCommandEvent> command = do_QueryObject(event);
+
+ // FIXME: Should probably figure out how to init this with the actual
+ // pressed keys, but this is a big old edge case anyway. -dwh
+ if (command &&
+ NS_SUCCEEDED(command->InitCommandEvent(NS_LITERAL_STRING("command"),
+ true, true,
+ doc->GetInnerWindow(), 0,
+ false, false, false,
+ false, nullptr))) {
+ event->SetTrusted(true);
+ bool dummy;
+ aTargetContent->DispatchEvent(event, &dummy);
+ }
+ }
+}
+
+NSString* nsMenuUtilsX::GetTruncatedCocoaLabel(const nsString& itemLabel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // We want to truncate long strings to some reasonable pixel length but there is no
+ // good API for doing that which works for all OS versions and architectures. For now
+ // we'll do nothing for consistency and depend on good user interface design to limit
+ // string lengths.
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(itemLabel.get())
+ length:itemLabel.Length()];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+uint8_t nsMenuUtilsX::GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute)
+{
+ uint8_t modifiers = knsMenuItemNoModifier;
+ char* str = ToNewCString(modifiersAttribute);
+ char* newStr;
+ char* token = strtok_r(str, ", \t", &newStr);
+ while (token != NULL) {
+ if (strcmp(token, "shift") == 0)
+ modifiers |= knsMenuItemShiftModifier;
+ else if (strcmp(token, "alt") == 0)
+ modifiers |= knsMenuItemAltModifier;
+ else if (strcmp(token, "control") == 0)
+ modifiers |= knsMenuItemControlModifier;
+ else if ((strcmp(token, "accel") == 0) ||
+ (strcmp(token, "meta") == 0)) {
+ modifiers |= knsMenuItemCommandModifier;
+ }
+ token = strtok_r(newStr, ", \t", &newStr);
+ }
+ free(str);
+
+ return modifiers;
+}
+
+unsigned int nsMenuUtilsX::MacModifiersForGeckoModifiers(uint8_t geckoModifiers)
+{
+ unsigned int macModifiers = 0;
+
+ if (geckoModifiers & knsMenuItemShiftModifier)
+ macModifiers |= NSShiftKeyMask;
+ if (geckoModifiers & knsMenuItemAltModifier)
+ macModifiers |= NSAlternateKeyMask;
+ if (geckoModifiers & knsMenuItemControlModifier)
+ macModifiers |= NSControlKeyMask;
+ if (geckoModifiers & knsMenuItemCommandModifier)
+ macModifiers |= NSCommandKeyMask;
+
+ return macModifiers;
+}
+
+nsMenuBarX* nsMenuUtilsX::GetHiddenWindowMenuBar()
+{
+ nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget();
+ if (hiddenWindowWidgetNoCOMPtr)
+ return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)->GetMenuBar();
+ else
+ return nullptr;
+}
+
+// It would be nice if we could localize these edit menu names.
+NSMenuItem* nsMenuUtilsX::GetStandardEditMenuItem()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // In principle we should be able to allocate this once and then always
+ // return the same object. But weird interactions happen between native
+ // app-modal dialogs and Gecko-modal dialogs that open above them. So what
+ // we return here isn't always released before it needs to be added to
+ // another menu. See bmo bug 468393.
+ NSMenuItem* standardEditMenuItem =
+ [[[NSMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""] autorelease];
+ NSMenu* standardEditMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
+ [standardEditMenuItem setSubmenu:standardEditMenu];
+ [standardEditMenu release];
+
+ // Add Undo
+ NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"];
+ [standardEditMenu addItem:undoItem];
+ [undoItem release];
+
+ // Add Redo
+ NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:@"Z"];
+ [standardEditMenu addItem:redoItem];
+ [redoItem release];
+
+ // Add separator
+ [standardEditMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add Cut
+ NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"];
+ [standardEditMenu addItem:cutItem];
+ [cutItem release];
+
+ // Add Copy
+ NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
+ [standardEditMenu addItem:copyItem];
+ [copyItem release];
+
+ // Add Paste
+ NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"];
+ [standardEditMenu addItem:pasteItem];
+ [pasteItem release];
+
+ // Add Delete
+ NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""];
+ [standardEditMenu addItem:deleteItem];
+ [deleteItem release];
+
+ // Add Select All
+ NSMenuItem* selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
+ [standardEditMenu addItem:selectAllItem];
+ [selectAllItem release];
+
+ return standardEditMenuItem;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* inContent)
+{
+ return (inContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters) ||
+ inContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::collapsed,
+ nsGkAtoms::_true, eCaseMatters));
+}
+
+// Determines how many items are visible among the siblings in a menu that are
+// before the given child. This will not count the application menu.
+int nsMenuUtilsX::CalculateNativeInsertionPoint(nsMenuObjectX* aParent,
+ nsMenuObjectX* aChild)
+{
+ int insertionPoint = 0;
+ nsMenuObjectTypeX parentType = aParent->MenuObjectType();
+ if (parentType == eMenuBarObjectType) {
+ nsMenuBarX* menubarParent = static_cast<nsMenuBarX*>(aParent);
+ uint32_t numMenus = menubarParent->GetMenuCount();
+ for (uint32_t i = 0; i < numMenus; i++) {
+ nsMenuX* currMenu = menubarParent->GetMenuAt(i);
+ if (currMenu == aChild)
+ return insertionPoint; // we found ourselves, break out
+ if (currMenu && [currMenu->NativeMenuItem() menu])
+ insertionPoint++;
+ }
+ }
+ else if (parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ nsMenuX* menuParent;
+ if (parentType == eSubmenuObjectType)
+ menuParent = static_cast<nsMenuX*>(aParent);
+ else
+ menuParent = static_cast<nsStandaloneNativeMenu*>(aParent)->GetMenuXObject();
+
+ uint32_t numItems = menuParent->GetItemCount();
+ for (uint32_t i = 0; i < numItems; i++) {
+ // Using GetItemAt instead of GetVisibleItemAt to avoid O(N^2)
+ nsMenuObjectX* currItem = menuParent->GetItemAt(i);
+ if (currItem == aChild)
+ return insertionPoint; // we found ourselves, break out
+ NSMenuItem* nativeItem = nil;
+ nsMenuObjectTypeX currItemType = currItem->MenuObjectType();
+ if (currItemType == eSubmenuObjectType)
+ nativeItem = static_cast<nsMenuX*>(currItem)->NativeMenuItem();
+ else
+ nativeItem = (NSMenuItem*)(currItem->NativeData());
+ if ([nativeItem menu])
+ insertionPoint++;
+ }
+ }
+ return insertionPoint;
+}
diff --git a/widget/cocoa/nsMenuX.h b/widget/cocoa/nsMenuX.h
new file mode 100644
index 0000000000..7b5146a0b8
--- /dev/null
+++ b/widget/cocoa/nsMenuX.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsMenuX_h_
+#define nsMenuX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsCOMPtr.h"
+#include "nsChangeObserver.h"
+
+class nsMenuX;
+class nsMenuItemIconX;
+class nsMenuItemX;
+class nsIWidget;
+
+// MenuDelegate is used to receive Cocoa notifications for setting
+// up carbon events. Protocol is defined as of 10.6 SDK.
+@interface MenuDelegate : NSObject < NSMenuDelegate >
+{
+ nsMenuX* mGeckoMenu; // weak ref
+}
+- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu;
+@end
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuX : public nsMenuObjectX,
+ public nsChangeObserver
+{
+public:
+ nsMenuX();
+ virtual ~nsMenuX();
+
+ // If > 0, the OS is indexing all the app's menus (triggered by opening
+ // Help menu on Leopard and higher). There are some things that are
+ // unsafe to do while this is happening.
+ static int32_t sIndexingMenuLevel;
+
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override {return (void*)mNativeMenu;}
+ nsMenuObjectTypeX MenuObjectType() override {return eSubmenuObjectType;}
+ void IconUpdated() override { mParent->IconUpdated(); }
+
+ // nsMenuX
+ nsresult Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode);
+ uint32_t GetItemCount();
+ nsMenuObjectX* GetItemAt(uint32_t aPos);
+ nsresult GetVisibleItemCount(uint32_t &aCount);
+ nsMenuObjectX* GetVisibleItemAt(uint32_t aPos);
+ nsEventStatus MenuOpened();
+ void MenuClosed();
+ void SetRebuild(bool aMenuEvent);
+ NSMenuItem* NativeMenuItem();
+ nsresult SetupIcon();
+
+ static bool IsXULHelpMenu(nsIContent* aMenuContent);
+
+protected:
+ void MenuConstruct();
+ nsresult RemoveAll();
+ nsresult SetEnabled(bool aIsEnabled);
+ nsresult GetEnabled(bool* aIsEnabled);
+ void GetMenuPopupContent(nsIContent** aResult);
+ bool OnOpen();
+ bool OnClose();
+ nsresult AddMenuItem(nsMenuItemX* aMenuItem);
+ nsMenuX* AddMenu(mozilla::UniquePtr<nsMenuX> aMenu);
+ void LoadMenuItem(nsIContent* inMenuItemContent);
+ void LoadSubMenu(nsIContent* inMenuContent);
+ GeckoNSMenu* CreateMenuWithGeckoString(nsString& menuTitle);
+
+ nsTArray<mozilla::UniquePtr<nsMenuObjectX>> mMenuObjectsArray;
+ nsString mLabel;
+ uint32_t mVisibleItemsCount; // cache
+ nsMenuObjectX* mParent; // [weak]
+ nsMenuGroupOwnerX* mMenuGroupOwner; // [weak]
+ // The icon object should never outlive its creating nsMenuX object.
+ RefPtr<nsMenuItemIconX> mIcon;
+ GeckoNSMenu* mNativeMenu; // [strong]
+ MenuDelegate* mMenuDelegate; // [strong]
+ // nsMenuX objects should always have a valid native menu item.
+ NSMenuItem* mNativeMenuItem; // [strong]
+ bool mIsEnabled;
+ bool mDestroyHandlerCalled;
+ bool mNeedsRebuild;
+ bool mConstructed;
+ bool mVisible;
+ bool mXBLAttached;
+};
+
+#endif // nsMenuX_h_
diff --git a/widget/cocoa/nsMenuX.mm b/widget/cocoa/nsMenuX.mm
new file mode 100644
index 0000000000..757221eac6
--- /dev/null
+++ b/widget/cocoa/nsMenuX.mm
@@ -0,0 +1,1051 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsMenuItemIconX.h"
+#include "nsStandaloneNativeMenu.h"
+
+#include "nsObjCExceptions.h"
+
+#include "nsToolkit.h"
+#include "nsCocoaUtils.h"
+#include "nsCOMPtr.h"
+#include "prinrval.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "plstr.h"
+#include "nsGkAtoms.h"
+#include "nsCRT.h"
+#include "nsBaseWidget.h"
+
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDocumentObserver.h"
+#include "nsIComponentManager.h"
+#include "nsIRollupListener.h"
+#include "nsIDOMElement.h"
+#include "nsBindingManager.h"
+#include "nsIServiceManager.h"
+#include "nsXULPopupManager.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "jsapi.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsIXPConnect.h"
+
+#include "mozilla/MouseEvents.h"
+
+using namespace mozilla;
+
+static bool gConstructingMenu = false;
+static bool gMenuMethodsSwizzled = false;
+
+int32_t nsMenuX::sIndexingMenuLevel = 0;
+
+
+//
+// Objective-C class used for representedObject
+//
+
+@implementation MenuItemInfo
+
+- (id) initWithMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner
+{
+ if ((self = [super init]) != nil) {
+ [self setMenuGroupOwner:aMenuGroupOwner];
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [self setMenuGroupOwner:nullptr];
+ [super dealloc];
+}
+
+- (nsMenuGroupOwnerX *) menuGroupOwner
+{
+ return mMenuGroupOwner;
+}
+
+- (void) setMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner
+{
+ // weak reference as the nsMenuGroupOwnerX owns all of its sub-objects
+ mMenuGroupOwner = aMenuGroupOwner;
+ if (aMenuGroupOwner) {
+ aMenuGroupOwner->AddMenuItemInfoToSet(self);
+ }
+}
+
+@end
+
+
+//
+// nsMenuX
+//
+
+nsMenuX::nsMenuX()
+: mVisibleItemsCount(0), mParent(nullptr), mMenuGroupOwner(nullptr),
+ mNativeMenu(nil), mNativeMenuItem(nil), mIsEnabled(true),
+ mDestroyHandlerCalled(false), mNeedsRebuild(true),
+ mConstructed(false), mVisible(true), mXBLAttached(false)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!gMenuMethodsSwizzled) {
+ nsToolkit::SwizzleMethods([NSMenu class], @selector(_addItem:toTable:),
+ @selector(nsMenuX_NSMenu_addItem:toTable:), true);
+ nsToolkit::SwizzleMethods([NSMenu class], @selector(_removeItem:fromTable:),
+ @selector(nsMenuX_NSMenu_removeItem:fromTable:), true);
+ // On SnowLeopard the Shortcut framework (which contains the
+ // SCTGRLIndex class) is loaded on demand, whenever the user first opens
+ // a menu (which normally hasn't happened yet). So we need to load it
+ // here explicitly.
+ dlopen("/System/Library/PrivateFrameworks/Shortcut.framework/Shortcut", RTLD_LAZY);
+ Class SCTGRLIndexClass = ::NSClassFromString(@"SCTGRLIndex");
+ nsToolkit::SwizzleMethods(SCTGRLIndexClass, @selector(indexMenuBarDynamically),
+ @selector(nsMenuX_SCTGRLIndex_indexMenuBarDynamically));
+
+ gMenuMethodsSwizzled = true;
+ }
+
+ mMenuDelegate = [[MenuDelegate alloc] initWithGeckoMenu:this];
+
+ if (!nsMenuBarX::sNativeEventTarget)
+ nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
+
+ MOZ_COUNT_CTOR(nsMenuX);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsMenuX::~nsMenuX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Prevent the icon object from outliving us.
+ if (mIcon)
+ mIcon->Destroy();
+
+ RemoveAll();
+
+ [mNativeMenu setDelegate:nil];
+ [mNativeMenu release];
+ [mMenuDelegate release];
+ // autorelease the native menu item so that anything else happening to this
+ // object happens before the native menu item actually dies
+ [mNativeMenuItem autorelease];
+
+ // alert the change notifier we don't care no more
+ if (mContent)
+ mMenuGroupOwner->UnregisterForContentChanges(mContent);
+
+ MOZ_COUNT_DTOR(nsMenuX);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuX::Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mContent = aNode;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel);
+ mNativeMenu = CreateMenuWithGeckoString(mLabel);
+
+ // register this menu to be notified when changes are made to our content object
+ mMenuGroupOwner = aMenuGroupOwner; // weak ref
+ NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one");
+ mMenuGroupOwner->RegisterForContentChanges(mContent, this);
+
+ mParent = aParent;
+ // our parent could be either a menu bar (if we're toplevel) or a menu (if we're a submenu)
+
+#ifdef DEBUG
+ nsMenuObjectTypeX parentType =
+#endif
+ mParent->MenuObjectType();
+ NS_ASSERTION((parentType == eMenuBarObjectType || parentType == eSubmenuObjectType || parentType == eStandaloneNativeMenuObjectType),
+ "Menu parent not a menu bar, menu, or native menu!");
+
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent))
+ mVisible = false;
+ if (mContent->GetChildCount() == 0)
+ mVisible = false;
+
+ NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
+ mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
+ [mNativeMenuItem setSubmenu:mNativeMenu];
+
+ SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters));
+
+ // We call MenuConstruct here because keyboard commands are dependent upon
+ // native menu items being created. If we only call MenuConstruct when a menu
+ // is actually selected, then we can't access keyboard commands until the
+ // menu gets selected, which is bad.
+ MenuConstruct();
+
+ mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsMenuX::AddMenuItem(nsMenuItemX* aMenuItem)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aMenuItem)
+ return NS_ERROR_INVALID_ARG;
+
+ mMenuObjectsArray.AppendElement(aMenuItem);
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(aMenuItem->Content()))
+ return NS_OK;
+ ++mVisibleItemsCount;
+
+ NSMenuItem* newNativeMenuItem = (NSMenuItem*)aMenuItem->NativeData();
+
+ // add the menu item to this menu
+ [mNativeMenu addItem:newNativeMenuItem];
+
+ // set up target/action
+ [newNativeMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [newNativeMenuItem setAction:@selector(menuItemHit:)];
+
+ // set its command. we get the unique command id from the menubar
+ [newNativeMenuItem setTag:mMenuGroupOwner->RegisterForCommand(aMenuItem)];
+ MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:mMenuGroupOwner];
+ [newNativeMenuItem setRepresentedObject:info];
+ [info release];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsMenuX* nsMenuX::AddMenu(UniquePtr<nsMenuX> aMenu)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // aMenu transfers ownership to mMenuObjectsArray and becomes nullptr, so
+ // we need to keep a raw pointer to access it conveniently.
+ nsMenuX* menu = aMenu.get();
+ mMenuObjectsArray.AppendElement(Move(aMenu));
+
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
+ return menu;
+ }
+
+ ++mVisibleItemsCount;
+
+ // We have to add a menu item and then associate the menu with it
+ NSMenuItem* newNativeMenuItem = menu->NativeMenuItem();
+ if (newNativeMenuItem) {
+ [mNativeMenu addItem:newNativeMenuItem];
+ [newNativeMenuItem setSubmenu:(NSMenu*)menu->NativeData()];
+ }
+
+ return menu;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
+}
+
+// Includes all items, including hidden/collapsed ones
+uint32_t nsMenuX::GetItemCount()
+{
+ return mMenuObjectsArray.Length();
+}
+
+// Includes all items, including hidden/collapsed ones
+nsMenuObjectX* nsMenuX::GetItemAt(uint32_t aPos)
+{
+ if (aPos >= (uint32_t)mMenuObjectsArray.Length())
+ return NULL;
+
+ return mMenuObjectsArray[aPos].get();
+}
+
+// Only includes visible items
+nsresult nsMenuX::GetVisibleItemCount(uint32_t &aCount)
+{
+ aCount = mVisibleItemsCount;
+ return NS_OK;
+}
+
+// Only includes visible items. Note that this is provides O(N) access
+// If you need to iterate or search, consider using GetItemAt and doing your own filtering
+nsMenuObjectX* nsMenuX::GetVisibleItemAt(uint32_t aPos)
+{
+
+ uint32_t count = mMenuObjectsArray.Length();
+ if (aPos >= mVisibleItemsCount || aPos >= count)
+ return NULL;
+
+ // If there are no invisible items, can provide direct access
+ if (mVisibleItemsCount == count)
+ return mMenuObjectsArray[aPos].get();
+
+ // Otherwise, traverse the array until we find the the item we're looking for.
+ nsMenuObjectX* item;
+ uint32_t visibleNodeIndex = 0;
+ for (uint32_t i = 0; i < count; i++) {
+ item = mMenuObjectsArray[i].get();
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(item->Content())) {
+ if (aPos == visibleNodeIndex) {
+ // we found the visible node we're looking for, return it
+ return item;
+ }
+ visibleNodeIndex++;
+ }
+ }
+
+ return NULL;
+}
+
+nsresult nsMenuX::RemoveAll()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mNativeMenu) {
+ // clear command id's
+ int itemCount = [mNativeMenu numberOfItems];
+ for (int i = 0; i < itemCount; i++)
+ mMenuGroupOwner->UnregisterCommand((uint32_t)[[mNativeMenu itemAtIndex:i] tag]);
+ // get rid of Cocoa menu items
+ for (int i = [mNativeMenu numberOfItems] - 1; i >= 0; i--)
+ [mNativeMenu removeItemAtIndex:i];
+ }
+
+ mMenuObjectsArray.Clear();
+ mVisibleItemsCount = 0;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsEventStatus nsMenuX::MenuOpened()
+{
+ // Open the node.
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true);
+
+ // Fire a handler. If we're told to stop, don't build the menu at all
+ bool keepProcessing = OnOpen();
+
+ if (!mNeedsRebuild || !keepProcessing)
+ return nsEventStatus_eConsumeNoDefault;
+
+ if (!mConstructed || mNeedsRebuild) {
+ if (mNeedsRebuild)
+ RemoveAll();
+
+ MenuConstruct();
+ mConstructed = true;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupShown, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+void nsMenuX::MenuClosed()
+{
+ if (mConstructed) {
+ // Don't close if a handler tells us to stop.
+ if (!OnClose())
+ return;
+
+ if (mNeedsRebuild)
+ mConstructed = false;
+
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHidden, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+
+ mDestroyHandlerCalled = true;
+ mConstructed = false;
+ }
+}
+
+void nsMenuX::MenuConstruct()
+{
+ mConstructed = false;
+ gConstructingMenu = true;
+
+ // reset destroy handler flag so that we'll know to fire it next time this menu goes away.
+ mDestroyHandlerCalled = false;
+
+ //printf("nsMenuX::MenuConstruct called for %s = %d \n", NS_LossyConvertUTF16toASCII(mLabel).get(), mNativeMenu);
+
+ // Retrieve our menupopup.
+ nsCOMPtr<nsIContent> menuPopup;
+ GetMenuPopupContent(getter_AddRefs(menuPopup));
+ if (!menuPopup) {
+ gConstructingMenu = false;
+ return;
+ }
+
+ // bug 365405: Manually wrap the menupopup node to make sure it's bounded
+ if (!mXBLAttached) {
+ nsresult rv;
+ nsCOMPtr<nsIXPConnect> xpconnect =
+ do_GetService(nsIXPConnect::GetCID(), &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsIDocument* ownerDoc = menuPopup->OwnerDoc();
+ dom::AutoJSAPI jsapi;
+ if (ownerDoc && jsapi.Init(ownerDoc->GetInnerWindow())) {
+ JSContext* cx = jsapi.cx();
+ JS::RootedObject ignoredObj(cx);
+ xpconnect->WrapNative(cx, JS::CurrentGlobalOrNull(cx), menuPopup,
+ NS_GET_IID(nsISupports), ignoredObj.address());
+ mXBLAttached = true;
+ }
+ }
+ }
+
+ // Iterate over the kids
+ uint32_t count = menuPopup->GetChildCount();
+ for (uint32_t i = 0; i < count; i++) {
+ nsIContent *child = menuPopup->GetChildAt(i);
+ if (child) {
+ // depending on the type, create a menu item, separator, or submenu
+ if (child->IsAnyOfXULElements(nsGkAtoms::menuitem,
+ nsGkAtoms::menuseparator)) {
+ LoadMenuItem(child);
+ } else if (child->IsXULElement(nsGkAtoms::menu)) {
+ LoadSubMenu(child);
+ }
+ }
+ } // for each menu item
+
+ gConstructingMenu = false;
+ mNeedsRebuild = false;
+ // printf("Done building, mMenuObjectsArray.Count() = %d \n", mMenuObjectsArray.Count());
+}
+
+void nsMenuX::SetRebuild(bool aNeedsRebuild)
+{
+ if (!gConstructingMenu)
+ mNeedsRebuild = aNeedsRebuild;
+}
+
+nsresult nsMenuX::SetEnabled(bool aIsEnabled)
+{
+ if (aIsEnabled != mIsEnabled) {
+ // we always want to rebuild when this changes
+ mIsEnabled = aIsEnabled;
+ [mNativeMenuItem setEnabled:(BOOL)mIsEnabled];
+ }
+ return NS_OK;
+}
+
+nsresult nsMenuX::GetEnabled(bool* aIsEnabled)
+{
+ NS_ENSURE_ARG_POINTER(aIsEnabled);
+ *aIsEnabled = mIsEnabled;
+ return NS_OK;
+}
+
+GeckoNSMenu* nsMenuX::CreateMenuWithGeckoString(nsString& menuTitle)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* title = [NSString stringWithCharacters:(UniChar*)menuTitle.get() length:menuTitle.Length()];
+ GeckoNSMenu* myMenu = [[GeckoNSMenu alloc] initWithTitle:title];
+ [myMenu setDelegate:mMenuDelegate];
+
+ // We don't want this menu to auto-enable menu items because then Cocoa
+ // overrides our decisions and things get incorrectly enabled/disabled.
+ [myMenu setAutoenablesItems:NO];
+
+ // we used to install Carbon event handlers here, but since NSMenu* doesn't
+ // create its underlying MenuRef until just before display, we delay until
+ // that happens. Now we install the event handlers when Cocoa notifies
+ // us that a menu is about to display - see the Cocoa MenuDelegate class.
+
+ return myMenu;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void nsMenuX::LoadMenuItem(nsIContent* inMenuItemContent)
+{
+ if (!inMenuItemContent)
+ return;
+
+ nsAutoString menuitemName;
+ inMenuItemContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, menuitemName);
+
+ // printf("menuitem %s \n", NS_LossyConvertUTF16toASCII(menuitemName).get());
+
+ EMenuItemType itemType = eRegularMenuItemType;
+ if (inMenuItemContent->IsXULElement(nsGkAtoms::menuseparator)) {
+ itemType = eSeparatorMenuItemType;
+ }
+ else {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr};
+ switch (inMenuItemContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
+ strings, eCaseMatters)) {
+ case 0: itemType = eCheckboxMenuItemType; break;
+ case 1: itemType = eRadioMenuItemType; break;
+ }
+ }
+
+ // Create the item.
+ nsMenuItemX* menuItem = new nsMenuItemX();
+ if (!menuItem)
+ return;
+
+ nsresult rv = menuItem->Create(this, menuitemName, itemType, mMenuGroupOwner, inMenuItemContent);
+ if (NS_FAILED(rv)) {
+ delete menuItem;
+ return;
+ }
+
+ AddMenuItem(menuItem);
+
+ // This needs to happen after the nsIMenuItem object is inserted into
+ // our item array in AddMenuItem()
+ menuItem->SetupIcon();
+}
+
+void nsMenuX::LoadSubMenu(nsIContent* inMenuContent)
+{
+ auto menu = MakeUnique<nsMenuX>();
+ if (!menu)
+ return;
+
+ nsresult rv = menu->Create(this, mMenuGroupOwner, inMenuContent);
+ if (NS_FAILED(rv))
+ return;
+
+ // |menu|'s ownership is transfer to AddMenu but, if it is successfully
+ // added, we can access it via the returned raw pointer.
+ nsMenuX* menu_ptr = AddMenu(Move(menu));
+
+ // This needs to happen after the nsIMenu object is inserted into
+ // our item array in AddMenu()
+ if (menu_ptr) {
+ menu_ptr->SetupIcon();
+ }
+}
+
+// This menu is about to open. Returns TRUE if we should keep processing the event,
+// FALSE if the handler wants to stop the opening of the menu.
+bool nsMenuX::OnOpen()
+{
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+
+ nsresult rv = NS_OK;
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+ if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault)
+ return false;
+
+ // If the open is going to succeed we need to walk our menu items, checking to
+ // see if any of them have a command attribute. If so, several attributes
+ // must potentially be updated.
+
+ // Get new popup content first since it might have changed as a result of the
+ // eXULPopupShowing event above.
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ if (!popupContent)
+ return true;
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->UpdateMenuItems(popupContent);
+ }
+
+ return true;
+}
+
+// Returns TRUE if we should keep processing the event, FALSE if the handler
+// wants to stop the closing of the menu.
+bool nsMenuX::OnClose()
+{
+ if (mDestroyHandlerCalled)
+ return true;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHiding, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+
+ nsresult rv = NS_OK;
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+
+ mDestroyHandlerCalled = true;
+
+ if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault)
+ return false;
+
+ return true;
+}
+
+// Find the |menupopup| child in the |popup| representing this menu. It should be one
+// of a very few children so we won't be iterating over a bazillion menu items to find
+// it (so the strcmp won't kill us).
+void nsMenuX::GetMenuPopupContent(nsIContent** aResult)
+{
+ if (!aResult)
+ return;
+ *aResult = nullptr;
+
+ // Check to see if we are a "menupopup" node (if we are a native menu).
+ {
+ int32_t dummy;
+ nsCOMPtr<nsIAtom> tag = mContent->OwnerDoc()->BindingManager()->ResolveTag(mContent, &dummy);
+ if (tag == nsGkAtoms::menupopup) {
+ *aResult = mContent;
+ NS_ADDREF(*aResult);
+ return;
+ }
+ }
+
+ // Otherwise check our child nodes.
+
+ uint32_t count = mContent->GetChildCount();
+
+ for (uint32_t i = 0; i < count; i++) {
+ int32_t dummy;
+ nsIContent *child = mContent->GetChildAt(i);
+ nsCOMPtr<nsIAtom> tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy);
+ if (tag == nsGkAtoms::menupopup) {
+ *aResult = child;
+ NS_ADDREF(*aResult);
+ return;
+ }
+ }
+}
+
+NSMenuItem* nsMenuX::NativeMenuItem()
+{
+ return mNativeMenuItem;
+}
+
+bool nsMenuX::IsXULHelpMenu(nsIContent* aMenuContent)
+{
+ bool retval = false;
+ if (aMenuContent) {
+ nsAutoString id;
+ aMenuContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+ if (id.Equals(NS_LITERAL_STRING("helpMenu")))
+ retval = true;
+ }
+ return retval;
+}
+
+//
+// nsChangeObserver
+//
+
+void nsMenuX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent,
+ nsIAtom *aAttribute)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // ignore the |open| attribute, which is by far the most common
+ if (gConstructingMenu || (aAttribute == nsGkAtoms::open))
+ return;
+
+ nsMenuObjectTypeX parentType = mParent->MenuObjectType();
+
+ if (aAttribute == nsGkAtoms::disabled) {
+ SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters));
+ }
+ else if (aAttribute == nsGkAtoms::label) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel);
+
+ // invalidate my parent. If we're a submenu parent, we have to rebuild
+ // the parent menu in order for the changes to be picked up. If we're
+ // a regular menu, just change the title and redraw the menubar.
+ if (parentType == eMenuBarObjectType) {
+ // reuse the existing menu, to avoid rebuilding the root menu bar.
+ NS_ASSERTION(mNativeMenu, "nsMenuX::AttributeChanged: invalid menu handle.");
+ NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
+ [mNativeMenu setTitle:newCocoaLabelString];
+ }
+ else if (parentType == eSubmenuObjectType) {
+ static_cast<nsMenuX*>(mParent)->SetRebuild(true);
+ }
+ else if (parentType == eStandaloneNativeMenuObjectType) {
+ static_cast<nsStandaloneNativeMenu*>(mParent)->GetMenuXObject()->SetRebuild(true);
+ }
+ }
+ else if (aAttribute == nsGkAtoms::hidden || aAttribute == nsGkAtoms::collapsed) {
+ SetRebuild(true);
+
+ bool contentIsHiddenOrCollapsed = nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent);
+
+ // don't do anything if the state is correct already
+ if (contentIsHiddenOrCollapsed != mVisible)
+ return;
+
+ if (contentIsHiddenOrCollapsed) {
+ if (parentType == eMenuBarObjectType ||
+ parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
+ // An exception will get thrown if we try to remove an item that isn't
+ // in the menu.
+ if ([parentMenu indexOfItem:mNativeMenuItem] != -1)
+ [parentMenu removeItem:mNativeMenuItem];
+ mVisible = false;
+ }
+ }
+ else {
+ if (parentType == eMenuBarObjectType ||
+ parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(mParent, this);
+ if (parentType == eMenuBarObjectType) {
+ // Before inserting we need to figure out if we should take the native
+ // application menu into account.
+ nsMenuBarX* mb = static_cast<nsMenuBarX*>(mParent);
+ if (mb->MenuContainsAppMenu())
+ insertionIndex++;
+ }
+ NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
+ [parentMenu insertItem:mNativeMenuItem atIndex:insertionIndex];
+ [mNativeMenuItem setSubmenu:mNativeMenu];
+ mVisible = true;
+ }
+ }
+ }
+ else if (aAttribute == nsGkAtoms::image) {
+ SetupIcon();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild,
+ int32_t aIndexInContainer)
+{
+ if (gConstructingMenu)
+ return;
+
+ SetRebuild(true);
+ mMenuGroupOwner->UnregisterForContentChanges(aChild);
+}
+
+void nsMenuX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
+ nsIContent *aChild)
+{
+ if (gConstructingMenu)
+ return;
+
+ SetRebuild(true);
+}
+
+nsresult nsMenuX::SetupIcon()
+{
+ // In addition to out-of-memory, menus that are children of the menu bar
+ // will not have mIcon set.
+ if (!mIcon)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return mIcon->SetupIcon();
+}
+
+//
+// MenuDelegate Objective-C class, used to set up Carbon events
+//
+
+@implementation MenuDelegate
+
+- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ NS_ASSERTION(geckoMenu, "Cannot initialize native menu delegate with NULL gecko menu! Will crash!");
+ mGeckoMenu = geckoMenu;
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item
+{
+ if (!menu || !item || !mGeckoMenu)
+ return;
+
+ nsMenuObjectX* target = mGeckoMenu->GetVisibleItemAt((uint32_t)[menu indexOfItem:item]);
+ if (target && (target->MenuObjectType() == eMenuItemObjectType)) {
+ nsMenuItemX* targetMenuItem = static_cast<nsMenuItemX*>(target);
+ bool handlerCalledPreventDefault; // but we don't actually care
+ targetMenuItem->DispatchDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), &handlerCalledPreventDefault);
+ }
+}
+
+- (void)menuWillOpen:(NSMenu *)menu
+{
+ if (!mGeckoMenu)
+ return;
+
+ // Don't do anything while the OS is (re)indexing our menus (on Leopard and
+ // higher). This stops the Help menu from being able to search in our
+ // menus, but it also resolves many other problems.
+ if (nsMenuX::sIndexingMenuLevel > 0)
+ return;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ if (rollupListener) {
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget) {
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+ [menu cancelTracking];
+ return;
+ }
+ }
+ mGeckoMenu->MenuOpened();
+}
+
+- (void)menuDidClose:(NSMenu *)menu
+{
+ if (!mGeckoMenu)
+ return;
+
+ // Don't do anything while the OS is (re)indexing our menus (on Leopard and
+ // higher). This stops the Help menu from being able to search in our
+ // menus, but it also resolves many other problems.
+ if (nsMenuX::sIndexingMenuLevel > 0)
+ return;
+
+ mGeckoMenu->MenuClosed();
+}
+
+@end
+
+// OS X Leopard (at least as of 10.5.2) has an obscure bug triggered by some
+// behavior that's present in Mozilla.org browsers but not (as best I can
+// tell) in Apple products like Safari. (It's not yet clear exactly what this
+// behavior is.)
+//
+// The bug is that sometimes you crash on quit in nsMenuX::RemoveAll(), on a
+// call to [NSMenu removeItemAtIndex:]. The crash is caused by trying to
+// access a deleted NSMenuItem object (sometimes (perhaps always?) by trying
+// to send it a _setChangedFlags: message). Though this object was deleted
+// some time ago, it remains registered as a potential target for a particular
+// key equivalent. So when [NSMenu removeItemAtIndex:] removes the current
+// target for that same key equivalent, the OS tries to "activate" the
+// previous target.
+//
+// The underlying reason appears to be that NSMenu's _addItem:toTable: and
+// _removeItem:fromTable: methods (which are used to keep a hashtable of
+// registered key equivalents) don't properly "retain" and "release"
+// NSMenuItem objects as they are added to and removed from the hashtable.
+//
+// Our (hackish) workaround is to shadow the OS's hashtable with another
+// hastable of our own (gShadowKeyEquivDB), and use it to "retain" and
+// "release" NSMenuItem objects as needed. This resolves bmo bugs 422287 and
+// 423669. When (if) Apple fixes this bug, we can remove this workaround.
+
+static NSMutableDictionary *gShadowKeyEquivDB = nil;
+
+// Class for values in gShadowKeyEquivDB.
+
+@interface KeyEquivDBItem : NSObject
+{
+ NSMenuItem *mItem;
+ NSMutableSet *mTables;
+}
+
+- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable;
+- (BOOL)hasTable:(NSMapTable *)aTable;
+- (int)addTable:(NSMapTable *)aTable;
+- (int)removeTable:(NSMapTable *)aTable;
+
+@end
+
+@implementation KeyEquivDBItem
+
+- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!gShadowKeyEquivDB)
+ gShadowKeyEquivDB = [[NSMutableDictionary alloc] init];
+ self = [super init];
+ if (aItem && aTable) {
+ mTables = [[NSMutableSet alloc] init];
+ mItem = [aItem retain];
+ [mTables addObject:[NSValue valueWithPointer:aTable]];
+ } else {
+ mTables = nil;
+ mItem = nil;
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTables)
+ [mTables release];
+ if (mItem)
+ [mItem release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)hasTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [mTables member:[NSValue valueWithPointer:aTable]] ? YES : NO;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// Does nothing if aTable (its index value) is already present in mTables.
+- (int)addTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (aTable)
+ [mTables addObject:[NSValue valueWithPointer:aTable]];
+ return [mTables count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (int)removeTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (aTable) {
+ NSValue *objectToRemove =
+ [mTables member:[NSValue valueWithPointer:aTable]];
+ if (objectToRemove)
+ [mTables removeObject:objectToRemove];
+ }
+ return [mTables count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+@end
+
+@interface NSMenu (MethodSwizzling)
++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable;
++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable;
+@end
+
+@implementation NSMenu (MethodSwizzling)
+
++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (aItem && aTable) {
+ NSValue *key = [NSValue valueWithPointer:aItem];
+ KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key];
+ if (shadowItem) {
+ [shadowItem addTable:aTable];
+ } else {
+ shadowItem = [[KeyEquivDBItem alloc] initWithItem:aItem table:aTable];
+ [gShadowKeyEquivDB setObject:shadowItem forKey:key];
+ // Release after [NSMutableDictionary setObject:forKey:] retains it (so
+ // that it will get dealloced when removeObjectForKey: is called).
+ [shadowItem release];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+
+ [self nsMenuX_NSMenu_addItem:aItem toTable:aTable];
+}
+
++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable
+{
+ [self nsMenuX_NSMenu_removeItem:aItem fromTable:aTable];
+
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (aItem && aTable) {
+ NSValue *key = [NSValue valueWithPointer:aItem];
+ KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key];
+ if (shadowItem && [shadowItem hasTable:aTable]) {
+ if (![shadowItem removeTable:aTable])
+ [gShadowKeyEquivDB removeObjectForKey:key];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// This class is needed to keep track of when the OS is (re)indexing all of
+// our menus. This appears to only happen on Leopard and higher, and can
+// be triggered by opening the Help menu. Some operations are unsafe while
+// this is happening -- notably the calls to [[NSImage alloc]
+// initWithSize:imageRect.size] and [newImage lockFocus] in nsMenuItemIconX::
+// OnStopFrame(). But we don't yet have a complete list, and Apple doesn't
+// yet have any documentation on this subject. (Apple also doesn't yet have
+// any documented way to find the information we seek here.) The "original"
+// of this class (the one whose indexMenuBarDynamically method we hook) is
+// defined in the Shortcut framework in /System/Library/PrivateFrameworks.
+@interface NSObject (SCTGRLIndexMethodSwizzling)
+- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically;
+@end
+
+@implementation NSObject (SCTGRLIndexMethodSwizzling)
+
+- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically
+{
+ // This method appears to be called (once) whenever the OS (re)indexes our
+ // menus. sIndexingMenuLevel is a int32_t just in case it might be
+ // reentered. As it's running, it spawns calls to two undocumented
+ // HIToolbox methods (_SimulateMenuOpening() and _SimulateMenuClosed()),
+ // which "simulate" the opening and closing of our menus without actually
+ // displaying them.
+ ++nsMenuX::sIndexingMenuLevel;
+ [self nsMenuX_SCTGRLIndex_indexMenuBarDynamically];
+ --nsMenuX::sIndexingMenuLevel;
+}
+
+@end
diff --git a/widget/cocoa/nsNativeThemeCocoa.h b/widget/cocoa/nsNativeThemeCocoa.h
new file mode 100644
index 0000000000..23f2bc4d32
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeCocoa.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNativeThemeCocoa_h_
+#define nsNativeThemeCocoa_h_
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsNativeTheme.h"
+
+@class CellDrawView;
+@class NSProgressBarCell;
+@class ContextAwareSearchFieldCell;
+class nsDeviceContext;
+struct SegmentedControlRenderSettings;
+
+namespace mozilla {
+class EventStates;
+} // namespace mozilla
+
+class nsNativeThemeCocoa : private nsNativeTheme,
+ public nsITheme
+{
+public:
+ enum {
+ eThemeGeometryTypeTitlebar = eThemeGeometryTypeUnknown + 1,
+ eThemeGeometryTypeToolbar,
+ eThemeGeometryTypeToolbox,
+ eThemeGeometryTypeWindowButtons,
+ eThemeGeometryTypeFullscreenButton,
+ eThemeGeometryTypeMenu,
+ eThemeGeometryTypeHighlightedMenuItem,
+ eThemeGeometryTypeVibrancyLight,
+ eThemeGeometryTypeVibrancyDark,
+ eThemeGeometryTypeTooltip,
+ eThemeGeometryTypeSheet,
+ eThemeGeometryTypeSourceList,
+ eThemeGeometryTypeSourceListSelection,
+ eThemeGeometryTypeActiveSourceListSelection
+ };
+
+ nsNativeThemeCocoa();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+ NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType, nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult, bool* aIsOverridable) override;
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+ NS_IMETHOD ThemeChanged() override;
+ bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, uint8_t aWidgetType) override;
+ bool WidgetIsContainer(uint8_t aWidgetType) override;
+ bool ThemeDrawsFocusForWidget(uint8_t aWidgetType) override;
+ bool ThemeNeedsComboboxDropmarker() override;
+ virtual bool WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) override;
+ virtual bool NeedToClearBackgroundBehindWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+ virtual bool WidgetProvidesFontSmoothingBackgroundColor(nsIFrame* aFrame, uint8_t aWidgetType,
+ nscolor* aColor) override;
+ virtual ThemeGeometryType ThemeGeometryTypeForWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+ virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) override;
+
+ void DrawProgress(CGContextRef context, const HIRect& inBoxRect,
+ bool inIsIndeterminate, bool inIsHorizontal,
+ double inValue, double inMaxValue, nsIFrame* aFrame);
+
+ static void DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped);
+
+protected:
+ virtual ~nsNativeThemeCocoa();
+
+ nsIntMargin DirectionAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame);
+ nsIFrame* SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter);
+ CGRect SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
+ nsIFrame* aCurrent, nsIFrame* aRight);
+ bool IsWindowSheet(nsIFrame* aFrame);
+
+ // HITheme drawing routines
+ void DrawFrame(CGContextRef context, HIThemeFrameKind inKind,
+ const HIRect& inBoxRect, bool inReadOnly,
+ mozilla::EventStates inState);
+ void DrawMeter(CGContextRef context, const HIRect& inBoxRect,
+ nsIFrame* aFrame);
+ void DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
+ mozilla::EventStates inState, nsIFrame* aFrame,
+ const SegmentedControlRenderSettings& aSettings);
+ void DrawTabPanel(CGContextRef context, const HIRect& inBoxRect, nsIFrame* aFrame);
+ void DrawScale(CGContextRef context, const HIRect& inBoxRect,
+ mozilla::EventStates inState, bool inDirection,
+ bool inIsReverse, int32_t inCurrentValue, int32_t inMinValue,
+ int32_t inMaxValue, nsIFrame* aFrame);
+ void DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
+ const HIRect& inBoxRect, bool inSelected,
+ mozilla::EventStates inState, nsIFrame* aFrame);
+ void DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame, mozilla::EventStates inState);
+ void DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ mozilla::EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame, float aOriginalHeight);
+ void DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
+ mozilla::EventStates inState, nsIFrame* aFrame,
+ const NSSize& aIconSize, NSString* aImageName,
+ bool aCenterHorizontally);
+ void DrawButton(CGContextRef context, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, bool inIsDefault,
+ ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
+ mozilla::EventStates inState, nsIFrame* aFrame);
+ void DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect,
+ mozilla::EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame);
+ void DrawDropdown(CGContextRef context, const HIRect& inBoxRect,
+ mozilla::EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame);
+ void DrawSpinButtons(CGContextRef context, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ mozilla::EventStates inState, nsIFrame* aFrame);
+ void DrawSpinButton(CGContextRef context, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ mozilla::EventStates inState,
+ nsIFrame* aFrame, uint8_t aWidgetType);
+ void DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
+ NSWindow* aWindow);
+ void DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame *aFrame);
+ void DrawResizer(CGContextRef cgContext, const HIRect& aRect, nsIFrame *aFrame);
+
+ // Scrollbars
+ void GetScrollbarPressStates(nsIFrame *aFrame,
+ mozilla::EventStates aButtonStates[]);
+ nsIFrame* GetParentScrollbarFrame(nsIFrame *aFrame);
+ bool IsParentScrollbarRolledOver(nsIFrame* aFrame);
+
+private:
+ NSButtonCell* mDisclosureButtonCell;
+ NSButtonCell* mHelpButtonCell;
+ NSButtonCell* mPushButtonCell;
+ NSButtonCell* mRadioButtonCell;
+ NSButtonCell* mCheckboxCell;
+ ContextAwareSearchFieldCell* mSearchFieldCell;
+ NSPopUpButtonCell* mDropdownCell;
+ NSComboBoxCell* mComboBoxCell;
+ NSProgressBarCell* mProgressBarCell;
+ NSLevelIndicatorCell* mMeterBarCell;
+ CellDrawView* mCellDrawView;
+};
+
+#endif // nsNativeThemeCocoa_h_
diff --git a/widget/cocoa/nsNativeThemeCocoa.mm b/widget/cocoa/nsNativeThemeCocoa.mm
new file mode 100644
index 0000000000..3c86954424
--- /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 0000000000..b1691b5163
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeColors.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNativeThemeColors_h_
+#define nsNativeThemeColors_h_
+
+#include "nsCocoaFeatures.h"
+#import <Cocoa/Cocoa.h>
+
+enum ColorName {
+ toolbarTopBorderGrey,
+ toolbarFillGrey,
+ toolbarBottomBorderGrey,
+};
+
+static const int sLionThemeColors[][2] = {
+ /* { active window, inactive window } */
+ // toolbar:
+ { 0xD0, 0xF0 }, // top separator line
+ { 0xB2, 0xE1 }, // fill color
+ { 0x59, 0x87 }, // bottom separator line
+};
+
+static const int sYosemiteThemeColors[][2] = {
+ /* { active window, inactive window } */
+ // toolbar:
+ { 0xBD, 0xDF }, // top separator line
+ { 0xD3, 0xF6 }, // fill color
+ { 0xB3, 0xD1 }, // bottom separator line
+};
+
+__attribute__((unused))
+static int NativeGreyColorAsInt(ColorName name, BOOL isMain)
+{
+ if (nsCocoaFeatures::OnYosemiteOrLater())
+ return sYosemiteThemeColors[name][isMain ? 0 : 1];
+ return sLionThemeColors[name][isMain ? 0 : 1];
+}
+
+__attribute__((unused))
+static float NativeGreyColorAsFloat(ColorName name, BOOL isMain)
+{
+ return NativeGreyColorAsInt(name, isMain) / 255.0f;
+}
+
+__attribute__((unused))
+static void DrawNativeGreyColorInRect(CGContextRef context, ColorName name,
+ CGRect rect, BOOL isMain)
+{
+ float grey = NativeGreyColorAsFloat(name, isMain);
+ CGContextSetRGBFillColor(context, grey, grey, grey, 1.0f);
+ CGContextFillRect(context, rect);
+}
+
+#endif // nsNativeThemeColors_h_
diff --git a/widget/cocoa/nsPIWidgetCocoa.idl b/widget/cocoa/nsPIWidgetCocoa.idl
new file mode 100644
index 0000000000..a8fd8149ce
--- /dev/null
+++ b/widget/cocoa/nsPIWidgetCocoa.idl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIWidget;
+
+[ptr] native NSWindowPtr(NSWindow);
+
+//
+// nsPIWidgetCocoa
+//
+// A private interface (unfrozen, private to the widget implementation) that
+// gives us access to some extra features on a widget/window.
+//
+[uuid(f75ff69e-3a51-419e-bd29-042f804bc2ed)]
+interface nsPIWidgetCocoa : nsISupports
+{
+ void SendSetZLevelEvent();
+
+ // Find the displayed child sheet (if aShown) or a child sheet that
+ // wants to be displayed (if !aShown)
+ nsIWidget GetChildSheet(in boolean aShown);
+
+ // Get the parent widget (if any) StandardCreate() was called with.
+ nsIWidget GetRealParent();
+
+ // If the object implementing this interface is a sheet, this will return the
+ // native NSWindow it is attached to
+ readonly attribute NSWindowPtr sheetWindowParent;
+
+ // True if window is a sheet
+ readonly attribute boolean isSheet;
+
+}; // nsPIWidgetCocoa
diff --git a/widget/cocoa/nsPrintDialogX.h b/widget/cocoa/nsPrintDialogX.h
new file mode 100644
index 0000000000..470f17d99d
--- /dev/null
+++ b/widget/cocoa/nsPrintDialogX.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintDialog_h_
+#define nsPrintDialog_h_
+
+#include "nsIPrintDialogService.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsIPrintSettings;
+class nsIStringBundle;
+
+class nsPrintDialogServiceX : public nsIPrintDialogService
+{
+public:
+ nsPrintDialogServiceX();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init() override;
+ NS_IMETHOD Show(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aSettings,
+ nsIWebBrowserPrint *aWebBrowserPrint) override;
+ NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aSettings) override;
+
+protected:
+ virtual ~nsPrintDialogServiceX();
+};
+
+@interface PrintPanelAccessoryView : NSView
+{
+ nsIPrintSettings* mSettings;
+ nsIStringBundle* mPrintBundle;
+ NSButton* mPrintSelectionOnlyCheckbox;
+ NSButton* mShrinkToFitCheckbox;
+ NSButton* mPrintBGColorsCheckbox;
+ NSButton* mPrintBGImagesCheckbox;
+ NSButtonCell* mAsLaidOutRadio;
+ NSButtonCell* mSelectedFrameRadio;
+ NSButtonCell* mSeparateFramesRadio;
+ NSPopUpButton* mHeaderLeftList;
+ NSPopUpButton* mHeaderCenterList;
+ NSPopUpButton* mHeaderRightList;
+ NSPopUpButton* mFooterLeftList;
+ NSPopUpButton* mFooterCenterList;
+ NSPopUpButton* mFooterRightList;
+}
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings;
+
+- (void)exportSettings;
+
+@end
+
+@interface PrintPanelAccessoryController : NSViewController <NSPrintPanelAccessorizing>
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings;
+
+- (void)exportSettings;
+
+@end
+
+#endif // nsPrintDialog_h_
diff --git a/widget/cocoa/nsPrintDialogX.mm b/widget/cocoa/nsPrintDialogX.mm
new file mode 100644
index 0000000000..a6d58d5bfb
--- /dev/null
+++ b/widget/cocoa/nsPrintDialogX.mm
@@ -0,0 +1,682 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsPrintDialogX.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsPrintSettingsX.h"
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIWebProgressListener.h"
+#include "nsIStringBundle.h"
+#include "nsIWebBrowserPrint.h"
+#include "nsCRT.h"
+
+#import <Cocoa/Cocoa.h>
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsPrintDialogServiceX, nsIPrintDialogService)
+
+nsPrintDialogServiceX::nsPrintDialogServiceX()
+{
+}
+
+nsPrintDialogServiceX::~nsPrintDialogServiceX()
+{
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::Show(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aSettings,
+ nsIWebBrowserPrint *aWebBrowserPrint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_PRECONDITION(aSettings, "aSettings must not be null");
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aSettings));
+ if (!settingsX)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc
+ = do_GetService("@mozilla.org/gfx/printsettings-service;1");
+
+ // Set the print job title
+ char16_t** docTitles;
+ uint32_t titleCount;
+ nsresult rv = aWebBrowserPrint->EnumerateDocumentNames(&titleCount, &docTitles);
+ if (NS_SUCCEEDED(rv) && titleCount > 0) {
+ CFStringRef cfTitleString = CFStringCreateWithCharacters(NULL, reinterpret_cast<const UniChar*>(docTitles[0]),
+ NS_strlen(docTitles[0]));
+ if (cfTitleString) {
+ ::PMPrintSettingsSetJobName(settingsX->GetPMPrintSettings(), cfTitleString);
+ CFRelease(cfTitleString);
+ }
+ for (int32_t i = titleCount - 1; i >= 0; i--) {
+ free(docTitles[i]);
+ }
+ free(docTitles);
+ docTitles = NULL;
+ titleCount = 0;
+ }
+
+ // Read default print settings from prefs
+ printSettingsSvc->InitPrintSettingsFromPrefs(settingsX, true,
+ nsIPrintSettings::kInitSaveNativeData);
+ NSPrintInfo* printInfo = settingsX->GetCocoaPrintInfo();
+
+ // Put the print info into the current print operation, since that's where
+ // [panel runModal] will look for it. We create the view because otherwise
+ // we'll get unrelated warnings printed to the console.
+ NSView* tmpView = [[NSView alloc] init];
+ NSPrintOperation* printOperation = [NSPrintOperation printOperationWithView:tmpView printInfo:printInfo];
+ [NSPrintOperation setCurrentOperation:printOperation];
+
+ NSPrintPanel* panel = [NSPrintPanel printPanel];
+ [panel setOptions:NSPrintPanelShowsCopies
+ | NSPrintPanelShowsPageRange
+ | NSPrintPanelShowsPaperSize
+ | NSPrintPanelShowsOrientation
+ | NSPrintPanelShowsScaling ];
+ PrintPanelAccessoryController* viewController =
+ [[PrintPanelAccessoryController alloc] initWithSettings:aSettings];
+ [panel addAccessoryController:viewController];
+ [viewController release];
+
+ // Show the dialog.
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int button = [panel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ NSPrintInfo* copy = [[[NSPrintOperation currentOperation] printInfo] copy];
+ if (!copy) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ [NSPrintOperation setCurrentOperation:nil];
+ [tmpView release];
+
+ if (button != NSFileHandlingPanelOKButton)
+ return NS_ERROR_ABORT;
+
+ settingsX->SetCocoaPrintInfo(copy);
+ settingsX->InitUnwriteableMargin();
+
+ // Save settings unless saving is pref'd off
+ if (Preferences::GetBool("print.save_print_settings", false)) {
+ printSettingsSvc->SavePrintSettingsToPrefs(settingsX, true,
+ nsIPrintSettings::kInitSaveNativeData);
+ }
+
+ // Get coordinate space resolution for converting paper size units to inches
+ NSWindow *win = [[NSApplication sharedApplication] mainWindow];
+ if (win) {
+ NSDictionary *devDesc = [win deviceDescription];
+ if (devDesc) {
+ NSSize res = [[devDesc objectForKey: NSDeviceResolution] sizeValue];
+ float scale = [win backingScaleFactor];
+ if (scale > 0) {
+ settingsX->SetInchesScale(res.width / scale, res.height / scale);
+ }
+ }
+ }
+
+ // Export settings.
+ [viewController exportSettings];
+
+ // If "ignore scaling" is checked, overwrite scaling factor with 1.
+ bool isShrinkToFitChecked;
+ settingsX->GetShrinkToFit(&isShrinkToFitChecked);
+ if (isShrinkToFitChecked) {
+ NSMutableDictionary* dict = [copy dictionary];
+ if (dict) {
+ [dict setObject: [NSNumber numberWithFloat: 1]
+ forKey: NSPrintScalingFactor];
+ }
+ // Set the scaling factor to 100% in the NSPrintInfo
+ // object so that it will not affect the paper size
+ // retrieved from the PMPageFormat routines.
+ [copy setScalingFactor:1.0];
+ } else {
+ aSettings->SetScaling([copy scalingFactor]);
+ }
+
+ // Set the adjusted paper size now that we've updated
+ // the scaling factor.
+ settingsX->InitAdjustedPaperSize();
+
+ [copy release];
+
+ int16_t pageRange;
+ aSettings->GetPrintRange(&pageRange);
+ if (pageRange != nsIPrintSettings::kRangeSelection) {
+ PMPrintSettings nativePrintSettings = settingsX->GetPMPrintSettings();
+ UInt32 firstPage, lastPage;
+ OSStatus status = ::PMGetFirstPage(nativePrintSettings, &firstPage);
+ if (status == noErr) {
+ status = ::PMGetLastPage(nativePrintSettings, &lastPage);
+ if (status == noErr && lastPage != UINT32_MAX) {
+ aSettings->SetPrintRange(nsIPrintSettings::kRangeSpecifiedPageRange);
+ aSettings->SetStartPageRange(firstPage);
+ aSettings->SetEndPageRange(lastPage);
+ }
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::ShowPageSetup(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aNSSettings)
+{
+ NS_PRECONDITION(aParent, "aParent must not be null");
+ NS_PRECONDITION(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aNSSettings));
+ if (!settingsX)
+ return NS_ERROR_FAILURE;
+
+ NSPrintInfo* printInfo = settingsX->GetCocoaPrintInfo();
+ NSPageLayout *pageLayout = [NSPageLayout pageLayout];
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int button = [pageLayout runModalWithPrintInfo:printInfo];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ return button == NSFileHandlingPanelOKButton ? NS_OK : NS_ERROR_ABORT;
+}
+
+// Accessory view
+
+@interface PrintPanelAccessoryView (Private)
+
+- (NSString*)localizedString:(const char*)aKey;
+
+- (int16_t)chosenFrameSetting;
+
+- (const char*)headerFooterStringForList:(NSPopUpButton*)aList;
+
+- (void)exportHeaderFooterSettings;
+
+- (void)initBundle;
+
+- (NSTextField*)label:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment;
+
+- (void)addLabel:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment;
+
+- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect;
+
+- (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect;
+
+- (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect;
+
+- (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect
+ selectedItem:(const char16_t*)aCurrentString;
+
+- (void)addOptionsSection;
+
+- (void)addAppearanceSection;
+
+- (void)addFramesSection;
+
+- (void)addHeaderFooterSection;
+
+- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox;
+
+- (NSString*)framesSummaryValue;
+
+- (NSString*)headerSummaryValue;
+
+- (NSString*)footerSummaryValue;
+
+@end
+
+static const char sHeaderFooterTags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
+
+@implementation PrintPanelAccessoryView
+
+// Public methods
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings
+{
+ [super initWithFrame:NSMakeRect(0, 0, 540, 270)];
+
+ mSettings = aSettings;
+ [self initBundle];
+ [self addOptionsSection];
+ [self addAppearanceSection];
+ [self addFramesSection];
+ [self addHeaderFooterSection];
+
+ return self;
+}
+
+- (void)exportSettings
+{
+ mSettings->SetPrintRange([mPrintSelectionOnlyCheckbox state] == NSOnState ?
+ (int16_t)nsIPrintSettings::kRangeSelection :
+ (int16_t)nsIPrintSettings::kRangeAllPages);
+ mSettings->SetShrinkToFit([mShrinkToFitCheckbox state] == NSOnState);
+ mSettings->SetPrintBGColors([mPrintBGColorsCheckbox state] == NSOnState);
+ mSettings->SetPrintBGImages([mPrintBGImagesCheckbox state] == NSOnState);
+ mSettings->SetPrintFrameType([self chosenFrameSetting]);
+
+ [self exportHeaderFooterSettings];
+}
+
+- (void)dealloc
+{
+ NS_IF_RELEASE(mPrintBundle);
+ [super dealloc];
+}
+
+// Localization
+
+- (void)initBundle
+{
+ nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", &mPrintBundle);
+}
+
+- (NSString*)localizedString:(const char*)aKey
+{
+ if (!mPrintBundle)
+ return @"";
+
+ nsXPIDLString intlString;
+ mPrintBundle->GetStringFromName(NS_ConvertUTF8toUTF16(aKey).get(), getter_Copies(intlString));
+ NSMutableString* s = [NSMutableString stringWithUTF8String:NS_ConvertUTF16toUTF8(intlString).get()];
+
+ // Remove all underscores (they're used in the GTK dialog for accesskeys).
+ [s replaceOccurrencesOfString:@"_" withString:@"" options:0 range:NSMakeRange(0, [s length])];
+ return s;
+}
+
+// Widget helpers
+
+- (NSTextField*)label:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment
+{
+ NSTextField* label = [[[NSTextField alloc] initWithFrame:aRect] autorelease];
+ [label setStringValue:[self localizedString:aLabel]];
+ [label setEditable:NO];
+ [label setSelectable:NO];
+ [label setBezeled:NO];
+ [label setBordered:NO];
+ [label setDrawsBackground:NO];
+ [label setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+ [label setAlignment:aAlignment];
+ return label;
+}
+
+- (void)addLabel:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment
+{
+ NSTextField* label = [self label:aLabel withFrame:aRect alignment:aAlignment];
+ [self addSubview:label];
+}
+
+- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect
+{
+ [self addLabel:aLabel withFrame:aRect alignment:NSRightTextAlignment];
+}
+
+- (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect
+{
+ [self addLabel:aLabel withFrame:aRect alignment:NSCenterTextAlignment];
+}
+
+- (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect
+{
+ aRect.origin.y += 4.0f;
+ NSButton* checkbox = [[[NSButton alloc] initWithFrame:aRect] autorelease];
+ [checkbox setButtonType:NSSwitchButton];
+ [checkbox setTitle:[self localizedString:aLabel]];
+ [checkbox setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+ [checkbox sizeToFit];
+ return checkbox;
+}
+
+- (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect
+ selectedItem:(const char16_t*)aCurrentString
+{
+ NSPopUpButton* list = [[[NSPopUpButton alloc] initWithFrame:aRect pullsDown:NO] autorelease];
+ [list setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [[list cell] setControlSize:NSSmallControlSize];
+ NSArray* items =
+ [NSArray arrayWithObjects:[self localizedString:"headerFooterBlank"],
+ [self localizedString:"headerFooterTitle"],
+ [self localizedString:"headerFooterURL"],
+ [self localizedString:"headerFooterDate"],
+ [self localizedString:"headerFooterPage"],
+ [self localizedString:"headerFooterPageTotal"],
+ nil];
+ [list addItemsWithTitles:items];
+
+ NS_ConvertUTF16toUTF8 currentStringUTF8(aCurrentString);
+ for (unsigned int i = 0; i < ArrayLength(sHeaderFooterTags); i++) {
+ if (!strcmp(currentStringUTF8.get(), sHeaderFooterTags[i])) {
+ [list selectItemAtIndex:i];
+ break;
+ }
+ }
+
+ return list;
+}
+
+// Build sections
+
+- (void)addOptionsSection
+{
+ // Title
+ [self addLabel:"optionsTitleMac" withFrame:NSMakeRect(0, 240, 151, 22)];
+
+ // "Print Selection Only"
+ mPrintSelectionOnlyCheckbox = [self checkboxWithLabel:"selectionOnly"
+ andFrame:NSMakeRect(156, 240, 0, 0)];
+
+ bool canPrintSelection;
+ mSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB,
+ &canPrintSelection);
+ [mPrintSelectionOnlyCheckbox setEnabled:canPrintSelection];
+
+ int16_t printRange;
+ mSettings->GetPrintRange(&printRange);
+ if (printRange == nsIPrintSettings::kRangeSelection) {
+ [mPrintSelectionOnlyCheckbox setState:NSOnState];
+ }
+
+ [self addSubview:mPrintSelectionOnlyCheckbox];
+
+ // "Shrink To Fit"
+ mShrinkToFitCheckbox = [self checkboxWithLabel:"shrinkToFit"
+ andFrame:NSMakeRect(156, 218, 0, 0)];
+
+ bool shrinkToFit;
+ mSettings->GetShrinkToFit(&shrinkToFit);
+ [mShrinkToFitCheckbox setState:(shrinkToFit ? NSOnState : NSOffState)];
+
+ [self addSubview:mShrinkToFitCheckbox];
+}
+
+- (void)addAppearanceSection
+{
+ // Title
+ [self addLabel:"appearanceTitleMac" withFrame:NSMakeRect(0, 188, 151, 22)];
+
+ // "Print Background Colors"
+ mPrintBGColorsCheckbox = [self checkboxWithLabel:"printBGColors"
+ andFrame:NSMakeRect(156, 188, 0, 0)];
+
+ bool geckoBool;
+ mSettings->GetPrintBGColors(&geckoBool);
+ [mPrintBGColorsCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
+
+ [self addSubview:mPrintBGColorsCheckbox];
+
+ // "Print Background Images"
+ mPrintBGImagesCheckbox = [self checkboxWithLabel:"printBGImages"
+ andFrame:NSMakeRect(156, 166, 0, 0)];
+
+ mSettings->GetPrintBGImages(&geckoBool);
+ [mPrintBGImagesCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
+
+ [self addSubview:mPrintBGImagesCheckbox];
+}
+
+- (void)addFramesSection
+{
+ // Title
+ [self addLabel:"framesTitleMac" withFrame:NSMakeRect(0, 124, 151, 22)];
+
+ // Radio matrix
+ NSButtonCell *radio = [[NSButtonCell alloc] init];
+ [radio setButtonType:NSRadioButton];
+ [radio setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+ NSMatrix *matrix = [[NSMatrix alloc] initWithFrame:NSMakeRect(156, 81, 400, 66)
+ mode:NSRadioModeMatrix
+ prototype:(NSCell*)radio
+ numberOfRows:3
+ numberOfColumns:1];
+ [radio release];
+ [matrix setCellSize:NSMakeSize(400, 21)];
+ [self addSubview:matrix];
+ [matrix release];
+ NSArray *cellArray = [matrix cells];
+ mAsLaidOutRadio = [cellArray objectAtIndex:0];
+ mSelectedFrameRadio = [cellArray objectAtIndex:1];
+ mSeparateFramesRadio = [cellArray objectAtIndex:2];
+ [mAsLaidOutRadio setTitle:[self localizedString:"asLaidOut"]];
+ [mSelectedFrameRadio setTitle:[self localizedString:"selectedFrame"]];
+ [mSeparateFramesRadio setTitle:[self localizedString:"separateFrames"]];
+
+ // Radio enabled state
+ int16_t frameUIFlag;
+ mSettings->GetHowToEnableFrameUI(&frameUIFlag);
+ if (frameUIFlag == nsIPrintSettings::kFrameEnableNone) {
+ [mAsLaidOutRadio setEnabled:NO];
+ [mSelectedFrameRadio setEnabled:NO];
+ [mSeparateFramesRadio setEnabled:NO];
+ } else if (frameUIFlag == nsIPrintSettings::kFrameEnableAsIsAndEach) {
+ [mSelectedFrameRadio setEnabled:NO];
+ }
+
+ // Radio values
+ int16_t printFrameType;
+ mSettings->GetPrintFrameType(&printFrameType);
+ switch (printFrameType) {
+ case nsIPrintSettings::kFramesAsIs:
+ [mAsLaidOutRadio setState:NSOnState];
+ break;
+ case nsIPrintSettings::kSelectedFrame:
+ [mSelectedFrameRadio setState:NSOnState];
+ break;
+ case nsIPrintSettings::kEachFrameSep:
+ [mSeparateFramesRadio setState:NSOnState];
+ break;
+ }
+}
+
+- (void)addHeaderFooterSection
+{
+ // Labels
+ [self addLabel:"pageHeadersTitleMac" withFrame:NSMakeRect(0, 44, 151, 22)];
+ [self addLabel:"pageFootersTitleMac" withFrame:NSMakeRect(0, 0, 151, 22)];
+ [self addCenteredLabel:"left" withFrame:NSMakeRect(156, 22, 100, 22)];
+ [self addCenteredLabel:"center" withFrame:NSMakeRect(256, 22, 100, 22)];
+ [self addCenteredLabel:"right" withFrame:NSMakeRect(356, 22, 100, 22)];
+
+ // Lists
+ nsXPIDLString sel;
+
+ mSettings->GetHeaderStrLeft(getter_Copies(sel));
+ mHeaderLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderLeftList];
+
+ mSettings->GetHeaderStrCenter(getter_Copies(sel));
+ mHeaderCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderCenterList];
+
+ mSettings->GetHeaderStrRight(getter_Copies(sel));
+ mHeaderRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderRightList];
+
+ mSettings->GetFooterStrLeft(getter_Copies(sel));
+ mFooterLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterLeftList];
+
+ mSettings->GetFooterStrCenter(getter_Copies(sel));
+ mFooterCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterCenterList];
+
+ mSettings->GetFooterStrRight(getter_Copies(sel));
+ mFooterRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterRightList];
+}
+
+// Export settings
+
+- (int16_t)chosenFrameSetting
+{
+ if ([mAsLaidOutRadio state] == NSOnState)
+ return nsIPrintSettings::kFramesAsIs;
+ if ([mSelectedFrameRadio state] == NSOnState)
+ return nsIPrintSettings::kSelectedFrame;
+ if ([mSeparateFramesRadio state] == NSOnState)
+ return nsIPrintSettings::kEachFrameSep;
+ return nsIPrintSettings::kNoFrames;
+}
+
+- (const char*)headerFooterStringForList:(NSPopUpButton*)aList
+{
+ NSInteger index = [aList indexOfSelectedItem];
+ NS_ASSERTION(index < NSInteger(ArrayLength(sHeaderFooterTags)), "Index of dropdown is higher than expected!");
+ return sHeaderFooterTags[index];
+}
+
+- (void)exportHeaderFooterSettings
+{
+ const char* headerFooterStr;
+ headerFooterStr = [self headerFooterStringForList:mHeaderLeftList];
+ mSettings->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mHeaderCenterList];
+ mSettings->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mHeaderRightList];
+ mSettings->SetHeaderStrRight(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mFooterLeftList];
+ mSettings->SetFooterStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mFooterCenterList];
+ mSettings->SetFooterStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mFooterRightList];
+ mSettings->SetFooterStrRight(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+}
+
+// Summary
+
+- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox
+{
+ if (![aCheckbox isEnabled])
+ return [self localizedString:"summaryNAValue"];
+
+ return [aCheckbox state] == NSOnState ?
+ [self localizedString:"summaryOnValue"] :
+ [self localizedString:"summaryOffValue"];
+}
+
+- (NSString*)framesSummaryValue
+{
+ switch([self chosenFrameSetting]) {
+ case nsIPrintSettings::kFramesAsIs:
+ return [self localizedString:"asLaidOut"];
+ case nsIPrintSettings::kSelectedFrame:
+ return [self localizedString:"selectedFrame"];
+ case nsIPrintSettings::kEachFrameSep:
+ return [self localizedString:"separateFrames"];
+ }
+ return [self localizedString:"summaryNAValue"];
+}
+
+- (NSString*)headerSummaryValue
+{
+ return [[mHeaderLeftList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [[mHeaderCenterList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [mHeaderRightList titleOfSelectedItem]]]]];
+}
+
+- (NSString*)footerSummaryValue
+{
+ return [[mFooterLeftList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [[mFooterCenterList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [mFooterRightList titleOfSelectedItem]]]]];
+}
+
+- (NSArray*)localizedSummaryItems
+{
+ return [NSArray arrayWithObjects:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryFramesTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self framesSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summarySelectionOnlyTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mPrintSelectionOnlyCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryShrinkToFitTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mShrinkToFitCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryPrintBGColorsTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mPrintBGColorsCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryPrintBGImagesTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mPrintBGImagesCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryHeaderTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self headerSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryFooterTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self footerSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ nil];
+}
+
+@end
+
+// Accessory controller
+
+@implementation PrintPanelAccessoryController
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings
+{
+ [super initWithNibName:nil bundle:nil];
+
+ NSView* accView = [[PrintPanelAccessoryView alloc] initWithSettings:aSettings];
+ [self setView:accView];
+ [accView release];
+ return self;
+}
+
+- (void)exportSettings
+{
+ return [(PrintPanelAccessoryView*)[self view] exportSettings];
+}
+
+- (NSArray *)localizedSummaryItems
+{
+ return [(PrintPanelAccessoryView*)[self view] localizedSummaryItems];
+}
+
+@end
diff --git a/widget/cocoa/nsPrintOptionsX.h b/widget/cocoa/nsPrintOptionsX.h
new file mode 100644
index 0000000000..e34e75059e
--- /dev/null
+++ b/widget/cocoa/nsPrintOptionsX.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintOptionsX_h_
+#define nsPrintOptionsX_h_
+
+#include "nsPrintOptionsImpl.h"
+
+namespace mozilla
+{
+namespace embedding
+{
+ class PrintData;
+} // namespace embedding
+} // namespace mozilla
+
+class nsPrintOptionsX : public nsPrintOptions
+{
+public:
+ nsPrintOptionsX();
+ virtual ~nsPrintOptionsX();
+
+ /*
+ * These serialize and deserialize methods are not symmetrical in that
+ * printSettingsX != deserialize(serialize(printSettingsX)). This is because
+ * the native print settings stored in the nsPrintSettingsX's NSPrintInfo
+ * object are not fully serialized. Only the values needed for successful
+ * printing are.
+ */
+ NS_IMETHODIMP SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ mozilla::embedding::PrintData* data);
+ NS_IMETHODIMP DeserializeToPrintSettings(const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings);
+
+protected:
+ nsresult _CreatePrintSettings(nsIPrintSettings **_retval);
+ nsresult ReadPrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags);
+ nsresult WritePrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags);
+};
+
+#endif // nsPrintOptionsX_h_
diff --git a/widget/cocoa/nsPrintOptionsX.mm b/widget/cocoa/nsPrintOptionsX.mm
new file mode 100644
index 0000000000..d9aa17b42e
--- /dev/null
+++ b/widget/cocoa/nsPrintOptionsX.mm
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "nsIServiceManager.h"
+#include "nsPrintOptionsX.h"
+#include "nsPrintSettingsX.h"
+
+// The constants for paper orientation were renamed in 10.9. __MAC_10_9 is
+// defined on OS X 10.9 and later. Although 10.8 and earlier are not supported
+// at this time, this allows for building on those older OS versions. The
+// values are consistent across OS versions so the rename does not affect
+// runtime, just compilation.
+#ifdef __MAC_10_9
+#define NS_PAPER_ORIENTATION_PORTRAIT (NSPaperOrientationPortrait)
+#define NS_PAPER_ORIENTATION_LANDSCAPE (NSPaperOrientationLandscape)
+#else
+#define NS_PAPER_ORIENTATION_PORTRAIT (NSPortraitOrientation)
+#define NS_PAPER_ORIENTATION_LANDSCAPE (NSLandscapeOrientation)
+#endif
+
+using namespace mozilla::embedding;
+
+nsPrintOptionsX::nsPrintOptionsX()
+{
+}
+
+nsPrintOptionsX::~nsPrintOptionsX()
+{
+}
+
+NS_IMETHODIMP
+nsPrintOptionsX::SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ PrintData* data)
+{
+ nsresult rv = nsPrintOptions::SerializeToPrintData(aSettings, aWBP, data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aWBP) {
+ // When serializing an nsIWebBrowserPrint, we need to pass up the first
+ // document name. We could pass up the entire collection of document
+ // names, but the OS X printing prompt code only really cares about
+ // the first one, so we just send the first to save IPC traffic.
+ char16_t** docTitles;
+ uint32_t titleCount;
+ rv = aWBP->EnumerateDocumentNames(&titleCount, &docTitles);
+ if (NS_SUCCEEDED(rv) && titleCount > 0) {
+ data->printJobName().Assign(docTitles[0]);
+ }
+
+ for (int32_t i = titleCount - 1; i >= 0; i--) {
+ free(docTitles[i]);
+ }
+ free(docTitles);
+ docTitles = nullptr;
+ }
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aSettings));
+ if (NS_WARN_IF(!settingsX)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSPrintInfo* printInfo = settingsX->GetCocoaPrintInfo();
+ if (NS_WARN_IF(!printInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ double adjustedWidth, adjustedHeight;
+ settingsX->GetAdjustedPaperSize(&adjustedWidth, &adjustedHeight);
+ data->adjustedPaperWidth() = adjustedWidth;
+ data->adjustedPaperHeight() = adjustedHeight;
+
+ NSDictionary* dict = [printInfo dictionary];
+ if (NS_WARN_IF(!dict)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSString* printerName = [dict objectForKey: NSPrintPrinterName];
+ if (printerName) {
+ nsCocoaUtils::GetStringForNSString(printerName, data->printerName());
+ }
+
+ NSString* faxNumber = [dict objectForKey: NSPrintFaxNumber];
+ if (faxNumber) {
+ nsCocoaUtils::GetStringForNSString(faxNumber, data->faxNumber());
+ }
+
+ NSURL* printToFileURL = [dict objectForKey: NSPrintJobSavingURL];
+ if (printToFileURL) {
+ nsCocoaUtils::GetStringForNSString([printToFileURL absoluteString],
+ data->toFileName());
+ }
+
+ NSDate* printTime = [dict objectForKey: NSPrintTime];
+ if (printTime) {
+ NSTimeInterval timestamp = [printTime timeIntervalSinceReferenceDate];
+ data->printTime() = timestamp;
+ }
+
+ NSString* disposition = [dict objectForKey: NSPrintJobDisposition];
+ if (disposition) {
+ nsCocoaUtils::GetStringForNSString(disposition, data->disposition());
+ }
+
+ NSString* paperName = [dict objectForKey: NSPrintPaperName];
+ if (paperName) {
+ nsCocoaUtils::GetStringForNSString(paperName, data->paperName());
+ }
+
+ float scalingFactor = [[dict objectForKey: NSPrintScalingFactor] floatValue];
+ data->scalingFactor() = scalingFactor;
+
+ int32_t orientation;
+ if ([printInfo orientation] == NS_PAPER_ORIENTATION_PORTRAIT) {
+ orientation = nsIPrintSettings::kPortraitOrientation;
+ } else {
+ orientation = nsIPrintSettings::kLandscapeOrientation;
+ }
+ data->orientation() = orientation;
+
+ NSSize paperSize = [printInfo paperSize];
+ float widthScale, heightScale;
+ settingsX->GetInchesScale(&widthScale, &heightScale);
+ if (orientation == nsIPrintSettings::kLandscapeOrientation) {
+ // switch widths and heights
+ data->widthScale() = heightScale;
+ data->heightScale() = widthScale;
+ data->paperWidth() = paperSize.height / heightScale;
+ data->paperHeight() = paperSize.width / widthScale;
+ } else {
+ data->widthScale() = widthScale;
+ data->heightScale() = heightScale;
+ data->paperWidth() = paperSize.width / widthScale;
+ data->paperHeight() = paperSize.height / heightScale;
+ }
+
+ data->numCopies() = [[dict objectForKey: NSPrintCopies] intValue];
+ data->printAllPages() = [[dict objectForKey: NSPrintAllPages] boolValue];
+ data->startPageRange() = [[dict objectForKey: NSPrintFirstPage] intValue];
+ data->endPageRange() = [[dict objectForKey: NSPrintLastPage] intValue];
+ data->mustCollate() = [[dict objectForKey: NSPrintMustCollate] boolValue];
+ data->printReversed() = [[dict objectForKey: NSPrintReversePageOrder] boolValue];
+ data->pagesAcross() = [[dict objectForKey: NSPrintPagesAcross] unsignedShortValue];
+ data->pagesDown() = [[dict objectForKey: NSPrintPagesDown] unsignedShortValue];
+ data->detailedErrorReporting() = [[dict objectForKey: NSPrintDetailedErrorReporting] boolValue];
+ data->addHeaderAndFooter() = [[dict objectForKey: NSPrintHeaderAndFooter] boolValue];
+ data->fileNameExtensionHidden() =
+ [[dict objectForKey: NSPrintJobSavingFileNameExtensionHidden] boolValue];
+
+ bool printSelectionOnly = [[dict objectForKey: NSPrintSelectionOnly] boolValue];
+ aSettings->SetPrintOptions(nsIPrintSettings::kEnableSelectionRB,
+ printSelectionOnly);
+ aSettings->GetPrintOptionsBits(&data->optionFlags());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintOptionsX::DeserializeToPrintSettings(const PrintData& data,
+ nsIPrintSettings* settings)
+{
+ nsresult rv = nsPrintOptions::DeserializeToPrintSettings(data, settings);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(settings));
+ if (NS_WARN_IF(!settingsX)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSPrintInfo* sharedPrintInfo = [NSPrintInfo sharedPrintInfo];
+ if (NS_WARN_IF(!sharedPrintInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSDictionary* sharedDict = [sharedPrintInfo dictionary];
+ if (NS_WARN_IF(!sharedDict)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // We need to create a new NSMutableDictionary to pass to NSPrintInfo with
+ // the values that we got from the other process.
+ NSMutableDictionary* newPrintInfoDict =
+ [NSMutableDictionary dictionaryWithDictionary:sharedDict];
+ if (NS_WARN_IF(!newPrintInfoDict)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NSString* printerName = nsCocoaUtils::ToNSString(data.printerName());
+ if (printerName) {
+ NSPrinter* printer = [NSPrinter printerWithName: printerName];
+ if (printer) {
+ [newPrintInfoDict setObject: printer forKey: NSPrintPrinter];
+ [newPrintInfoDict setObject: printerName forKey: NSPrintPrinterName];
+ }
+ }
+
+ [newPrintInfoDict setObject: [NSNumber numberWithInt: data.numCopies()]
+ forKey: NSPrintCopies];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.printAllPages()]
+ forKey: NSPrintAllPages];
+ [newPrintInfoDict setObject: [NSNumber numberWithInt: data.startPageRange()]
+ forKey: NSPrintFirstPage];
+ [newPrintInfoDict setObject: [NSNumber numberWithInt: data.endPageRange()]
+ forKey: NSPrintLastPage];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.mustCollate()]
+ forKey: NSPrintMustCollate];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.printReversed()]
+ forKey: NSPrintReversePageOrder];
+
+ [newPrintInfoDict setObject: nsCocoaUtils::ToNSString(data.disposition())
+ forKey: NSPrintJobDisposition];
+
+ [newPrintInfoDict setObject: nsCocoaUtils::ToNSString(data.paperName())
+ forKey: NSPrintPaperName];
+
+ [newPrintInfoDict setObject: [NSNumber numberWithFloat: data.scalingFactor()]
+ forKey: NSPrintScalingFactor];
+
+ CGFloat width = data.paperWidth() * data.widthScale();
+ CGFloat height = data.paperHeight() * data.heightScale();
+ [newPrintInfoDict setObject: [NSValue valueWithSize:NSMakeSize(width,height)]
+ forKey: NSPrintPaperSize];
+
+ int paperOrientation;
+ if (data.orientation() == nsIPrintSettings::kPortraitOrientation) {
+ paperOrientation = NS_PAPER_ORIENTATION_PORTRAIT;
+ settings->SetOrientation(nsIPrintSettings::kPortraitOrientation);
+ } else {
+ paperOrientation = NS_PAPER_ORIENTATION_LANDSCAPE;
+ settings->SetOrientation(nsIPrintSettings::kLandscapeOrientation);
+ }
+ [newPrintInfoDict setObject: [NSNumber numberWithInt:paperOrientation]
+ forKey: NSPrintOrientation];
+
+ [newPrintInfoDict setObject: [NSNumber numberWithShort: data.pagesAcross()]
+ forKey: NSPrintPagesAcross];
+ [newPrintInfoDict setObject: [NSNumber numberWithShort: data.pagesDown()]
+ forKey: NSPrintPagesDown];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.detailedErrorReporting()]
+ forKey: NSPrintDetailedErrorReporting];
+ [newPrintInfoDict setObject: nsCocoaUtils::ToNSString(data.faxNumber())
+ forKey: NSPrintFaxNumber];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.addHeaderAndFooter()]
+ forKey: NSPrintHeaderAndFooter];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.fileNameExtensionHidden()]
+ forKey: NSPrintJobSavingFileNameExtensionHidden];
+
+ // At this point, the base class should have properly deserialized the print
+ // options bitfield for nsIPrintSettings, so that it holds the correct value
+ // for kEnableSelectionRB, which we use to set NSPrintSelectionOnly.
+
+ bool printSelectionOnly = false;
+ rv = settings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &printSelectionOnly);
+ if (NS_SUCCEEDED(rv)) {
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: printSelectionOnly]
+ forKey: NSPrintSelectionOnly];
+ } else {
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: NO]
+ forKey: NSPrintSelectionOnly];
+ }
+
+ NSURL* jobSavingURL =
+ [NSURL URLWithString: nsCocoaUtils::ToNSString(data.toFileName())];
+ if (jobSavingURL) {
+ [newPrintInfoDict setObject: jobSavingURL forKey: NSPrintJobSavingURL];
+ }
+
+ NSTimeInterval timestamp = data.printTime();
+ NSDate* printTime = [NSDate dateWithTimeIntervalSinceReferenceDate: timestamp];
+ if (printTime) {
+ [newPrintInfoDict setObject: printTime forKey: NSPrintTime];
+ }
+
+ // Next, we create a new NSPrintInfo with the values in our dictionary.
+ NSPrintInfo* newPrintInfo =
+ [[NSPrintInfo alloc] initWithDictionary: newPrintInfoDict];
+ if (NS_WARN_IF(!newPrintInfo)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // And now swap in the new NSPrintInfo we've just populated.
+ settingsX->SetCocoaPrintInfo(newPrintInfo);
+ [newPrintInfo release];
+
+ settingsX->SetAdjustedPaperSize(data.adjustedPaperWidth(),
+ data.adjustedPaperHeight());
+
+ return NS_OK;
+}
+
+nsresult
+nsPrintOptionsX::ReadPrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags)
+{
+ nsresult rv;
+
+ rv = nsPrintOptions::ReadPrefs(aPS, aPrinterName, aFlags);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrintOptions::ReadPrefs() failed");
+
+ RefPtr<nsPrintSettingsX> printSettingsX(do_QueryObject(aPS));
+ if (!printSettingsX)
+ return NS_ERROR_NO_INTERFACE;
+ rv = printSettingsX->ReadPageFormatFromPrefs();
+
+ return NS_OK;
+}
+
+nsresult nsPrintOptionsX::_CreatePrintSettings(nsIPrintSettings **_retval)
+{
+ nsresult rv;
+ *_retval = nullptr;
+
+ nsPrintSettingsX* printSettings = new nsPrintSettingsX; // does not initially ref count
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = printSettings);
+
+ rv = printSettings->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(*_retval);
+ return rv;
+ }
+
+ InitPrintSettingsFromPrefs(*_retval, false, nsIPrintSettings::kInitSaveAll);
+ return rv;
+}
+
+nsresult
+nsPrintOptionsX::WritePrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags)
+{
+ nsresult rv;
+
+ rv = nsPrintOptions::WritePrefs(aPS, aPrinterName, aFlags);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrintOptions::WritePrefs() failed");
+
+ RefPtr<nsPrintSettingsX> printSettingsX(do_QueryObject(aPS));
+ if (!printSettingsX)
+ return NS_ERROR_NO_INTERFACE;
+ rv = printSettingsX->WritePageFormatToPrefs();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrintSettingsX::WritePageFormatToPrefs() failed");
+
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsPrintSettingsX.h b/widget/cocoa/nsPrintSettingsX.h
new file mode 100644
index 0000000000..1d755b2505
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsX.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintSettingsX_h_
+#define nsPrintSettingsX_h_
+
+#include "nsPrintSettingsImpl.h"
+#import <Cocoa/Cocoa.h>
+
+#define NS_PRINTSETTINGSX_IID \
+{ 0x0DF2FDBD, 0x906D, 0x4726, \
+ { 0x9E, 0x4D, 0xCF, 0xE0, 0x87, 0x8D, 0x70, 0x7C } }
+
+class nsPrintSettingsX : public nsPrintSettings
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PRINTSETTINGSX_IID)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsPrintSettingsX();
+ nsresult Init();
+ NSPrintInfo* GetCocoaPrintInfo() { return mPrintInfo; }
+ void SetCocoaPrintInfo(NSPrintInfo* aPrintInfo);
+ virtual nsresult ReadPageFormatFromPrefs();
+ virtual nsresult WritePageFormatToPrefs();
+ virtual nsresult GetEffectivePageSize(double *aWidth,
+ double *aHeight) override;
+
+ // In addition to setting the paper width and height, these
+ // overrides set the adjusted width and height returned from
+ // GetEffectivePageSize. This is needed when a paper size is
+ // set manually without using a print dialog a la reftest-print.
+ virtual nsresult SetPaperWidth(double aPaperWidth) override;
+ virtual nsresult SetPaperHeight(double aPaperWidth) override;
+
+ PMPrintSettings GetPMPrintSettings();
+ PMPrintSession GetPMPrintSession();
+ PMPageFormat GetPMPageFormat();
+ void SetPMPageFormat(PMPageFormat aPageFormat);
+
+ // Re-initialize mUnwriteableMargin with values from mPageFormat.
+ // Should be called whenever mPageFormat is initialized or overwritten.
+ nsresult InitUnwriteableMargin();
+
+ // Re-initialize mAdjustedPaper{Width,Height} with values from mPageFormat.
+ // Should be called whenever mPageFormat is initialized or overwritten.
+ nsresult InitAdjustedPaperSize();
+
+ void SetInchesScale(float aWidthScale, float aHeightScale);
+ void GetInchesScale(float *aWidthScale, float *aHeightScale);
+
+ void SetAdjustedPaperSize(double aWidth, double aHeight);
+ void GetAdjustedPaperSize(double *aWidth, double *aHeight);
+
+protected:
+ virtual ~nsPrintSettingsX();
+
+ nsPrintSettingsX(const nsPrintSettingsX& src);
+ nsPrintSettingsX& operator=(const nsPrintSettingsX& rhs);
+
+ nsresult _Clone(nsIPrintSettings **_retval) override;
+ nsresult _Assign(nsIPrintSettings *aPS) override;
+
+ // The out param has a ref count of 1 on return so caller needs to PMRelase() when done.
+ OSStatus CreateDefaultPageFormat(PMPrintSession aSession, PMPageFormat& outFormat);
+ OSStatus CreateDefaultPrintSettings(PMPrintSession aSession, PMPrintSettings& outSettings);
+
+ NSPrintInfo* mPrintInfo;
+
+ // Scaling factors used to convert the NSPrintInfo
+ // paper size units to inches
+ float mWidthScale;
+ float mHeightScale;
+ double mAdjustedPaperWidth;
+ double mAdjustedPaperHeight;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintSettingsX, NS_PRINTSETTINGSX_IID)
+
+#endif // nsPrintSettingsX_h_
diff --git a/widget/cocoa/nsPrintSettingsX.mm b/widget/cocoa/nsPrintSettingsX.mm
new file mode 100644
index 0000000000..73a8e78d2b
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsX.mm
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrintSettingsX.h"
+#include "nsObjCExceptions.h"
+
+#include "plbase64.h"
+#include "plstr.h"
+
+#include "nsCocoaUtils.h"
+
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+#define MAC_OS_X_PAGE_SETUP_PREFNAME "print.macosx.pagesetup-2"
+#define COCOA_PAPER_UNITS_PER_INCH 72.0
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsX, nsPrintSettings, nsPrintSettingsX)
+
+nsPrintSettingsX::nsPrintSettingsX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mPrintInfo = [[NSPrintInfo sharedPrintInfo] copy];
+ mWidthScale = COCOA_PAPER_UNITS_PER_INCH;
+ mHeightScale = COCOA_PAPER_UNITS_PER_INCH;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsPrintSettingsX::nsPrintSettingsX(const nsPrintSettingsX& src)
+{
+ *this = src;
+}
+
+nsPrintSettingsX::~nsPrintSettingsX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mPrintInfo release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsPrintSettingsX& nsPrintSettingsX::operator=(const nsPrintSettingsX& rhs)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (this == &rhs) {
+ return *this;
+ }
+
+ nsPrintSettings::operator=(rhs);
+
+ [mPrintInfo release];
+ mPrintInfo = [rhs.mPrintInfo copy];
+
+ return *this;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(*this);
+}
+
+nsresult nsPrintSettingsX::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ InitUnwriteableMargin();
+ InitAdjustedPaperSize();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Should be called whenever the page format changes.
+NS_IMETHODIMP nsPrintSettingsX::InitUnwriteableMargin()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMPaper paper;
+ PMPaperMargins paperMargin;
+ PMPageFormat pageFormat = GetPMPageFormat();
+ ::PMGetPageFormatPaper(pageFormat, &paper);
+ ::PMPaperGetMargins(paper, &paperMargin);
+ mUnwriteableMargin.top = NS_POINTS_TO_INT_TWIPS(paperMargin.top);
+ mUnwriteableMargin.left = NS_POINTS_TO_INT_TWIPS(paperMargin.left);
+ mUnwriteableMargin.bottom = NS_POINTS_TO_INT_TWIPS(paperMargin.bottom);
+ mUnwriteableMargin.right = NS_POINTS_TO_INT_TWIPS(paperMargin.right);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::InitAdjustedPaperSize()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMPageFormat pageFormat = GetPMPageFormat();
+
+ PMRect paperRect;
+ ::PMGetAdjustedPaperRect(pageFormat, &paperRect);
+
+ mAdjustedPaperWidth = paperRect.right - paperRect.left;
+ mAdjustedPaperHeight = paperRect.bottom - paperRect.top;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsPrintSettingsX::SetCocoaPrintInfo(NSPrintInfo* aPrintInfo)
+{
+ if (mPrintInfo != aPrintInfo) {
+ [mPrintInfo release];
+ mPrintInfo = [aPrintInfo retain];
+ }
+}
+
+NS_IMETHODIMP nsPrintSettingsX::ReadPageFormatFromPrefs()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsAutoCString encodedData;
+ nsresult rv =
+ Preferences::GetCString(MAC_OS_X_PAGE_SETUP_PREFNAME, &encodedData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // decode the base64
+ char* decodedData = PL_Base64Decode(encodedData.get(), encodedData.Length(), nullptr);
+ NSData* data = [NSData dataWithBytes:decodedData length:strlen(decodedData)];
+ if (!data)
+ return NS_ERROR_FAILURE;
+
+ PMPageFormat newPageFormat;
+ OSStatus status = ::PMPageFormatCreateWithDataRepresentation((CFDataRef)data, &newPageFormat);
+ if (status == noErr) {
+ SetPMPageFormat(newPageFormat);
+ }
+ InitUnwriteableMargin();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::WritePageFormatToPrefs()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMPageFormat pageFormat = GetPMPageFormat();
+ if (pageFormat == kPMNoPageFormat)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NSData* data = nil;
+ OSStatus err = ::PMPageFormatCreateDataRepresentation(pageFormat, (CFDataRef*)&data, kPMDataFormatXMLDefault);
+ if (err != noErr)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString encodedData;
+ encodedData.Adopt(PL_Base64Encode((char*)[data bytes], [data length], nullptr));
+ if (!encodedData.get())
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return Preferences::SetCString(MAC_OS_X_PAGE_SETUP_PREFNAME, encodedData);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsPrintSettingsX::_Clone(nsIPrintSettings **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsPrintSettingsX *newSettings = new nsPrintSettingsX(*this);
+ if (!newSettings)
+ return NS_ERROR_FAILURE;
+ *_retval = newSettings;
+ NS_ADDREF(*_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::_Assign(nsIPrintSettings *aPS)
+{
+ nsPrintSettingsX *printSettingsX = static_cast<nsPrintSettingsX*>(aPS);
+ if (!printSettingsX)
+ return NS_ERROR_UNEXPECTED;
+ *this = *printSettingsX;
+ return NS_OK;
+}
+
+PMPrintSettings
+nsPrintSettingsX::GetPMPrintSettings()
+{
+ return static_cast<PMPrintSettings>([mPrintInfo PMPrintSettings]);
+}
+
+PMPrintSession
+nsPrintSettingsX::GetPMPrintSession()
+{
+ return static_cast<PMPrintSession>([mPrintInfo PMPrintSession]);
+}
+
+PMPageFormat
+nsPrintSettingsX::GetPMPageFormat()
+{
+ return static_cast<PMPageFormat>([mPrintInfo PMPageFormat]);
+}
+
+void
+nsPrintSettingsX::SetPMPageFormat(PMPageFormat aPageFormat)
+{
+ PMPageFormat oldPageFormat = GetPMPageFormat();
+ ::PMCopyPageFormat(aPageFormat, oldPageFormat);
+ [mPrintInfo updateFromPMPageFormat];
+}
+
+void
+nsPrintSettingsX::SetInchesScale(float aWidthScale, float aHeightScale)
+{
+ if (aWidthScale > 0 && aHeightScale > 0) {
+ mWidthScale = aWidthScale;
+ mHeightScale = aHeightScale;
+ }
+}
+
+void
+nsPrintSettingsX::GetInchesScale(float *aWidthScale, float *aHeightScale)
+{
+ *aWidthScale = mWidthScale;
+ *aHeightScale = mHeightScale;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::SetPaperWidth(double aPaperWidth)
+{
+ mPaperWidth = aPaperWidth;
+ mAdjustedPaperWidth = aPaperWidth * mWidthScale;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::SetPaperHeight(double aPaperHeight)
+{
+ mPaperHeight = aPaperHeight;
+ mAdjustedPaperHeight = aPaperHeight * mHeightScale;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsX::GetEffectivePageSize(double *aWidth, double *aHeight)
+{
+ *aWidth = NS_INCHES_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
+ *aHeight = NS_INCHES_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
+ return NS_OK;
+}
+
+void nsPrintSettingsX::SetAdjustedPaperSize(double aWidth, double aHeight)
+{
+ mAdjustedPaperWidth = aWidth;
+ mAdjustedPaperHeight = aHeight;
+}
+
+void nsPrintSettingsX::GetAdjustedPaperSize(double *aWidth, double *aHeight)
+{
+ *aWidth = mAdjustedPaperWidth;
+ *aHeight = mAdjustedPaperHeight;
+}
diff --git a/widget/cocoa/nsSandboxViolationSink.h b/widget/cocoa/nsSandboxViolationSink.h
new file mode 100644
index 0000000000..35b5d89af5
--- /dev/null
+++ b/widget/cocoa/nsSandboxViolationSink.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSandboxViolationSink_h_
+#define nsSandboxViolationSink_h_
+
+#include <stdint.h>
+
+// Class for tracking sandbox violations. Currently it just logs them to
+// stdout and the system console. In the future it may do more.
+
+// What makes this possible is the fact that Apple' sandboxd calls
+// notify_post("com.apple.sandbox.violation.*") whenever it's notified by the
+// Sandbox kernel extension of a sandbox violation. We register to receive
+// these notifications. But the notifications are empty, and are sent for
+// every violation in every process. So we need to do more to get only "our"
+// violations, and to find out what kind of violation they were. See the
+// implementation of nsSandboxViolationSink::ViolationHandler().
+
+#define SANDBOX_VIOLATION_QUEUE_NAME "org.mozilla.sandbox.violation.queue"
+#define SANDBOX_VIOLATION_NOTIFICATION_NAME "com.apple.sandbox.violation.*"
+
+class nsSandboxViolationSink
+{
+public:
+ static void Start();
+ static void Stop();
+private:
+ static void ViolationHandler();
+ static int mNotifyToken;
+ static uint64_t mLastMsgReceived;
+};
+
+#endif // nsSandboxViolationSink_h_
diff --git a/widget/cocoa/nsSandboxViolationSink.mm b/widget/cocoa/nsSandboxViolationSink.mm
new file mode 100644
index 0000000000..0572173344
--- /dev/null
+++ b/widget/cocoa/nsSandboxViolationSink.mm
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSandboxViolationSink.h"
+
+#include <unistd.h>
+#include <time.h>
+#include <asl.h>
+#include <dispatch/dispatch.h>
+#include <notify.h>
+#include "nsCocoaDebugUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+
+int nsSandboxViolationSink::mNotifyToken = 0;
+uint64_t nsSandboxViolationSink::mLastMsgReceived = 0;
+
+void
+nsSandboxViolationSink::Start()
+{
+ if (mNotifyToken) {
+ return;
+ }
+ notify_register_dispatch(SANDBOX_VIOLATION_NOTIFICATION_NAME,
+ &mNotifyToken,
+ dispatch_queue_create(SANDBOX_VIOLATION_QUEUE_NAME,
+ DISPATCH_QUEUE_SERIAL),
+ ^(int token) { ViolationHandler(); });
+}
+
+void
+nsSandboxViolationSink::Stop()
+{
+ if (!mNotifyToken) {
+ return;
+ }
+ notify_cancel(mNotifyToken);
+ mNotifyToken = 0;
+}
+
+// We need to query syslogd to find out what violations occurred, and whether
+// they were "ours". We can use the Apple System Log facility to do this.
+// Besides calling notify_post("com.apple.sandbox.violation.*"), Apple's
+// sandboxd also reports all sandbox violations (sent to it by the Sandbox
+// kernel extension) to syslogd, which stores them and makes them viewable
+// in the system console. This is the database we query.
+
+// ViolationHandler() is always called on its own secondary thread. This
+// makes it unlikely it will interfere with other browser activity.
+
+void
+nsSandboxViolationSink::ViolationHandler()
+{
+ aslmsg query = asl_new(ASL_TYPE_QUERY);
+
+ asl_set_query(query, ASL_KEY_FACILITY, "com.apple.sandbox",
+ ASL_QUERY_OP_EQUAL);
+
+ // Only get reports that were generated very recently.
+ char query_time[30] = {0};
+ SprintfLiteral(query_time, "%li", time(NULL) - 2);
+ asl_set_query(query, ASL_KEY_TIME, query_time,
+ ASL_QUERY_OP_NUMERIC | ASL_QUERY_OP_GREATER_EQUAL);
+
+ // This code is easier to test if we don't just track "our" violations,
+ // which are (normally) few and far between. For example (for the time
+ // being at least) four appleeventsd sandbox violations happen every time
+ // we start the browser in e10s mode. But it makes sense to default to
+ // only tracking "our" violations.
+ if (mozilla::Preferences::GetBool(
+ "security.sandbox.mac.track.violations.oursonly", true)) {
+ // This makes each of our processes log its own violations. It might
+ // be better to make the chrome process log all the other processes'
+ // violations.
+ char query_pid[20] = {0};
+ SprintfLiteral(query_pid, "%u", getpid());
+ asl_set_query(query, ASL_KEY_REF_PID, query_pid, ASL_QUERY_OP_EQUAL);
+ }
+
+ aslresponse response = asl_search(nullptr, query);
+
+ // Each time ViolationHandler() is called we grab as many messages as are
+ // available. Otherwise we might not get them all.
+ if (response) {
+ while (true) {
+ aslmsg hit = nullptr;
+ aslmsg found = nullptr;
+ const char* id_str;
+
+ while ((hit = aslresponse_next(response))) {
+ // Record the message id to avoid logging the same violation more
+ // than once.
+ id_str = asl_get(hit, ASL_KEY_MSG_ID);
+ uint64_t id_val = atoll(id_str);
+ if (id_val <= mLastMsgReceived) {
+ continue;
+ }
+ mLastMsgReceived = id_val;
+ found = hit;
+ break;
+ }
+ if (!found) {
+ break;
+ }
+
+ const char* pid_str = asl_get(found, ASL_KEY_REF_PID);
+ const char* message_str = asl_get(found, ASL_KEY_MSG);
+ nsCocoaDebugUtils::DebugLog("nsSandboxViolationSink::ViolationHandler(): id %s, pid %s, message %s",
+ id_str, pid_str, message_str);
+ }
+ aslresponse_free(response);
+ }
+}
diff --git a/widget/cocoa/nsScreenCocoa.h b/widget/cocoa/nsScreenCocoa.h
new file mode 100644
index 0000000000..268d5beb0a
--- /dev/null
+++ b/widget/cocoa/nsScreenCocoa.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsScreenCocoa_h_
+#define nsScreenCocoa_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsBaseScreen.h"
+
+class nsScreenCocoa : public nsBaseScreen
+{
+public:
+ explicit nsScreenCocoa (NSScreen *screen);
+ ~nsScreenCocoa ();
+
+ NS_IMETHOD GetId(uint32_t* outId);
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor);
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor)
+ {
+ return GetContentsScaleFactor(aScaleFactor);
+ }
+
+ NSScreen *CocoaScreen() { return mScreen; }
+
+private:
+ CGFloat BackingScaleFactor();
+
+ NSScreen *mScreen;
+ uint32_t mId;
+};
+
+#endif // nsScreenCocoa_h_
diff --git a/widget/cocoa/nsScreenCocoa.mm b/widget/cocoa/nsScreenCocoa.mm
new file mode 100644
index 0000000000..08905bf0ad
--- /dev/null
+++ b/widget/cocoa/nsScreenCocoa.mm
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsScreenCocoa.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+
+static uint32_t sScreenId = 0;
+
+nsScreenCocoa::nsScreenCocoa (NSScreen *screen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mScreen = [screen retain];
+ mId = ++sScreenId;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsScreenCocoa::~nsScreenCocoa ()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mScreen release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetId(uint32_t *outId)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *outId = mId;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen frame];
+
+ mozilla::LayoutDeviceIntRect r =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetAvailRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen visibleFrame];
+
+ mozilla::LayoutDeviceIntRect r =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen frame];
+
+ mozilla::DesktopIntRect r = nsCocoaUtils::CocoaRectToGeckoRect(frame);
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetAvailRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen visibleFrame];
+
+ mozilla::DesktopIntRect r = nsCocoaUtils::CocoaRectToGeckoRect(frame);
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetPixelDepth(int32_t *aPixelDepth)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindowDepth depth = [mScreen depth];
+ int bpp = NSBitsPerPixelFromDepth(depth);
+
+ *aPixelDepth = bpp;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetColorDepth(int32_t *aColorDepth)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindowDepth depth = [mScreen depth];
+ int bpp = NSBitsPerPixelFromDepth (depth);
+
+ *aColorDepth = bpp;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetContentsScaleFactor(double *aContentsScaleFactor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *aContentsScaleFactor = (double) BackingScaleFactor();
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+CGFloat
+nsScreenCocoa::BackingScaleFactor()
+{
+ return nsCocoaUtils::GetBackingScaleFactor(mScreen);
+}
diff --git a/widget/cocoa/nsScreenManagerCocoa.h b/widget/cocoa/nsScreenManagerCocoa.h
new file mode 100644
index 0000000000..61a059d977
--- /dev/null
+++ b/widget/cocoa/nsScreenManagerCocoa.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsScreenManagerCocoa_h_
+#define nsScreenManagerCocoa_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+#include "nsIScreenManager.h"
+#include "nsScreenCocoa.h"
+
+class nsScreenManagerCocoa : public nsIScreenManager
+{
+public:
+ nsScreenManagerCocoa();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+protected:
+ virtual ~nsScreenManagerCocoa();
+
+private:
+
+ nsScreenCocoa *ScreenForCocoaScreen(NSScreen *screen);
+ nsTArray< RefPtr<nsScreenCocoa> > mScreenList;
+};
+
+#endif // nsScreenManagerCocoa_h_
diff --git a/widget/cocoa/nsScreenManagerCocoa.mm b/widget/cocoa/nsScreenManagerCocoa.mm
new file mode 100644
index 0000000000..9a0cbb9cc5
--- /dev/null
+++ b/widget/cocoa/nsScreenManagerCocoa.mm
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsScreenManagerCocoa.h"
+#include "nsObjCExceptions.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsScreenManagerCocoa, nsIScreenManager)
+
+nsScreenManagerCocoa::nsScreenManagerCocoa()
+{
+}
+
+nsScreenManagerCocoa::~nsScreenManagerCocoa()
+{
+}
+
+nsScreenCocoa*
+nsScreenManagerCocoa::ScreenForCocoaScreen(NSScreen *screen)
+{
+ for (uint32_t i = 0; i < mScreenList.Length(); ++i) {
+ nsScreenCocoa* sc = mScreenList[i];
+ if (sc->CocoaScreen() == screen) {
+ // doesn't addref
+ return sc;
+ }
+ }
+
+ // didn't find it; create and insert
+ RefPtr<nsScreenCocoa> sc = new nsScreenCocoa(screen);
+ mScreenList.AppendElement(sc);
+ return sc.get();
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::ScreenForId (uint32_t aId, nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
+
+ *outScreen = nullptr;
+
+ for (uint32_t i = 0; i < mScreenList.Length(); ++i) {
+ nsScreenCocoa* sc = mScreenList[i];
+ uint32_t id;
+ nsresult rv = sc->GetId(&id);
+
+ if (NS_SUCCEEDED(rv) && id == aId) {
+ *outScreen = sc;
+ NS_ADDREF(*outScreen);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::ScreenForRect (int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight,
+ nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+ NSRect inRect =
+ nsCocoaUtils::GeckoRectToCocoaRect(DesktopIntRect(aX, aY,
+ aWidth, aHeight));
+ NSScreen *screenWindowIsOn = [NSScreen mainScreen];
+ float greatestArea = 0;
+
+ while (NSScreen *screen = [screenEnum nextObject]) {
+ NSDictionary *desc = [screen deviceDescription];
+ if ([desc objectForKey:NSDeviceIsScreen] == nil)
+ continue;
+
+ NSRect r = NSIntersectionRect([screen frame], inRect);
+ float area = r.size.width * r.size.height;
+ if (area > greatestArea) {
+ greatestArea = area;
+ screenWindowIsOn = screen;
+ }
+ }
+
+ *outScreen = ScreenForCocoaScreen(screenWindowIsOn);
+ NS_ADDREF(*outScreen);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::GetPrimaryScreen (nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // the mainScreen is the screen with the "key window" (focus, I assume?)
+ NSScreen *sc = [[NSScreen screens] objectAtIndex:0];
+
+ *outScreen = ScreenForCocoaScreen(sc);
+ NS_ADDREF(*outScreen);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::GetNumberOfScreens (uint32_t *aNumberOfScreens)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSArray *ss = [NSScreen screens];
+
+ *aNumberOfScreens = [ss count];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::GetSystemDefaultScale(float *aDefaultScale)
+{
+ *aDefaultScale = 1.0f;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::ScreenForNativeWidget (void *nativeWidget, nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindow *window = static_cast<NSWindow*>(nativeWidget);
+ if (window) {
+ nsIScreen *screen = ScreenForCocoaScreen([window screen]);
+ *outScreen = screen;
+ NS_ADDREF(*outScreen);
+ return NS_OK;
+ }
+
+ *outScreen = nullptr;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsSound.h b/widget/cocoa/nsSound.h
new file mode 100644
index 0000000000..0e0293ae28
--- /dev/null
+++ b/widget/cocoa/nsSound.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSound_h_
+#define nsSound_h_
+
+#include "nsISound.h"
+#include "nsIStreamLoader.h"
+
+class nsSound : public nsISound,
+ public nsIStreamLoaderObserver
+{
+public:
+ nsSound();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+protected:
+ virtual ~nsSound();
+};
+
+#endif // nsSound_h_
diff --git a/widget/cocoa/nsSound.mm b/widget/cocoa/nsSound.mm
new file mode 100644
index 0000000000..04c6b4d764
--- /dev/null
+++ b/widget/cocoa/nsSound.mm
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSound.h"
+#include "nsContentUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsIURL.h"
+#include "nsString.h"
+
+#import <Cocoa/Cocoa.h>
+
+NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
+
+nsSound::nsSound()
+{
+}
+
+nsSound::~nsSound()
+{
+}
+
+NS_IMETHODIMP
+nsSound::Beep()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSBeep();
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
+ nsISupports *context,
+ nsresult aStatus,
+ uint32_t dataLen,
+ const uint8_t *data)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSData *value = [NSData dataWithBytes:data length:dataLen];
+
+ NSSound *sound = [[NSSound alloc] initWithData:value];
+
+ [sound play];
+
+ [sound autorelease];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::Play(nsIURL *aURL)
+{
+ nsCOMPtr<nsIURI> uri(do_QueryInterface(aURL));
+ nsCOMPtr<nsIStreamLoader> loader;
+ return NS_NewStreamLoader(getter_AddRefs(loader),
+ uri,
+ this, // aObserver
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+}
+
+NS_IMETHODIMP
+nsSound::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSound::PlaySystemSound(const nsAString &aSoundAlias)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_IsMozAliasSound(aSoundAlias)) {
+ NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead");
+ // Mac doesn't have system sound settings for each user actions.
+ return NS_OK;
+ }
+
+ NSString *name = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aSoundAlias.BeginReading())
+ length:aSoundAlias.Length()];
+ NSSound *sound = [NSSound soundNamed:name];
+ if (sound) {
+ [sound stop];
+ [sound play];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::PlayEventSound(uint32_t aEventId)
+{
+ // Mac doesn't have system sound settings for each user actions.
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsStandaloneNativeMenu.h b/widget/cocoa/nsStandaloneNativeMenu.h
new file mode 100644
index 0000000000..e03742b1e0
--- /dev/null
+++ b/widget/cocoa/nsStandaloneNativeMenu.h
@@ -0,0 +1,40 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStandaloneNativeMenu_h_
+#define nsStandaloneNativeMenu_h_
+
+#include "nsMenuGroupOwnerX.h"
+#include "nsMenuX.h"
+#include "nsIStandaloneNativeMenu.h"
+
+class nsStandaloneNativeMenu : public nsMenuGroupOwnerX, public nsIStandaloneNativeMenu
+{
+public:
+ nsStandaloneNativeMenu();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISTANDALONENATIVEMENU
+
+ // nsMenuObjectX
+ nsMenuObjectTypeX MenuObjectType() override { return eStandaloneNativeMenuObjectType; }
+ void * NativeData() override { return mMenu != nullptr ? mMenu->NativeData() : nullptr; }
+ virtual void IconUpdated() override;
+
+ nsMenuX * GetMenuXObject() { return mMenu; }
+
+ // If this menu is the menu of a system status bar item (NSStatusItem),
+ // let the menu know about the status item so that it can propagate
+ // any icon changes to the status item.
+ void SetContainerStatusBarItem(NSStatusItem* aItem);
+
+protected:
+ virtual ~nsStandaloneNativeMenu();
+
+ nsMenuX * mMenu;
+ NSStatusItem* mContainerStatusBarItem;
+};
+
+#endif
diff --git a/widget/cocoa/nsStandaloneNativeMenu.mm b/widget/cocoa/nsStandaloneNativeMenu.mm
new file mode 100644
index 0000000000..98a5fd8f6f
--- /dev/null
+++ b/widget/cocoa/nsStandaloneNativeMenu.mm
@@ -0,0 +1,213 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsStandaloneNativeMenu.h"
+#include "nsMenuUtilsX.h"
+#include "nsIDOMElement.h"
+#include "nsIMutationObserver.h"
+#include "nsGkAtoms.h"
+#include "nsObjCExceptions.h"
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsStandaloneNativeMenu, nsMenuGroupOwnerX,
+ nsIMutationObserver, nsIStandaloneNativeMenu)
+
+nsStandaloneNativeMenu::nsStandaloneNativeMenu()
+: mMenu(nullptr)
+, mContainerStatusBarItem(nil)
+{
+}
+
+nsStandaloneNativeMenu::~nsStandaloneNativeMenu()
+{
+ if (mMenu) delete mMenu;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::Init(nsIDOMElement * aDOMElement)
+{
+ NS_ASSERTION(mMenu == nullptr, "nsNativeMenu::Init - mMenu not null!");
+
+ nsresult rv;
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!content->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menupopup))
+ return NS_ERROR_FAILURE;
+
+ rv = nsMenuGroupOwnerX::Create(content);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mMenu = new nsMenuX();
+ rv = mMenu->Create(this, this, content);
+ if (NS_FAILED(rv)) {
+ delete mMenu;
+ mMenu = nullptr;
+ return rv;
+ }
+
+ mMenu->SetupIcon();
+
+ return NS_OK;
+}
+
+static void
+UpdateMenu(nsMenuX * aMenu)
+{
+ aMenu->MenuOpened();
+ aMenu->MenuClosed();
+
+ uint32_t itemCount = aMenu->GetItemCount();
+ for (uint32_t i = 0; i < itemCount; i++) {
+ nsMenuObjectX * menuObject = aMenu->GetItemAt(i);
+ if (menuObject->MenuObjectType() == eSubmenuObjectType) {
+ UpdateMenu(static_cast<nsMenuX*>(menuObject));
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::MenuWillOpen(bool * aResult)
+{
+ NS_ASSERTION(mMenu != nullptr, "nsStandaloneNativeMenu::OnOpen - mMenu is null!");
+
+ // Force an update on the mMenu by faking an open/close on all of
+ // its submenus.
+ UpdateMenu(mMenu);
+
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::GetNativeMenu(void ** aVoidPointer)
+{
+ if (mMenu) {
+ *aVoidPointer = mMenu->NativeData();
+ [[(NSObject *)(*aVoidPointer) retain] autorelease];
+ return NS_OK;
+ } else {
+ *aVoidPointer = nullptr;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+}
+
+static NSMenuItem *
+NativeMenuItemWithLocation(NSMenu * currentSubmenu, NSString * locationString)
+{
+ NSArray * indexes = [locationString componentsSeparatedByString:@"|"];
+ NSUInteger indexCount = [indexes count];
+ if (indexCount == 0)
+ return nil;
+
+ for (NSUInteger i = 0; i < indexCount; i++) {
+ NSInteger targetIndex = [[indexes objectAtIndex:i] integerValue];
+ NSInteger itemCount = [currentSubmenu numberOfItems];
+ if (targetIndex < itemCount) {
+ NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
+
+ // If this is the last index, just return the menu item.
+ if (i == (indexCount - 1))
+ return menuItem;
+
+ // If this is not the last index, find the submenu and keep going.
+ if ([menuItem hasSubmenu])
+ currentSubmenu = [menuItem submenu];
+ else
+ return nil;
+ }
+ }
+
+ return nil;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::ActivateNativeMenuItemAt(const nsAString& indexString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mMenu)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NSString * locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSMenu * menu = static_cast<NSMenu *> (mMenu->NativeData());
+ NSMenuItem * item = NativeMenuItemWithLocation(menu, locationString);
+
+ // We can't perform an action on an item with a submenu, that will raise
+ // an obj-c exception.
+ if (item && ![item hasSubmenu]) {
+ NSMenu * parent = [item menu];
+ if (parent) {
+ // NSLog(@"Performing action for native menu item titled: %@\n",
+ // [[currentSubmenu itemAtIndex:targetIndex] title]);
+ [parent performActionForItemAtIndex:[parent indexOfItem:item]];
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+ if (!mMenu)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0)
+ return NS_OK;
+
+ nsMenuX* currentMenu = mMenu;
+
+ // now find the correct submenu
+ for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
+ int targetIndex = [[indexes objectAtIndex:i] intValue];
+ int visible = 0;
+ uint32_t length = currentMenu->GetItemCount();
+ for (unsigned int j = 0; j < length; j++) {
+ nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
+ if (!targetMenu)
+ return NS_OK;
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
+ visible++;
+ if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
+ currentMenu = static_cast<nsMenuX*>(targetMenu);
+ // fake open/close to cause lazy update to happen
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+ break;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsStandaloneNativeMenu::IconUpdated()
+{
+ if (mContainerStatusBarItem) {
+ [mContainerStatusBarItem setImage:[mMenu->NativeMenuItem() image]];
+ }
+}
+
+void
+nsStandaloneNativeMenu::SetContainerStatusBarItem(NSStatusItem* aItem)
+{
+ mContainerStatusBarItem = aItem;
+ IconUpdated();
+}
diff --git a/widget/cocoa/nsSystemStatusBarCocoa.h b/widget/cocoa/nsSystemStatusBarCocoa.h
new file mode 100644
index 0000000000..51aa4df00d
--- /dev/null
+++ b/widget/cocoa/nsSystemStatusBarCocoa.h
@@ -0,0 +1,40 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSystemStatusBarCocoa_h_
+#define nsSystemStatusBarCocoa_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsISystemStatusBar.h"
+#include "nsClassHashtable.h"
+
+class nsStandaloneNativeMenu;
+@class NSStatusItem;
+
+class nsSystemStatusBarCocoa : public nsISystemStatusBar
+{
+public:
+ nsSystemStatusBarCocoa() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISYSTEMSTATUSBAR
+
+protected:
+ virtual ~nsSystemStatusBarCocoa() {}
+
+ struct StatusItem
+ {
+ explicit StatusItem(nsStandaloneNativeMenu* aMenu);
+ ~StatusItem();
+
+ private:
+ RefPtr<nsStandaloneNativeMenu> mMenu;
+ NSStatusItem* mStatusItem;
+ };
+
+ nsClassHashtable<nsISupportsHashKey, StatusItem> mItems;
+};
+
+#endif // nsSystemStatusBarCocoa_h_
diff --git a/widget/cocoa/nsSystemStatusBarCocoa.mm b/widget/cocoa/nsSystemStatusBarCocoa.mm
new file mode 100644
index 0000000000..522da71451
--- /dev/null
+++ b/widget/cocoa/nsSystemStatusBarCocoa.mm
@@ -0,0 +1,74 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsSystemStatusBarCocoa.h"
+#include "nsStandaloneNativeMenu.h"
+#include "nsObjCExceptions.h"
+#include "nsIDOMElement.h"
+
+NS_IMPL_ISUPPORTS(nsSystemStatusBarCocoa, nsISystemStatusBar)
+
+NS_IMETHODIMP
+nsSystemStatusBarCocoa::AddItem(nsIDOMElement* aDOMElement)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ RefPtr<nsStandaloneNativeMenu> menu = new nsStandaloneNativeMenu();
+ nsresult rv = menu->Init(aDOMElement);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISupports> keyPtr = aDOMElement;
+ mItems.Put(keyPtr, new StatusItem(menu));
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSystemStatusBarCocoa::RemoveItem(nsIDOMElement* aDOMElement)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mItems.Remove(aDOMElement);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsSystemStatusBarCocoa::StatusItem::StatusItem(nsStandaloneNativeMenu* aMenu)
+ : mMenu(aMenu)
+{
+ MOZ_COUNT_CTOR(nsSystemStatusBarCocoa::StatusItem);
+
+ NSMenu* nativeMenu = nil;
+ mMenu->GetNativeMenu(reinterpret_cast<void**>(&nativeMenu));
+
+ mStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
+ [mStatusItem setMenu:nativeMenu];
+ [mStatusItem setHighlightMode:YES];
+
+ // We want the status item to get its image from menu item that mMenu was
+ // initialized with. Icon loads are asynchronous, so we need to let the menu
+ // know about the item so that it can update its icon as soon as it has
+ // loaded.
+ mMenu->SetContainerStatusBarItem(mStatusItem);
+}
+
+nsSystemStatusBarCocoa::StatusItem::~StatusItem()
+{
+ mMenu->SetContainerStatusBarItem(nil);
+ [[NSStatusBar systemStatusBar] removeStatusItem:mStatusItem];
+ [mStatusItem release];
+ mStatusItem = nil;
+
+ MOZ_COUNT_DTOR(nsSystemStatusBarCocoa::StatusItem);
+}
diff --git a/widget/cocoa/nsToolkit.h b/widget/cocoa/nsToolkit.h
new file mode 100644
index 0000000000..1631a8ac24
--- /dev/null
+++ b/widget/cocoa/nsToolkit.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsToolkit_h_
+#define nsToolkit_h_
+
+#include "nscore.h"
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#import <objc/Object.h>
+#import <IOKit/IOKitLib.h>
+
+class nsToolkit
+{
+public:
+ nsToolkit();
+ virtual ~nsToolkit();
+
+ static nsToolkit* GetToolkit();
+
+ static void Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+ }
+
+ static void PostSleepWakeNotification(const char* aNotification);
+
+ static nsresult SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
+ bool classMethods = false);
+
+ void RegisterForAllProcessMouseEvents();
+ void UnregisterAllProcessMouseEventHandlers();
+
+protected:
+
+ nsresult RegisterForSleepWakeNotifications();
+ void RemoveSleepWakeNotifications();
+
+protected:
+
+ static nsToolkit* gToolkit;
+
+ CFRunLoopSourceRef mSleepWakeNotificationRLS;
+ io_object_t mPowerNotifier;
+
+ CFMachPortRef mEventTapPort;
+ CFRunLoopSourceRef mEventTapRLS;
+};
+
+#endif // nsToolkit_h_
diff --git a/widget/cocoa/nsToolkit.mm b/widget/cocoa/nsToolkit.mm
new file mode 100644
index 0000000000..4d0222d5d3
--- /dev/null
+++ b/widget/cocoa/nsToolkit.mm
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsToolkit.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <mach/mach_port.h>
+#include <mach/mach_interface.h>
+#include <mach/mach_init.h>
+
+extern "C" {
+#include <mach-o/getsect.h>
+}
+#include <unistd.h>
+#include <dlfcn.h>
+
+#import <Cocoa/Cocoa.h>
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <IOKit/IOMessage.h>
+
+#include "nsCocoaUtils.h"
+#include "nsObjCExceptions.h"
+
+#include "nsGkAtoms.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsBaseWidget.h"
+
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+static io_connect_t gRootPort = MACH_PORT_NULL;
+
+nsToolkit* nsToolkit::gToolkit = nullptr;
+
+nsToolkit::nsToolkit()
+: mSleepWakeNotificationRLS(nullptr)
+, mEventTapPort(nullptr)
+, mEventTapRLS(nullptr)
+{
+ MOZ_COUNT_CTOR(nsToolkit);
+ RegisterForSleepWakeNotifications();
+}
+
+nsToolkit::~nsToolkit()
+{
+ MOZ_COUNT_DTOR(nsToolkit);
+ RemoveSleepWakeNotifications();
+ UnregisterAllProcessMouseEventHandlers();
+}
+
+void
+nsToolkit::PostSleepWakeNotification(const char* aNotification)
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(nullptr, aNotification, nullptr);
+}
+
+// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html
+static void ToolkitSleepWakeCallback(void *refCon, io_service_t service, natural_t messageType, void * messageArgument)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ switch (messageType)
+ {
+ case kIOMessageSystemWillSleep:
+ // System is going to sleep now.
+ nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC);
+ ::IOAllowPowerChange(gRootPort, (long)messageArgument);
+ break;
+
+ case kIOMessageCanSystemSleep:
+ // In this case, the computer has been idle for several minutes
+ // and will sleep soon so you must either allow or cancel
+ // this notification. Important: if you don’t respond, there will
+ // be a 30-second timeout before the computer sleeps.
+ // In Mozilla's case, we always allow sleep.
+ ::IOAllowPowerChange(gRootPort,(long)messageArgument);
+ break;
+
+ case kIOMessageSystemHasPoweredOn:
+ // Handle wakeup.
+ nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ break;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult
+nsToolkit::RegisterForSleepWakeNotifications()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ IONotificationPortRef notifyPortRef;
+
+ NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
+
+ gRootPort = ::IORegisterForSystemPower(0, &notifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
+ if (gRootPort == MACH_PORT_NULL) {
+ NS_ERROR("IORegisterForSystemPower failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef);
+ ::CFRunLoopAddSource(::CFRunLoopGetCurrent(),
+ mSleepWakeNotificationRLS,
+ kCFRunLoopDefaultMode);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsToolkit::RemoveSleepWakeNotifications()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mSleepWakeNotificationRLS) {
+ ::IODeregisterForSystemPower(&mPowerNotifier);
+ ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(),
+ mSleepWakeNotificationRLS,
+ kCFRunLoopDefaultMode);
+
+ mSleepWakeNotificationRLS = nullptr;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Converts aPoint from the CoreGraphics "global display coordinate" system
+// (which includes all displays/screens and has a top-left origin) to its
+// (presumed) Cocoa counterpart (assumed to be the same as the "screen
+// coordinates" system), which has a bottom-left origin.
+static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint)
+{
+ NSPoint cocoaPoint;
+ cocoaPoint.x = aPoint.x;
+ cocoaPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
+ return cocoaPoint;
+}
+
+// Since our event tap is "listen only", events arrive here a little after
+// they've already been processed.
+static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ((type == kCGEventTapDisabledByUserInput) ||
+ (type == kCGEventTapDisabledByTimeout))
+ return event;
+ if ([NSApp isActive])
+ return event;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE(rollupListener, event);
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (!rollupWidget)
+ return event;
+
+ // Don't bother with rightMouseDown events here -- because of the delay,
+ // we'll end up closing browser context menus that we just opened. Since
+ // these events usually raise a context menu, we'll handle them by hooking
+ // the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed
+ // notification (in nsAppShell.mm's AppShellDelegate).
+ if (type == kCGEventRightMouseDown)
+ return event;
+ NSWindow *ctxMenuWindow = (NSWindow*) rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
+ if (!ctxMenuWindow)
+ return event;
+ NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event));
+ // Don't roll up the rollup widget if our mouseDown happens over it (doing
+ // so would break the corresponding context menu).
+ if (NSPointInRect(screenLocation, [ctxMenuWindow frame]))
+ return event;
+ rollupListener->Rollup(0, false, nullptr, nullptr);
+ return event;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL);
+}
+
+// Cocoa Firefox's use of custom context menus requires that we explicitly
+// handle mouse events from other processes that the OS handles
+// "automatically" for native context menus -- mouseMoved events so that
+// right-click context menus work properly when our browser doesn't have the
+// focus (bmo bug 368077), and mouseDown events so that our browser can
+// dismiss a context menu when a mouseDown happens in another process (bmo
+// bug 339945).
+void
+nsToolkit::RegisterForAllProcessMouseEvents()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (getenv("MOZ_DEBUG"))
+ return;
+
+ // Don't do this for apps that use native context menus.
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+
+ if (!mEventTapRLS) {
+ // Using an event tap for mouseDown events (instead of installing a
+ // handler for them on the EventMonitor target) works around an Apple
+ // bug that causes OS menus (like the Clock menu) not to work properly
+ // on OS X 10.4.X and below (bmo bug 381448).
+ // We install our event tap "listen only" to get around yet another Apple
+ // bug -- when we install it as an event filter on any kind of mouseDown
+ // event, that kind of event stops working in the main menu, and usually
+ // mouse event processing stops working in all apps in the current login
+ // session (so the entire OS appears to be hung)! The downside of
+ // installing listen-only is that events arrive at our handler slightly
+ // after they've already been processed.
+ mEventTapPort = CGEventTapCreate(kCGSessionEventTap,
+ kCGHeadInsertEventTap,
+ kCGEventTapOptionListenOnly,
+ CGEventMaskBit(kCGEventLeftMouseDown)
+ | CGEventMaskBit(kCGEventRightMouseDown)
+ | CGEventMaskBit(kCGEventOtherMouseDown),
+ EventTapCallback,
+ nullptr);
+ if (!mEventTapPort)
+ return;
+ mEventTapRLS = CFMachPortCreateRunLoopSource(nullptr, mEventTapPort, 0);
+ if (!mEventTapRLS) {
+ CFRelease(mEventTapPort);
+ mEventTapPort = nullptr;
+ return;
+ }
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsToolkit::UnregisterAllProcessMouseEventHandlers()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mEventTapRLS) {
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS,
+ kCFRunLoopDefaultMode);
+ CFRelease(mEventTapRLS);
+ mEventTapRLS = nullptr;
+ }
+ if (mEventTapPort) {
+ // mEventTapPort must be invalidated as well as released. Otherwise the
+ // event tap doesn't get destroyed until the browser process ends (it
+ // keeps showing up in the list returned by CGGetEventTapList()).
+ CFMachPortInvalidate(mEventTapPort);
+ CFRelease(mEventTapPort);
+ mEventTapPort = nullptr;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Return the nsToolkit instance. If a toolkit does not yet exist, then one
+// will be created.
+// static
+nsToolkit* nsToolkit::GetToolkit()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!gToolkit) {
+ gToolkit = new nsToolkit();
+ }
+
+ return gToolkit;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
+}
+
+// An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X
+// Leopard and is available to 64-bit binaries on Leopard and above. Based on
+// ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling.
+// Since the Method type becomes an opaque type as of Objective-C 2.0, we'll
+// have to switch to using accessor methods like method_exchangeImplementations()
+// when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard
+// and above).
+//
+// Be aware that, if aClass doesn't have an orgMethod selector but one of its
+// superclasses does, the method substitution will (in effect) take place in
+// that superclass (rather than in aClass itself). The substitution has
+// effect on the class where it takes place and all of that class's
+// subclasses. In order for method swizzling to work properly, posedMethod
+// needs to be unique in the class where the substitution takes place and all
+// of its subclasses.
+nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
+ bool classMethods)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ Method original = nil;
+ Method posed = nil;
+
+ if (classMethods) {
+ original = class_getClassMethod(aClass, orgMethod);
+ posed = class_getClassMethod(aClass, posedMethod);
+ } else {
+ original = class_getInstanceMethod(aClass, orgMethod);
+ posed = class_getInstanceMethod(aClass, posedMethod);
+ }
+
+ if (!original || !posed)
+ return NS_ERROR_FAILURE;
+
+ method_exchangeImplementations(original, posed);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsWidgetFactory.mm b/widget/cocoa/nsWidgetFactory.mm
new file mode 100644
index 0000000000..3bddaf95ce
--- /dev/null
+++ b/widget/cocoa/nsWidgetFactory.mm
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFactory.h"
+#include "nsISupports.h"
+#include "nsIComponentManager.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+
+#include "nsWidgetsCID.h"
+
+#include "nsChildView.h"
+#include "nsCocoaWindow.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsFilePicker.h"
+#include "nsColorPicker.h"
+
+#include "nsClipboard.h"
+#include "nsClipboardHelper.h"
+#include "nsTransferable.h"
+#include "nsHTMLFormatConverter.h"
+#include "nsDragService.h"
+#include "nsToolkit.h"
+
+#include "nsLookAndFeel.h"
+
+#include "nsSound.h"
+#include "nsIdleServiceX.h"
+#include "NativeKeyBindings.h"
+#include "OSXNotificationCenter.h"
+
+#include "nsScreenManagerCocoa.h"
+#include "nsDeviceContextSpecX.h"
+#include "nsPrintOptionsX.h"
+#include "nsPrintDialogX.h"
+#include "nsPrintSession.h"
+#include "nsToolkitCompsCID.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCocoaWindow)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsChildView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFilePicker)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsColorPicker)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerCocoa)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecX)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsX, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintDialogServiceX, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceX, nsIdleServiceX::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(OSXNotificationCenter, Init)
+
+#include "nsMenuBarX.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeMenuServiceX)
+
+#include "nsBidiKeyboard.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard)
+
+#include "nsNativeThemeCocoa.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeThemeCocoa)
+
+#include "nsMacDockSupport.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacDockSupport)
+
+#include "nsMacWebAppUtils.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacWebAppUtils)
+
+#include "nsStandaloneNativeMenu.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsStandaloneNativeMenu)
+
+#include "nsSystemStatusBarCocoa.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSystemStatusBarCocoa)
+
+#include "GfxInfo.h"
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+} // namespace widget
+} // namespace mozilla
+
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_POPUP_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_SOUND_CID);
+NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
+NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTDIALOGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MACDOCKSUPPORT_CID);
+NS_DEFINE_NAMED_CID(NS_MACWEBAPPUTILS_CID);
+NS_DEFINE_NAMED_CID(NS_STANDALONENATIVEMENU_CID);
+NS_DEFINE_NAMED_CID(NS_MACSYSTEMSTATUSBAR_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, NULL, nsCocoaWindowConstructor },
+ { &kNS_POPUP_CID, false, NULL, nsCocoaWindowConstructor },
+ { &kNS_CHILD_CID, false, NULL, nsChildViewConstructor },
+ { &kNS_FILEPICKER_CID, false, NULL, nsFilePickerConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_COLORPICKER_CID, false, NULL, nsColorPickerConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_APPSHELL_CID, false, NULL, nsAppShellConstructor, mozilla::Module::ALLOW_IN_GPU_PROCESS },
+ { &kNS_SOUND_CID, false, NULL, nsSoundConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_TRANSFERABLE_CID, false, NULL, nsTransferableConstructor },
+ { &kNS_HTMLFORMATCONVERTER_CID, false, NULL, nsHTMLFormatConverterConstructor },
+ { &kNS_CLIPBOARD_CID, false, NULL, nsClipboardConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_CLIPBOARDHELPER_CID, false, NULL, nsClipboardHelperConstructor },
+ { &kNS_DRAGSERVICE_CID, false, NULL, nsDragServiceConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_BIDIKEYBOARD_CID, false, NULL, nsBidiKeyboardConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_THEMERENDERER_CID, false, NULL, nsNativeThemeCocoaConstructor },
+ { &kNS_SCREENMANAGER_CID, false, NULL, nsScreenManagerCocoaConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_DEVICE_CONTEXT_SPEC_CID, false, NULL, nsDeviceContextSpecXConstructor },
+ { &kNS_PRINTSESSION_CID, false, NULL, nsPrintSessionConstructor },
+ { &kNS_PRINTSETTINGSSERVICE_CID, false, NULL, nsPrintOptionsXConstructor },
+ { &kNS_PRINTDIALOGSERVICE_CID, false, NULL, nsPrintDialogServiceXConstructor },
+ { &kNS_IDLE_SERVICE_CID, false, NULL, nsIdleServiceXConstructor },
+ { &kNS_SYSTEMALERTSSERVICE_CID, false, NULL, OSXNotificationCenterConstructor },
+ { &kNS_NATIVEMENUSERVICE_CID, false, NULL, nsNativeMenuServiceXConstructor },
+ { &kNS_MACDOCKSUPPORT_CID, false, NULL, nsMacDockSupportConstructor },
+ { &kNS_MACWEBAPPUTILS_CID, false, NULL, nsMacWebAppUtilsConstructor },
+ { &kNS_STANDALONENATIVEMENU_CID, false, NULL, nsStandaloneNativeMenuConstructor },
+ { &kNS_MACSYSTEMSTATUSBAR_CID, false, NULL, nsSystemStatusBarCocoaConstructor },
+ { &kNS_GFXINFO_CID, false, NULL, mozilla::widget::GfxInfoConstructor },
+ { NULL }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/mac;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/popup/mac;1", &kNS_POPUP_CID },
+ { "@mozilla.org/widgets/childwindow/mac;1", &kNS_CHILD_CID },
+ { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/appshell/mac;1", &kNS_APPSHELL_CID, mozilla::Module::ALLOW_IN_GPU_PROCESS },
+ { "@mozilla.org/sound;1", &kNS_SOUND_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
+ { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
+ { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
+ { NS_PRINTDIALOGSERVICE_CONTRACTID, &kNS_PRINTDIALOGSERVICE_CID },
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/system-alerts-service;1", &kNS_SYSTEMALERTSSERVICE_CID },
+ { "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID },
+ { "@mozilla.org/widget/macdocksupport;1", &kNS_MACDOCKSUPPORT_CID },
+ { "@mozilla.org/widget/mac-web-app-utils;1", &kNS_MACWEBAPPUTILS_CID },
+ { "@mozilla.org/widget/standalonenativemenu;1", &kNS_STANDALONENATIVEMENU_CID },
+ { "@mozilla.org/widget/macsystemstatusbar;1", &kNS_MACSYSTEMSTATUSBAR_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { NULL }
+};
+
+static void
+nsWidgetCocoaModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ NativeKeyBindings::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsToolkit::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ NULL,
+ NULL,
+ nsAppShellInit,
+ nsWidgetCocoaModuleDtor,
+ mozilla::Module::ALLOW_IN_GPU_PROCESS
+};
+
+NSMODULE_DEFN(nsWidgetMacModule) = &kWidgetModule;
diff --git a/widget/cocoa/nsWindowMap.h b/widget/cocoa/nsWindowMap.h
new file mode 100644
index 0000000000..c6ad72c010
--- /dev/null
+++ b/widget/cocoa/nsWindowMap.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWindowMap_h_
+#define nsWindowMap_h_
+
+#import <Cocoa/Cocoa.h>
+
+// WindowDataMap
+//
+// In both mozilla and embedding apps, we need to have a place to put
+// per-top-level-window logic and data, to handle such things as IME
+// commit when the window gains/loses focus. We can't use a window
+// delegate, because an embeddor probably already has one. Nor can we
+// subclass NSWindow, again because we can't impose that burden on the
+// embeddor.
+//
+// So we have a global map of NSWindow -> TopLevelWindowData, and set
+// up TopLevelWindowData as a notification observer etc.
+
+@interface WindowDataMap : NSObject
+{
+@private
+ NSMutableDictionary* mWindowMap; // dict of TopLevelWindowData keyed by address of NSWindow
+}
+
++ (WindowDataMap*)sharedWindowDataMap;
+
+- (void)ensureDataForWindow:(NSWindow*)inWindow;
+- (id)dataForWindow:(NSWindow*)inWindow;
+
+// set data for a given window. inData is retained (and any previously set data
+// is released).
+- (void)setData:(id)inData forWindow:(NSWindow*)inWindow;
+
+// remove the data for the given window. the data is released.
+- (void)removeDataForWindow:(NSWindow*)inWindow;
+
+@end
+
+@class ChildView;
+
+// TopLevelWindowData
+//
+// Class to hold per-window data, and handle window state changes.
+
+@interface TopLevelWindowData : NSObject
+{
+@private
+}
+
+- (id)initWithWindow:(NSWindow*)inWindow;
++ (void)activateInWindow:(NSWindow*)aWindow;
++ (void)deactivateInWindow:(NSWindow*)aWindow;
++ (void)activateInWindowViews:(NSWindow*)aWindow;
++ (void)deactivateInWindowViews:(NSWindow*)aWindow;
+
+@end
+
+#endif // nsWindowMap_h_
diff --git a/widget/cocoa/nsWindowMap.mm b/widget/cocoa/nsWindowMap.mm
new file mode 100644
index 0000000000..c43b024086
--- /dev/null
+++ b/widget/cocoa/nsWindowMap.mm
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindowMap.h"
+#include "nsObjCExceptions.h"
+#include "nsChildView.h"
+#include "nsCocoaWindow.h"
+
+@interface WindowDataMap(Private)
+
+- (NSString*)keyForWindow:(NSWindow*)inWindow;
+
+@end
+
+@interface TopLevelWindowData(Private)
+
+- (void)windowResignedKey:(NSNotification*)inNotification;
+- (void)windowBecameKey:(NSNotification*)inNotification;
+- (void)windowWillClose:(NSNotification*)inNotification;
+
+@end
+
+#pragma mark -
+
+@implementation WindowDataMap
+
++ (WindowDataMap*)sharedWindowDataMap
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ static WindowDataMap* sWindowMap = nil;
+ if (!sWindowMap)
+ sWindowMap = [[WindowDataMap alloc] init];
+
+ return sWindowMap;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)init
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mWindowMap = [[NSMutableDictionary alloc] initWithCapacity:10];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindowMap release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)ensureDataForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!inWindow || [self dataForWindow:inWindow])
+ return;
+
+ TopLevelWindowData* windowData = [[TopLevelWindowData alloc] initWithWindow:inWindow];
+ [self setData:windowData forWindow:inWindow]; // takes ownership
+ [windowData release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (id)dataForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [mWindowMap objectForKey:[self keyForWindow:inWindow]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)setData:(id)inData forWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindowMap setObject:inData forKey:[self keyForWindow:inWindow]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)removeDataForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindowMap removeObjectForKey:[self keyForWindow:inWindow]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSString*)keyForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSString stringWithFormat:@"%p", inWindow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+@end
+
+// TopLevelWindowData
+//
+// This class holds data about top-level windows. We can't use a window
+// delegate, because an embedder may already have one.
+
+@implementation TopLevelWindowData
+
+- (id)initWithWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowBecameKey:)
+ name:NSWindowDidBecomeKeyNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowResignedKey:)
+ name:NSWindowDidResignKeyNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowBecameMain:)
+ name:NSWindowDidBecomeMainNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowResignedMain:)
+ name:NSWindowDidResignMainNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:inWindow];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// As best I can tell, if the notification's object has a corresponding
+// top-level widget (an nsCocoaWindow object), it has a delegate (set in
+// nsCocoaWindow::StandardCreate()) of class WindowDelegate, and otherwise
+// not (Camino didn't use top-level widgets (nsCocoaWindow objects) --
+// only child widgets (nsChildView objects)). (The notification is sent
+// to windowBecameKey: or windowBecameMain: below.)
+//
+// For use with clients that (like Firefox) do use top-level widgets (and
+// have NSWindow delegates of class WindowDelegate).
++ (void)activateInWindow:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
+ return;
+
+ if ([delegate toplevelActiveState])
+ return;
+ [delegate sendToplevelActivateEvents];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// See comments above activateInWindow:
+//
+// If we're using top-level widgets (nsCocoaWindow objects), we send them
+// NS_DEACTIVATE events (which propagate to child widgets (nsChildView
+// objects) via nsWebShellWindow::HandleEvent()).
+//
+// For use with clients that (like Firefox) do use top-level widgets (and
+// have NSWindow delegates of class WindowDelegate).
++ (void)deactivateInWindow:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
+ return;
+
+ if (![delegate toplevelActiveState])
+ return;
+ [delegate sendToplevelDeactivateEvents];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// For use with clients that (like Camino) don't use top-level widgets (and
+// don't have NSWindow delegates of class WindowDelegate).
++ (void)activateInWindowViews:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ id firstResponder = [aWindow firstResponder];
+ if ([firstResponder isKindOfClass:[ChildView class]])
+ [firstResponder viewsWindowDidBecomeKey];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// For use with clients that (like Camino) don't use top-level widgets (and
+// don't have NSWindow delegates of class WindowDelegate).
++ (void)deactivateInWindowViews:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ id firstResponder = [aWindow firstResponder];
+ if ([firstResponder isKindOfClass:[ChildView class]])
+ [firstResponder viewsWindowDidResignKey];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// We make certain exceptions for top-level windows in non-embedders (see
+// comment above windowBecameMain below). And we need (elsewhere) to guard
+// against sending duplicate events. But in general the NS_ACTIVATE event
+// should be sent when a native window becomes key, and the NS_DEACTIVATE
+// event should be sent when it resignes key.
+- (void)windowBecameKey:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
+ [TopLevelWindowData activateInWindowViews:window];
+ } else if ([window isSheet]) {
+ [TopLevelWindowData activateInWindow:window];
+ }
+
+ [[window contentView] setNeedsDisplay:YES];
+}
+
+- (void)windowResignedKey:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
+ [TopLevelWindowData deactivateInWindowViews:window];
+ } else if ([window isSheet]) {
+ [TopLevelWindowData deactivateInWindow:window];
+ }
+
+ [[window contentView] setNeedsDisplay:YES];
+}
+
+// The appearance of a top-level window depends on its main state (not its key
+// state). So (for non-embedders) we need to ensure that a top-level window
+// is main when an NS_ACTIVATE event is sent to Gecko for it.
+- (void)windowBecameMain:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ // Don't send events to a top-level window that has a sheet open above it --
+ // as far as Gecko is concerned, it's inactive, and stays so until the sheet
+ // closes.
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
+ [TopLevelWindowData activateInWindow:window];
+}
+
+- (void)windowResignedMain:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
+ [TopLevelWindowData deactivateInWindow:window];
+}
+
+- (void)windowWillClose:(NSNotification*)inNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // postpone our destruction
+ [[self retain] autorelease];
+
+ // remove ourselves from the window map (which owns us)
+ [[WindowDataMap sharedWindowDataMap] removeDataForWindow:[inNotification object]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/resources/MainMenu.nib/classes.nib b/widget/cocoa/resources/MainMenu.nib/classes.nib
new file mode 100644
index 0000000000..b9b4b09f6b
--- /dev/null
+++ b/widget/cocoa/resources/MainMenu.nib/classes.nib
@@ -0,0 +1,4 @@
+{
+ IBClasses = ({CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; });
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/widget/cocoa/resources/MainMenu.nib/info.nib b/widget/cocoa/resources/MainMenu.nib/info.nib
new file mode 100644
index 0000000000..bcf3ace841
--- /dev/null
+++ b/widget/cocoa/resources/MainMenu.nib/info.nib
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBDocumentLocation</key>
+ <string>159 127 356 240 0 0 1920 1178 </string>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>413 971 130 44 0 0 1920 1178 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>443.0</string>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>8F46</string>
+</dict>
+</plist>
diff --git a/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib b/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000000..16b3f7e523
--- /dev/null
+++ b/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib
Binary files differ