Bug 431503. Infrastructure and tests for native key event translation. r=josh,karlt,sr=jst

This commit is contained in:
roc+@cs.cmu.edu 2008-05-05 16:01:07 -07:00
parent 4ce4eede88
commit a41b7ed86d
14 changed files with 781 additions and 90 deletions

View File

@ -47,7 +47,7 @@
interface nsIDOMElement;
[scriptable, uuid(7a55fc2b-afb3-41c6-9e50-3fee341fa87c)]
[scriptable, uuid(1cfc1a0a-e348-4b18-b61b-935c192f85c4)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -144,6 +144,19 @@ interface nsIDOMWindowUtils : nsISupports {
in long aCharCode,
in long aModifiers);
/**
* See nsIWidget::SynthesizeNativeKeyEvent
*
* Cannot be accessed from unprivileged context (not content-accessible)
* Will throw a DOM security error if called without UniversalXPConnect
* privileges.
*/
void sendNativeKeyEvent(in long aNativeKeyboardLayout,
in long aNativeKeyCode,
in long aModifierFlags,
in AString aCharacters,
in AString aUnmodifiedCharacters);
/**
* Focus the element aElement. The element should be in the same document
* that the window is displaying. Pass null to blur the element, if any,

View File

@ -276,6 +276,28 @@ nsDOMWindowUtils::SendKeyEvent(const nsAString& aType,
return widget->DispatchEvent(&event, status);
}
NS_IMETHODIMP
nsDOMWindowUtils::SendNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRInt32 aModifiers,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
return NS_ERROR_DOM_SECURITY_ERR;
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidget();
if (!widget)
return NS_ERROR_FAILURE;
widget->SynthesizeNativeKeyEvent(aNativeKeyboardLayout, aNativeKeyCode,
aModifiers, aCharacters, aUnmodifiedCharacters);
return NS_OK;
}
nsIWidget*
nsDOMWindowUtils::GetWidget()
{

View File

@ -95,10 +95,10 @@ typedef nsEventStatus (*PR_CALLBACK EVENT_CALLBACK)(nsGUIEvent *event);
#define NS_NATIVE_PLUGIN_PORT_CG 101
#endif
// 9151e8c9-a1cc-44e9-a70d-afb3956d4e13
// e197eeba-a82b-46d9-8aa9-52e1133fc593
#define NS_IWIDGET_IID \
{ 0x9151e8c9, 0xa1cc, 0x44e9, \
{ 0xa7, 0x0d, 0xaf, 0xb3, 0x95, 0x6d, 0x4e, 0x13 } }
{ 0xe197eeba, 0xa82b, 0x46d9, \
{ 0x8a, 0xa9, 0x52, 0xe1, 0x13, 0x3f, 0xc5, 0x93 } }
// Hide the native window systems real window type so as to avoid
// including native window system types and APIs. This is necessary
@ -1062,6 +1062,47 @@ class nsIWidget : public nsISupports {
*/
NS_IMETHOD BeginResizeDrag(nsGUIEvent* aEvent, PRInt32 aHorizontal, PRInt32 aVertical) = 0;
enum Modifiers {
CAPS_LOCK = 0x01, // when CapsLock is active
NUM_LOCK = 0x02, // when NumLock is active
SHIFT_L = 0x0100,
SHIFT_R = 0x0200,
CTRL_L = 0x0400,
CTRL_R = 0x0800,
ALT_L = 0x1000, // includes Option
ALT_R = 0x2000,
COMMAND = 0x4000,
HELP = 0x8000,
FUNCTION = 0x10000,
NUMERIC_KEY_PAD = 0x01000000 // when the key is coming from the keypad
};
/**
* Utility method intended for testing. Dispatches native key events
* to this widget to simulate the press and release of a key.
* @param aNativeKeyboardLayout a *platform-specific* constant.
* On Mac, this is the resource ID for a 'uchr' or 'kchr' resource.
* On Windows, it is converted to a hex string and passed to
* LoadKeyboardLayout, see
* http://msdn.microsoft.com/en-us/library/ms646305(VS.85).aspx
* @param aNativeKeyCode a *platform-specific* keycode.
* On Windows, this is the virtual key code.
* @param aModifiers some combination of the above 'Modifiers' flags;
* not all flags will apply to all platforms. Mac ignores the _R
* modifiers. Windows ignores COMMAND, NUMERIC_KEY_PAD, HELP and
* FUNCTION.
* @param aCharacters characters that the OS would decide to generate
* from the event. On Windows, this is the charCode passed by
* WM_CHAR.
* @param aUnmodifiedCharacters characters that the OS would decide
* to generate from the event if modifier keys (other than shift)
* were assumed inactive. Needed on Mac, ignored on Windows.
*/
virtual void SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters) = 0;
protected:
// keep the list of children. We also keep track of our siblings.
// The ownership model is as follows: parent holds a strong ref to

View File

@ -385,6 +385,12 @@ protected:
virtual NSView* CreateCocoaView(NSRect inFrame);
void TearDownView();
virtual void SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters);
protected:
NSView<mozView>* mView; // my parallel cocoa view (ChildView or NativeScrollbarView), [STRONG]

View File

@ -157,10 +157,14 @@ nsIWidget * gRollupWidget = nsnull;
- (void)processPendingRedraws;
- (PRBool)processKeyDownEvent:(NSEvent*)theEvent keyEquiv:(BOOL)isKeyEquiv;
- (BOOL)ensureCorrectMouseEventTarget:(NSEvent *)anEvent;
- (void)maybeInitContextMenuTracking;
+ (NSEvent*)makeNewCocoaEventWithType:(NSEventType)type fromEvent:(NSEvent*)theEvent;
#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)
@ -1248,6 +1252,68 @@ void nsChildView::LiveResizeEnded()
mLiveResizeInProgress = PR_FALSE;
}
static NSString* ToNSString(const nsAString& aString)
{
return [NSString stringWithCharacters:aString.BeginReading()
length:aString.Length()];
}
static PRInt32 gOverrideKeyboardLayout;
static const PRUint32 sModifierFlagMap[][2] = {
{ nsIWidget::CAPS_LOCK, NSAlphaShiftKeyMask },
{ nsIWidget::SHIFT_L, NSShiftKeyMask },
{ nsIWidget::CTRL_L, NSControlKeyMask },
{ nsIWidget::ALT_L, NSAlternateKeyMask },
{ nsIWidget::COMMAND, NSCommandKeyMask },
{ nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask },
{ nsIWidget::HELP, NSHelpKeyMask },
{ nsIWidget::FUNCTION, NSFunctionKeyMask }
};
void nsChildView::SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
NS_ASSERTION(aNativeKeyboardLayout, "Layout cannot be 0");
PRUint32 modifierFlags = 0;
for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(sModifierFlagMap); ++i) {
if (aModifierFlags & sModifierFlagMap[i][0]) {
modifierFlags |= sModifierFlagMap[i][1];
}
}
int windowNumber = [[mView window] windowNumber];
NSEvent* downEvent = [NSEvent keyEventWithType:NSKeyDown
location:NSMakePoint(0,0)
modifierFlags:modifierFlags
timestamp:0
windowNumber:windowNumber
context:[NSGraphicsContext currentContext]
characters:ToNSString(aCharacters)
charactersIgnoringModifiers:ToNSString(aUnmodifiedCharacters)
isARepeat:NO
keyCode:aNativeKeyCode];
NSEvent* upEvent = [ChildView makeNewCocoaEventWithType:NSKeyUp
fromEvent:downEvent];
if (downEvent && upEvent) {
PRInt32 currentLayout = gOverrideKeyboardLayout;
gOverrideKeyboardLayout = aNativeKeyboardLayout;
ChildView* view = static_cast<ChildView*>(mView);
[view keyDown:downEvent];
[view keyUp:upEvent];
// processKeyDownEvent and keyUp block exceptions so we're sure to
// reach here to restore gOverrideKeyboardLayout
gOverrideKeyboardLayout = currentLayout;
}
NS_OBJC_END_TRY_ABORT_BLOCK;
}
#pragma mark -
@ -3948,9 +4014,9 @@ static PRBool IsNormalCharInputtingEvent(const nsKeyEvent& aEvent)
// unshiftCharCode for accessKeys and accelKeys.
if (outGeckoEvent->isControl || outGeckoEvent->isMeta ||
outGeckoEvent->isAlt) {
SInt16 keyLayoutID =
::GetScriptVariable(::GetScriptManagerVariable(smKeyScript),
smScriptKeys);
SInt16 keyLayoutID = gOverrideKeyboardLayout ? gOverrideKeyboardLayout
: ::GetScriptVariable(::GetScriptManagerVariable(smKeyScript),
smScriptKeys);
Handle handle = ::GetResource('uchr', keyLayoutID);
PRUint32 unshiftedChar = 0;
PRUint32 shiftedChar = 0;
@ -3979,14 +4045,21 @@ static PRBool IsNormalCharInputtingEvent(const nsKeyEvent& aEvent)
kbType, 0, &deadKeyState, 1, &len, chars);
if (noErr == err && len > 0)
shiftedCmdChar = chars[0];
} else if ((handle = (char**)::GetScriptManagerVariable(smKCHRCache))) {
UInt32 state = 0;
UInt32 keyCode = [aKeyEvent keyCode];
unshiftedChar = ::KeyTranslate(handle, keyCode, &state) & charCodeMask;
keyCode = [aKeyEvent keyCode] | shiftKey;
shiftedChar = ::KeyTranslate(handle, keyCode, &state) & charCodeMask;
keyCode = [aKeyEvent keyCode] | shiftKey | cmdKey;
shiftedCmdChar = ::KeyTranslate(handle, keyCode, &state) & charCodeMask;
} else {
if (gOverrideKeyboardLayout) {
handle = ::GetResource('kchr', gOverrideKeyboardLayout);
} else {
handle = (char**)::GetScriptManagerVariable(smKCHRCache);
}
if (handle) {
UInt32 state = 0;
UInt32 keyCode = [aKeyEvent keyCode];
unshiftedChar = ::KeyTranslate(handle, keyCode, &state) & charCodeMask;
keyCode = [aKeyEvent keyCode] | shiftKey;
shiftedChar = ::KeyTranslate(handle, keyCode, &state) & charCodeMask;
keyCode = [aKeyEvent keyCode] | shiftKey | cmdKey;
shiftedCmdChar = ::KeyTranslate(handle, keyCode, &state) & charCodeMask;
}
}
// If the current keyboad layout is switchable by Cmd key
// (e.g., Dvorak-QWERTY layout), we should not append the alternative
@ -4385,9 +4458,7 @@ static PRBool IsNormalCharInputtingEvent(const nsKeyEvent& aEvent)
if (!textContent.mSucceeded || textContent.mReply.mString.IsEmpty())
return nil;
NSString* nsstr =
[NSString stringWithCharacters:textContent.mReply.mString.get()
length:textContent.mReply.mString.Length()];
NSString* nsstr = ToNSString(textContent.mReply.mString);
NSAttributedString* result =
[[[NSAttributedString alloc] initWithString:nsstr
attributes:nil] autorelease];
@ -4551,6 +4622,20 @@ static PRBool IsNormalCharInputtingEvent(const nsKeyEvent& aEvent)
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
}
#ifdef PR_LOGGING
static const char* ToEscapedString(NSString* aString, nsCAutoString& aBuf)
{
for (PRUint32 i = 0; i < [aString length]; ++i) {
unichar ch = [aString characterAtIndex:i];
if (ch >= 32 && ch < 128) {
aBuf.Append(char(ch));
} else {
aBuf += nsPrintfCString("\\u%04x", ch);
}
}
return aBuf.get();
}
#endif
// Returns PR_TRUE if Gecko claims to have handled the event, PR_FALSE otherwise.
- (PRBool)processKeyDownEvent:(NSEvent*)theEvent keyEquiv:(BOOL)isKeyEquiv
@ -4560,6 +4645,16 @@ static PRBool IsNormalCharInputtingEvent(const nsKeyEvent& aEvent)
if (!mGeckoChild)
return NO;
#ifdef PR_LOGGING
nsCAutoString str1;
nsCAutoString str2;
#endif
PR_LOG(sCocoaLog, PR_LOG_ALWAYS,
("ChildView processKeyDownEvent: keycode=%d,modifiers=%x,chars=%s,charsIgnoringModifiers=%s\n",
[theEvent keyCode], [theEvent modifierFlags],
ToEscapedString([theEvent characters], str1),
ToEscapedString([theEvent charactersIgnoringModifiers], str2)));
nsAutoRetainCocoaObject kungFuDeathGrip(self);
mCurKeyEvent = theEvent;
@ -4744,6 +4839,16 @@ static BOOL keyUpAlreadySentKeyDown = NO;
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
#ifdef PR_LOGGING
nsCAutoString str1;
nsCAutoString str2;
#endif
PR_LOG(sCocoaLog, PR_LOG_ALWAYS,
("ChildView keyUp: keycode=%d,modifiers=%x,chars=%s,charsIgnoringModifiers=%s\n",
[theEvent keyCode], [theEvent modifierFlags],
ToEscapedString([theEvent characters], str1),
ToEscapedString([theEvent charactersIgnoringModifiers], str2)));
if (mGeckoChild && mIsPluginView) {
// I'm not sure the call to TSMProcessRawKeyEvent() is needed here (though
// WebKit makes one). But we definitely need to short-circuit NSKeyUp

View File

@ -2325,7 +2325,7 @@ IsBasicLatinLetterOrNumeral(PRUint32 aChar)
}
gboolean
nsWindow::OnKeyPressEvent(GtkWidget *aWidget, GdkEventKey *aEvent)
nsWindow::OnKeyPressEvent(GdkEventKey *aEvent)
{
LOGFOCUS(("OnKeyPressEvent [%p]\n", (void *)this));
@ -2500,7 +2500,7 @@ nsWindow::OnKeyPressEvent(GtkWidget *aWidget, GdkEventKey *aEvent)
}
gboolean
nsWindow::OnKeyReleaseEvent(GtkWidget *aWidget, GdkEventKey *aEvent)
nsWindow::OnKeyReleaseEvent(GdkEventKey *aEvent)
{
LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void *)this));
@ -2528,6 +2528,17 @@ nsWindow::OnKeyReleaseEvent(GtkWidget *aWidget, GdkEventKey *aEvent)
return FALSE;
}
void
nsWindow::SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters,
PRBool aAllowIME)
{
}
void
nsWindow::OnScrollEvent(GtkWidget *aWidget, GdkEventScroll *aEvent)
{
@ -4791,7 +4802,7 @@ key_press_event_cb(GtkWidget *widget, GdkEventKey *event)
nsRefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
return focusWindow->OnKeyPressEvent(widget, event);
return focusWindow->OnKeyPressEvent(event);
}
gboolean
@ -4805,7 +4816,7 @@ key_release_event_cb(GtkWidget *widget, GdkEventKey *event)
nsRefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
return focusWindow->OnKeyReleaseEvent(widget, event);
return focusWindow->OnKeyReleaseEvent(event);
}
/* static */

View File

@ -186,10 +186,8 @@ public:
GdkEventFocus *aEvent);
void OnContainerFocusOutEvent(GtkWidget *aWidget,
GdkEventFocus *aEvent);
gboolean OnKeyPressEvent(GtkWidget *aWidget,
GdkEventKey *aEvent);
gboolean OnKeyReleaseEvent(GtkWidget *aWidget,
GdkEventKey *aEvent);
gboolean OnKeyPressEvent(GdkEventKey *aEvent);
gboolean OnKeyReleaseEvent(GdkEventKey *aEvent);
void OnScrollEvent(GtkWidget *aWidget,
GdkEventScroll *aEvent);
void OnVisibilityNotifyEvent(GtkWidget *aWidget,
@ -364,6 +362,12 @@ public:
gfxASurface *GetThebesSurface();
virtual void SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters);
#ifdef ACCESSIBILITY
static PRBool sAccessibilityEnabled;
#endif

View File

@ -204,7 +204,7 @@ KeyboardLayout::KeyboardLayout ()
mDeadKeyTableListHead = nsnull;
#endif
LoadLayout ();
LoadLayout (::GetKeyboardLayout(0));
}
KeyboardLayout::~KeyboardLayout ()
@ -348,7 +348,7 @@ KeyboardLayout::GetUniCharsWithShiftState(PRUint8 aVirtualKey,
#endif
}
void KeyboardLayout::LoadLayout ()
void KeyboardLayout::LoadLayout (HKL aLayout)
{
#ifndef WINCE
PRUint32 shiftState;
@ -361,7 +361,7 @@ void KeyboardLayout::LoadLayout ()
mActiveDeadKey = -1;
mNumOfChars = 0;
mKeyboardLayout = ::GetKeyboardLayout (0);
mKeyboardLayout = aLayout;
ReleaseDeadKeyTables ();
@ -385,7 +385,7 @@ void KeyboardLayout::LoadLayout ()
PRUint16 uniChars [5];
PRInt32 rv;
rv = ::ToUnicode (virtualKey, 0, kbdState, (LPWSTR)uniChars, NS_ARRAY_LENGTH (uniChars), 0);
rv = ::ToUnicodeEx (virtualKey, 0, kbdState, (LPWSTR)uniChars, NS_ARRAY_LENGTH (uniChars), 0, mKeyboardLayout);
if (rv < 0) // dead-key
{
@ -394,7 +394,7 @@ void KeyboardLayout::LoadLayout ()
// Repeat dead-key to deactivate it and get its character representation.
PRUint16 deadChar [2];
rv = ::ToUnicode (virtualKey, 0, kbdState, (LPWSTR)deadChar, NS_ARRAY_LENGTH (deadChar), 0);
rv = ::ToUnicodeEx (virtualKey, 0, kbdState, (LPWSTR)deadChar, NS_ARRAY_LENGTH (deadChar), 0, mKeyboardLayout);
NS_ASSERTION (rv == 2, "Expecting twice repeated dead-key character");
@ -583,7 +583,7 @@ PRBool KeyboardLayout::EnsureDeadKeyActive (PRBool aIsActive, PRUint8 aDeadKey,
{
PRUint16 dummyChars [5];
rv = ::ToUnicode (aDeadKey, 0, (PBYTE)aDeadKeyKbdState, (LPWSTR)dummyChars, NS_ARRAY_LENGTH (dummyChars), 0);
rv = ::ToUnicodeEx (aDeadKey, 0, (PBYTE)aDeadKeyKbdState, (LPWSTR)dummyChars, NS_ARRAY_LENGTH (dummyChars), 0, mKeyboardLayout);
// returned values:
// <0 - Dead key state is active. The keyboard driver will wait for next character.
// 1 - Previous pressed key was a valid base character that produced exactly one composite character.
@ -654,7 +654,7 @@ PRUint32 KeyboardLayout::GetDeadKeyCombinations (PRUint8 aDeadKey, const PBYTE a
PRUint16 compositeChars [5];
PRInt32 rv;
rv = ::ToUnicode (virtualKey, 0, kbdState, (LPWSTR)compositeChars, NS_ARRAY_LENGTH (compositeChars), 0);
rv = ::ToUnicodeEx (virtualKey, 0, kbdState, (LPWSTR)compositeChars, NS_ARRAY_LENGTH (compositeChars), 0, mKeyboardLayout);
switch (rv)
{
@ -668,7 +668,7 @@ PRUint32 KeyboardLayout::GetDeadKeyCombinations (PRUint8 aDeadKey, const PBYTE a
// character one more time to determine the base character.
PRUint16 baseChars [5];
rv = ::ToUnicode (virtualKey, 0, kbdState, (LPWSTR)baseChars, NS_ARRAY_LENGTH (baseChars), 0);
rv = ::ToUnicodeEx (virtualKey, 0, kbdState, (LPWSTR)baseChars, NS_ARRAY_LENGTH (baseChars), 0, mKeyboardLayout);
NS_ASSERTION (rv == 1, "One base character expected");

View File

@ -177,7 +177,7 @@ public:
#endif
}
void LoadLayout ();
void LoadLayout (HKL aLayout);
void OnKeyDown (PRUint8 aVirtualKey);
PRUint32 GetUniChars (PRUint16* aUniChars, PRUint8* aShiftStates, PRUint32 aMaxChars) const;
PRUint32 GetUniCharsWithShiftState(PRUint8 aVirtualKey, PRUint8 aShiftStates,

View File

@ -79,6 +79,7 @@
#include <windows.h>
#include <process.h>
#include "nsUnicharUtils.h"
#include "prlog.h"
#ifdef WINCE
#include "aygshell.h"
@ -151,6 +152,10 @@
#include "prprf.h"
#include "prmem.h"
#ifdef PR_LOGGING
PRLogModuleInfo* sWindowsLog = nsnull;
#endif
static const char kMozHeapDumpMessageString[] = "MOZ_HeapDump";
#define kWindowPositionSlop 20
@ -647,6 +652,11 @@ void nsWindow::GlobalMsgWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lP
//-------------------------------------------------------------------------
nsWindow::nsWindow() : nsBaseWidget()
{
#ifdef PR_LOGGING
if (!sWindowsLog)
sWindowsLog = PR_NewLogModule("nsWindowsWidgets");
#endif
mWnd = 0;
mPaintDC = 0;
mPrevWndProc = NULL;
@ -3115,19 +3125,33 @@ StringCaseInsensitiveEquals(const PRUint16* aChars1, const PRUint32 aNumChars1,
return comp(aChars1, aChars2, aNumChars1) == 0;
}
/**
* nsWindow::OnKeyDown peeks into the message queue and pulls out
* WM_CHAR messages for processing. During testing we don't want to
* mess with the real message queue. Instead we pass a
* pseudo-WM_CHAR-message using this structure, and OnKeyDown will use
* that as if it was in the message queue, and refrain from actually
* looking at or touching the message queue.
*/
struct nsFakeCharMessage {
UINT mCharCode;
UINT mScanCode;
};
//-------------------------------------------------------------------------
//
//
//-------------------------------------------------------------------------
BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData)
BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, LPARAM aKeyData,
nsFakeCharMessage* aFakeCharMessage)
{
#ifdef VK_BROWSER_BACK
#ifdef VK_BROWSER_BACK
// VK_BROWSER_BACK and VK_BROWSER_FORWARD are converted to nsCommandEvents
if (aVirtualKeyCode == VK_BROWSER_BACK)
{
DispatchCommandEvent(APPCOMMAND_BROWSER_BACKWARD);
return TRUE;
}
}
else if (aVirtualKeyCode == VK_BROWSER_FORWARD)
{
DispatchCommandEvent(APPCOMMAND_BROWSER_FORWARD);
@ -3143,7 +3167,7 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData)
aVirtualKeyCode : MapFromNativeToDOM(aVirtualKeyCode);
#ifdef DEBUG
//printf("In OnKeyDown virt: %d scan: %d\n", DOMKeyCode, aScanCode);
//printf("In OnKeyDown virt: %d\n", DOMKeyCode);
#endif
BOOL noDefault = DispatchKeyEvent(NS_KEY_DOWN, 0, nsnull, DOMKeyCode, aKeyData);
@ -3161,8 +3185,8 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData)
PRUint32 extraFlags = (noDefault ? NS_EVENT_FLAG_NO_DEFAULT : 0);
MSG msg;
BOOL gotMsg = ::PeekMessageW(&msg, mWnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD);
PRBool anyCharMessagesRemoved = PR_FALSE;
BOOL gotMsg = aFakeCharMessage ||
::PeekMessageW(&msg, mWnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD);
// Enter and backspace are always handled here to avoid for example the
// confusion between ctrl-enter and ctrl-J.
if (DOMKeyCode == NS_VK_RETURN || DOMKeyCode == NS_VK_BACK ||
@ -3173,14 +3197,21 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData)
// They can be more than one because of:
// * Dead-keys not pairing with base character
// * Some keyboard layouts may map up to 4 characters to the single key
PRBool anyCharMessagesRemoved = PR_FALSE;
while (gotMsg && (msg.message == WM_CHAR || msg.message == WM_SYSCHAR))
{
::GetMessageW(&msg, mWnd, WM_KEYFIRST, WM_KEYLAST);
if (aFakeCharMessage) {
anyCharMessagesRemoved = PR_TRUE;
gotMsg = ::PeekMessageW (&msg, mWnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD);
} else {
while (gotMsg && (msg.message == WM_CHAR || msg.message == WM_SYSCHAR))
{
PR_LOG(sWindowsLog, PR_LOG_ALWAYS,
("%s charCode=%d scanCode=%d\n", msg.message == WM_SYSCHAR ? "WM_SYSCHAR" : "WM_CHAR",
msg.wParam, HIWORD(msg.lParam) & 0xFF));
::GetMessageW(&msg, mWnd, WM_KEYFIRST, WM_KEYLAST);
anyCharMessagesRemoved = PR_TRUE;
gotMsg = ::PeekMessageW (&msg, mWnd, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD);
}
}
if (!anyCharMessagesRemoved && DOMKeyCode == NS_VK_BACK) {
@ -3210,24 +3241,29 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData)
// http://bugzilla.mozilla.gr.jp/show_bug.cgi?id=2885 (written in Japanese)
// http://bugzilla.mozilla.org/show_bug.cgi?id=194559 (written in English)
NS_ASSERTION(!aFakeCharMessage, "We shouldn't be touching the real msg queue");
::GetMessageW(&msg, mWnd, WM_CHAR, WM_CHAR);
}
}
}
else if (gotMsg &&
(msg.message == WM_CHAR || msg.message == WM_SYSCHAR || msg.message == WM_DEADCHAR)) {
(aFakeCharMessage ||
msg.message == WM_CHAR || msg.message == WM_SYSCHAR || msg.message == WM_DEADCHAR)) {
if (aFakeCharMessage)
return OnChar(aFakeCharMessage->mCharCode, aFakeCharMessage->mScanCode, extraFlags);
// If prevent default set for keydown, do same for keypress
::GetMessageW(&msg, mWnd, msg.message, msg.message);
if (msg.message == WM_DEADCHAR)
return PR_FALSE;
#ifdef KE_DEBUG
printf("%s\tchar=%c\twp=%4x\tlp=%8x\n",
(msg.message == WM_SYSCHAR) ? "WM_SYSCHAR" : "WM_CHAR",
msg.wParam, msg.wParam, msg.lParam);
#endif
BOOL result = OnChar(msg.wParam, msg.lParam, extraFlags);
PR_LOG(sWindowsLog, PR_LOG_ALWAYS,
("%s charCode=%d scanCode=%d\n",
msg.message == WM_SYSCHAR ? "WM_SYSCHAR" : "WM_CHAR",
msg.wParam, HIWORD(msg.lParam) & 0xFF));
BOOL result = OnChar(msg.wParam, HIWORD(msg.lParam) & 0xFF, extraFlags);
// If a syschar keypress wasn't processed, Windows may want to
// handle it to activate a native menu.
if (!result && msg.message == WM_SYSCHAR)
@ -3402,9 +3438,11 @@ BOOL nsWindow::OnKeyDown(UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData)
//
//
//-------------------------------------------------------------------------
BOOL nsWindow::OnKeyUp( UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData)
BOOL nsWindow::OnKeyUp( UINT aVirtualKeyCode, LPARAM aKeyData)
{
#ifdef VK_BROWSER_BACK
PR_LOG(sWindowsLog, PR_LOG_ALWAYS, ("nsWindow::OnKeyUp VK=%d\n", aVirtualKeyCode));
#ifdef VK_BROWSER_BACK
if (aVirtualKeyCode == VK_BROWSER_BACK || aVirtualKeyCode == VK_BROWSER_FORWARD)
return TRUE;
#endif
@ -3419,14 +3457,8 @@ BOOL nsWindow::OnKeyUp( UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyData)
//
//
//-------------------------------------------------------------------------
BOOL nsWindow::OnChar(UINT charCode, LPARAM keyData, PRUint32 aFlags)
BOOL nsWindow::OnChar(UINT charCode, UINT aScanCode, PRUint32 aFlags)
{
// These must be checked here too as a lone WM_CHAR could be received
// if a child window didn't handle it (for example Alt+Space in a content window)
mIsShiftDown = IS_VK_DOWN(NS_VK_SHIFT);
mIsControlDown = IS_VK_DOWN(NS_VK_CONTROL);
mIsAltDown = IS_VK_DOWN(NS_VK_ALT);
// ignore [shift+]alt+space so the OS can handle it
if (mIsAltDown && !mIsControlDown && IS_VK_DOWN(NS_VK_SPACE)) {
return FALSE;
@ -3471,10 +3503,10 @@ BOOL nsWindow::OnChar(UINT charCode, LPARAM keyData, PRUint32 aFlags)
// Keep the characters unshifted for shortcuts and accesskeys and make sure
// that numbers are always passed as such (among others: bugs 50255 and 351310)
if (uniChar && (mIsControlDown || mIsAltDown)) {
UINT virtualKeyCode = ::MapVirtualKey(HIWORD(keyData) & 0xFF, MAPVK_VSC_TO_VK);
UINT virtualKeyCode = ::MapVirtualKeyEx(aScanCode, MAPVK_VSC_TO_VK, gKeyboardLayout);
UINT unshiftedCharCode =
virtualKeyCode >= '0' && virtualKeyCode <= '9' ? virtualKeyCode :
mIsShiftDown ? ::MapVirtualKey(virtualKeyCode, MAPVK_VK_TO_CHAR) : 0;
mIsShiftDown ? ::MapVirtualKeyEx(virtualKeyCode, MAPVK_VK_TO_CHAR, gKeyboardLayout) : 0;
// ignore diacritics (top bit set) and key mapping errors (char code 0)
if ((INT)unshiftedCharCode > 0)
uniChar = unshiftedCharCode;
@ -3494,6 +3526,96 @@ BOOL nsWindow::OnChar(UINT charCode, LPARAM keyData, PRUint32 aFlags)
return result;
}
static const PRUint32 sModifierKeyMap[][3] = {
{ nsIWidget::CAPS_LOCK, VK_CAPITAL, 0 },
{ nsIWidget::NUM_LOCK, VK_NUMLOCK, 0 },
{ nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT },
{ nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT },
{ nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL },
{ nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL },
{ nsIWidget::ALT_L, VK_MENU, VK_LMENU },
{ nsIWidget::ALT_R, VK_MENU, VK_RMENU }
};
struct KeyPair {
PRUint8 mGeneral;
PRUint8 mSpecific;
KeyPair(PRUint32 aGeneral, PRUint32 aSpecific)
: mGeneral(PRUint8(aGeneral)), mSpecific(PRUint8(aSpecific)) {}
};
static void
SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray, PRUint32 aModifiers)
{
for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(sModifierKeyMap); ++i) {
const PRUint32* map = sModifierKeyMap[i];
if (aModifiers & map[0]) {
aArray->AppendElement(KeyPair(map[1], map[2]));
}
}
}
void
nsWindow::SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters)
{
// Setup clean key state and load desired layout
BYTE originalKbdState[256];
::GetKeyboardState(originalKbdState);
BYTE kbdState[256];
memset(kbdState, 0, sizeof(kbdState));
// This changes the state of the keyboard for the current thread only,
// and we'll restore it soon, so this should be OK.
::SetKeyboardState(kbdState);
HKL oldLayout = gKeyboardLayout;
gKeyboardLayout = (HKL)aNativeKeyboardLayout;
gKbdLayout.LoadLayout(gKeyboardLayout);
nsAutoTArray<KeyPair,10> keySequence;
SetupKeyModifiersSequence(&keySequence, aModifierFlags);
NS_ASSERTION(aNativeKeyCode >= 0 && aNativeKeyCode < 256,
"Native VK key code out of range");
keySequence.AppendElement(KeyPair(aNativeKeyCode, 0));
// Simulate the pressing of each modifier key and then the real key
for (PRUint32 i = 0; i < keySequence.Length(); ++i) {
PRUint8 key = keySequence[i].mGeneral;
PRUint8 keySpecific = keySequence[i].mSpecific;
kbdState[key] = 0x81; // key is down and toggled on if appropriate
if (keySpecific) {
kbdState[keySpecific] = 0x81;
}
::SetKeyboardState(kbdState);
SetupModKeyState();
if (i == keySequence.Length() - 1 && aCharacters.Length() > 0) {
UINT scanCode = ::MapVirtualKeyEx(aNativeKeyCode, MAPVK_VK_TO_VSC, gKeyboardLayout);
nsFakeCharMessage msg = { aCharacters.CharAt(0), scanCode };
OnKeyDown(key, 0, &msg);
} else {
OnKeyDown(key, 0, nsnull);
}
}
for (PRUint32 i = keySequence.Length(); i > 0; --i) {
PRUint8 key = keySequence[i - 1].mGeneral;
PRUint8 keySpecific = keySequence[i - 1].mSpecific;
kbdState[key] = 0; // key is up and toggled off if appropriate
if (keySpecific) {
kbdState[keySpecific] = 0;
}
::SetKeyboardState(kbdState);
SetupModKeyState();
OnKeyUp(key, 0);
}
// Restore old key state and layout
::SetKeyboardState(originalKbdState);
gKeyboardLayout = oldLayout;
gKbdLayout.LoadLayout(gKeyboardLayout);
SetupModKeyState();
}
void nsWindow::ConstrainZLevel(HWND *aAfter)
{
@ -4003,6 +4125,13 @@ void nsWindow::PostSleepWakeNotification(const char* aNotification)
}
#endif
void nsWindow::SetupModKeyState()
{
mIsShiftDown = IS_VK_DOWN(NS_VK_SHIFT);
mIsControlDown = IS_VK_DOWN(NS_VK_CONTROL);
mIsAltDown = IS_VK_DOWN(NS_VK_ALT);
}
PRBool nsWindow::ProcessMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *aRetValue)
{
static UINT vkKeyCached = 0; // caches VK code fon WM_KEYDOWN
@ -4237,22 +4366,24 @@ PRBool nsWindow::ProcessMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT
case WM_SYSCHAR:
case WM_CHAR:
{
#ifdef KE_DEBUG
printf("%s\tchar=%c\twp=%4x\tlp=%8x\n", (msg == WM_SYSCHAR) ? "WM_SYSCHAR" : "WM_CHAR", wParam, wParam, lParam);
#endif
result = OnChar(wParam, lParam);
PR_LOG(sWindowsLog, PR_LOG_ALWAYS,
("%s charCode=%d scanCode=%d\n", msg == WM_SYSCHAR ? "WM_SYSCHAR" : "WM_CHAR",
wParam, HIWORD(lParam) & 0xFF));
// These must be checked here too as a lone WM_CHAR could be received
// if a child window didn't handle it (for example Alt+Space in a content window)
SetupModKeyState();
result = OnChar(wParam, HIWORD(lParam) & 0xFF);
}
break;
case WM_SYSKEYUP:
case WM_KEYUP:
PR_LOG(sWindowsLog, PR_LOG_ALWAYS,
("%s VK=%d\n", msg == WM_SYSKEYDOWN ? "WM_SYSKEYUP" : "WM_KEYUP", wParam));
#ifdef KE_DEBUG
printf("%s\t\twp=%x\tlp=%x\n", (WM_KEYUP==msg) ? "WM_KEYUP" : "WM_SYSKEYUP", wParam, lParam);
#endif
mIsShiftDown = IS_VK_DOWN(NS_VK_SHIFT);
mIsControlDown = IS_VK_DOWN(NS_VK_CONTROL);
mIsAltDown = IS_VK_DOWN(NS_VK_ALT);
SetupModKeyState();
// Note: the original code passed (HIWORD(lParam)) to OnKeyUp as
// scan code. However, this breaks Alt+Num pad input.
@ -4276,7 +4407,7 @@ PRBool nsWindow::ProcessMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT
// This helps avoid triggering the menu bar for ALT key accelerators used in
// assistive technologies such as Window-Eyes and ZoomText, and when using Alt+Tab
// to switch back to Mozilla in Windows 95 and Windows 98
result = OnKeyUp(wParam, (HIWORD(lParam)), lParam);
result = OnKeyUp(wParam, lParam);
}
else {
result = PR_FALSE;
@ -4288,13 +4419,10 @@ PRBool nsWindow::ProcessMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT
// Let the fall through if it isn't a key pad
case WM_SYSKEYDOWN:
case WM_KEYDOWN:
#ifdef KE_DEBUG
printf("%s\t\twp=%4x\tlp=%8x\n", (WM_KEYDOWN==msg) ? "WM_KEYDOWN" : "WM_SYSKEYDOWN", wParam, lParam);
#endif
PR_LOG(sWindowsLog, PR_LOG_ALWAYS,
("%s VK=%d\n", msg == WM_SYSKEYDOWN ? "WM_SYSKEYDOWN" : "WM_KEYDOWN", wParam));
mIsShiftDown = IS_VK_DOWN(NS_VK_SHIFT);
mIsControlDown = IS_VK_DOWN(NS_VK_CONTROL);
mIsAltDown = IS_VK_DOWN(NS_VK_ALT);
SetupModKeyState();
// Note: the original code passed (HIWORD(lParam)) to OnKeyDown as
// scan code. However, this breaks Alt+Num pad input.
@ -4318,7 +4446,7 @@ PRBool nsWindow::ProcessMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT
result = PR_FALSE;
}
else if (!sIMEIsComposing) {
result = OnKeyDown(wParam, (HIWORD(lParam)), lParam);
result = OnKeyDown(wParam, lParam, nsnull);
}
else
result = PR_FALSE;
@ -6570,8 +6698,7 @@ BOOL nsWindow::OnInputLangChange(HKL aHKL, LRESULT *oRetValue)
if (gKeyboardLayout != aHKL)
{
gKeyboardLayout = aHKL;
gKbdLayout.LoadLayout();
gKbdLayout.LoadLayout(gKeyboardLayout);
}
ResetInputState();

View File

@ -67,6 +67,7 @@ class nsIFile;
class imgIContainer;
struct nsAlternativeCharCode;
struct nsFakeCharMessage;
#ifdef ACCESSIBILITY
#include "OLEACC.H"
@ -308,11 +309,12 @@ protected:
virtual PRBool OnMove(PRInt32 aX, PRInt32 aY);
virtual PRBool OnPaint(HDC aDC = nsnull);
virtual PRBool OnResize(nsRect &aWindowRect);
BOOL OnChar(UINT charCode, LPARAM keyData, PRUint32 aFlags = 0);
BOOL OnKeyDown( UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyCode);
BOOL OnKeyUp( UINT aVirtualKeyCode, UINT aScanCode, LPARAM aKeyCode);
void SetupModKeyState();
BOOL OnChar(UINT charCode, UINT aScanCode, PRUint32 aFlags = 0);
BOOL OnKeyDown( UINT aVirtualKeyCode, LPARAM aKeyCode,
nsFakeCharMessage* aFakeCharMessage);
BOOL OnKeyUp( UINT aVirtualKeyCode, LPARAM aKeyCode);
UINT MapFromNativeToDOM(UINT aNativeKeyCode);
@ -367,6 +369,12 @@ protected:
PRBool CanTakeFocus();
virtual void SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters);
private:

View File

@ -156,6 +156,12 @@ protected:
return mLastRollup;
}
virtual void SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters) {}
protected:
void* mClientData;
EVENT_CALLBACK mEventCallback;

View File

@ -45,6 +45,7 @@ include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_TEST_FILES = test_bug343416.xul \
test_keycodes.xul \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,347 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Key event tests"
onload="runTest()"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<title>Key event tests</title>
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display">
<!-- for some reason, if we don't have 'accesskey' here, adding it dynamically later
doesn't work! -->
<button id="button" accesskey="z">Hello</button>
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
function synthesizeNativeKey(aLayout, aKeyCode, aModifiers, aSystemChars,
aSystemUnmodifiedChars, aWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
if (!aWindow)
aWindow = window;
var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
if (utils) {
var modifiers = 0;
if (aModifiers.capsLock) modifiers |= 0x01;
if (aModifiers.numLock) modifiers |= 0x02;
if (aModifiers.shift) modifiers |= 0x0100;
if (aModifiers.shiftRight) modifiers |= 0x0200;
if (aModifiers.ctrl) modifiers |= 0x0400;
if (aModifiers.ctrlRight) modifiers |= 0x0800;
if (aModifiers.alt) modifiers |= 0x1000;
if (aModifiers.altRight) modifiers |= 0x2000;
if (aModifiers.command) modifiers |= 0x4000;
if (aModifiers.help) modifiers |= 0x8000;
if (aModifiers.function) modifiers |= 0x10000;
if (aModifiers.numericKeyPad) modifiers |= 0x01000000;
utils.sendNativeKeyEvent(aLayout, aKeyCode, modifiers,
aSystemChars, aSystemUnmodifiedChars);
}
}
var keyboardLayouts;
if (navigator.platform.indexOf("Mac") == 0) {
// These constants can be found by inspecting files under
// /System/Library/Keyboard\ Layouts/Unicode.bundle/Contents/Resources/
keyboardLayouts = {
"US-Extended":-2,
"Greek":-18944
};
} else if (navigator.platform.indexOf("Win") == 0) {
// These constants can be found by inspecting registry keys under
// HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Keyboard Layouts
keyboardLayouts = {
"US":0x409,
"Greek":0x408
};
}
// Test the charcodes and modifiers being delivered to keypress handlers.
function runPressTests()
{
var pressList;
function onKeyPress(e)
{
pressList.push(e);
e.preventDefault();
}
// The first parameter is the complete input event. The second parameter is
// what to test against.
// XXX should probably check that keydown and keyup events were dispatched too
function testKey(aEvent, aExpectGeckoChar)
{
pressList = [];
synthesizeNativeKey(keyboardLayouts[aEvent.layout],
aEvent.keyCode, aEvent, aEvent.chars, aEvent.unmodifiedChars);
var name = aEvent.layout + " '" + aEvent.chars + "'";
if (aEvent.shift) {
name += " [Shift]";
}
if (aEvent.ctrl) {
name += " [Ctrl]";
}
if (aEvent.alt) {
name += " [Alt]";
}
is(pressList.length, aExpectGeckoChar == "" ? 0 : 1, name + ", wrong number of press events");
if (pressList.length == 0)
return;
var e = pressList[0];
is(e.ctrlKey, aEvent.ctrl || 0, name + ", Ctrl mismatch");
is(e.metaKey, aEvent.command || 0, name + ", Command mismatch");
is(e.altKey, aEvent.alt || 0, name + ", Alt mismatch");
is(e.shiftKey, aEvent.shift || 0, name + ", Shift mismatch");
if (aExpectGeckoChar.length > 0) {
is(e.charCode, aExpectGeckoChar.charCodeAt(0), name + ", charcode");
} else {
is(e.charCode, 0, name + ", no charcode");
}
}
document.addEventListener("keypress", onKeyPress, false);
// These tests have to be per-plaform.
if (navigator.platform.indexOf("Mac") == 0) {
// On Mac, you can produce event records for any desired keyboard input
// by running with NSPR_LOG_MODULES=nsCocoaWidgets:5 and typing into the browser.
// We will dump the key event fields to the console. Use the International system
// preferences widget to enable other keyboard layouts and select them from the
// input menu to see what keyboard events they generate.
// Note that it's possible to send bogus key events here, e.g.
// {keyCode:0, chars:"z", unmodifiedChars:"P"} --- sendNativeKeyEvent
// makes no attempt to verify that the keyCode matches the characters. So only
// test key event records that you saw Cocoa send.
// Plain text input
testKey({layout:"US-Extended", keyCode:0, chars:"a", unmodifiedChars:"a"},
"a");
testKey({layout:"US-Extended", keyCode:11, chars:"b", unmodifiedChars:"b"},
"b");
testKey({layout:"US-Extended", keyCode:0, shift:1, chars:"A", unmodifiedChars:"A"},
"A");
// Ctrl keys
testKey({layout:"US-Extended", keyCode:0, ctrl:1, chars:"\u0001", unmodifiedChars:"a"},
"a");
testKey({layout:"US-Extended", keyCode:0, ctrl:1, shift:1, chars:"\u0001", unmodifiedChars:"A"},
"A");
// Alt keys
testKey({layout:"US-Extended", keyCode:0, alt:1, chars:"\u00e5", unmodifiedChars:"a"},
"\u00e5");
testKey({layout:"US-Extended", keyCode:0, alt:1, shift:1, chars:"\u00c5", unmodifiedChars:"A"},
"\u00c5");
// Command keys
testKey({layout:"US-Extended", keyCode:0, command:1, chars:"a", unmodifiedChars:"a"},
"a");
// Shift-cmd gives us the unshifted character
testKey({layout:"US-Extended", keyCode:0, command:1, shift:1, chars:"a", unmodifiedChars:"A"},
"a");
// Ctrl-cmd gives us the unshifted character
testKey({layout:"US-Extended", keyCode:0, command:1, ctrl:1, chars:"\u0001", unmodifiedChars:"a"},
"a");
// Alt-cmd gives us the *shifted* character
testKey({layout:"US-Extended", keyCode:0, command:1, alt:1, chars:"\u00e5", unmodifiedChars:"a"},
"\u00e5");
testKey({layout:"US-Extended", keyCode:0, command:1, alt:1, shift:1, chars:"\u00c5", unmodifiedChars:"a"},
"\u00c5");
// Greek ctrl keys produce Latin charcodes
testKey({layout:"Greek", keyCode:0, ctrl:1, chars:"\u0001", unmodifiedChars:"\u03b1"},
"a");
testKey({layout:"Greek", keyCode:0, ctrl:1, shift:1, chars:"\u0001", unmodifiedChars:"\u0391"},
"A");
// Greek command keys
testKey({layout:"Greek", keyCode:0, command:1, chars:"a", unmodifiedChars:"\u03b1"},
"a");
// Shift-cmd gives us the unshifted character
testKey({layout:"Greek", keyCode:0, command:1, shift:1, chars:"a", unmodifiedChars:"\u391"},
"a");
// Ctrl-cmd gives us the unshifted character
testKey({layout:"Greek", keyCode:0, command:1, ctrl:1, chars:"\u0001", unmodifiedChars:"\u03b1"},
"a");
// Alt-cmd gives us the *shifted* character
testKey({layout:"Greek", keyCode:0, command:1, alt:1, chars:"\u00a8", unmodifiedChars:"\u03b1"},
"\u00a8");
testKey({layout:"Greek", keyCode:0, command:1, alt:1, shift:1, chars:"\u00b9", unmodifiedChars:"\u0391"},
"\u00b9");
}
if (navigator.platform.indexOf("Win") == 0) {
// On Windows, you can use Spy++ or Winspector (free) to watch window messages.
// The keyCode is given by the wParam of the last WM_KEYDOWN message. The
// chars string is given by the wParam of the WM_CHAR message. unmodifiedChars
// is not needed on Windows.
// Plain text input
testKey({layout:"US", keyCode:65, chars:"a"},
"a");
testKey({layout:"US", keyCode:66, chars:"b"},
"b");
testKey({layout:"US", keyCode:65, shift:1, chars:"A"},
"A");
// Ctrl keys
testKey({layout:"US", keyCode:65, ctrl:1, chars:"\u0001"},
"a");
testKey({layout:"US", keyCode:65, ctrl:1, shift:1, chars:"\u0001"},
"A");
// Alt keys
testKey({layout:"US", keyCode:65, alt:1, chars:"a"},
"a");
testKey({layout:"US", keyCode:65, alt:1, shift:1, chars:"A"},
"A");
// Shift-ctrl-alt generates no WM_CHAR, but we still get a keypress
testKey({layout:"US", keyCode:65, alt:1, ctrl:1, shift:1, chars:""},
"A");
// Greek plain text
testKey({layout:"Greek", keyCode:65, chars:"\u03b1"},
"\u03b1");
testKey({layout:"Greek", keyCode:65, shift:1, chars:"\u0391"},
"\u0391");
// Greek ctrl keys produce Latin charcodes
testKey({layout:"Greek", keyCode:65, ctrl:1, chars:"\u0001"},
"a");
testKey({layout:"Greek", keyCode:65, ctrl:1, shift:1, chars:"\u0001"},
"A");
}
document.removeEventListener("keypress", onKeyPress, false);
}
// Test the activation (or not) of an HTML accesskey
function runAccessKeyTests()
{
var button = document.getElementById("button");
var activationCount;
function onClick(e)
{
++activationCount;
}
// The first parameter is the complete input event. The second and third parameters are
// what to test against.
function testKey(aEvent, aAccessKey, aShouldActivate)
{
activationCount = 0;
button.setAttribute("accesskey", aAccessKey);
synthesizeNativeKey(keyboardLayouts[aEvent.layout],
aEvent.keyCode, aEvent, aEvent.chars, aEvent.unmodifiedChars);
var name = aEvent.layout + " '" + aEvent.chars + "'";
is(activationCount, aShouldActivate ? 1 : 0, name + ", activating '" + aAccessKey + "'");
}
button.addEventListener("click", onClick, false);
// These tests have to be per-plaform.
if (navigator.platform.indexOf("Mac") == 0) {
// Basic sanity checks
testKey({layout:"US-Extended", keyCode:0, chars:"a", unmodifiedChars:"a"},
"a", false);
testKey({layout:"US-Extended", keyCode:0, ctrl:1, chars:"\u0001", unmodifiedChars:"a"},
"a", true);
testKey({layout:"US-Extended", keyCode:0, ctrl:1, chars:"\u0001", unmodifiedChars:"a"},
"A", true);
// Shift-ctrl does not activate accesskeys
testKey({layout:"US-Extended", keyCode:0, ctrl:1, shift:1, chars:"\u0001", unmodifiedChars:"A"},
"a", false);
testKey({layout:"US-Extended", keyCode:0, ctrl:1, shift:1, chars:"\u0001", unmodifiedChars:"A"},
"A", false);
// Alt-ctrl does not activate accesskeys
testKey({layout:"US-Extended", keyCode:0, ctrl:1, alt:1, chars:"\u0001", unmodifiedChars:"a"},
"a", false);
testKey({layout:"US-Extended", keyCode:0, ctrl:1, alt:1, chars:"\u0001", unmodifiedChars:"a"},
"A", false);
// Greek layout can activate a Latin accesskey
testKey({layout:"Greek", keyCode:0, ctrl:1, chars:"\u0001", unmodifiedChars:"\u03b1"},
"a", true);
testKey({layout:"Greek", keyCode:0, ctrl:1, chars:"\u0001", unmodifiedChars:"\u03b1"},
"A", true);
// ... and a Greek accesskey!
testKey({layout:"Greek", keyCode:0, ctrl:1, chars:"\u0001", unmodifiedChars:"\u03b1"},
"\u03b1", true);
testKey({layout:"Greek", keyCode:0, ctrl:1, chars:"\u0001", unmodifiedChars:"\u03b1"},
"\u0391", true);
}
if (navigator.platform.indexOf("Win") == 0) {
// Basic sanity checks
testKey({layout:"US", keyCode:65, chars:"a"},
"a", false);
testKey({layout:"US", keyCode:65, shift:1, alt:1, chars:"A"},
"a", true);
testKey({layout:"US", keyCode:65, shift:1, alt:1, chars:"A"},
"A", true);
// shift-alt-ctrl does not activate accesskeys
testKey({layout:"US", keyCode:65, ctrl:1, shift:1, alt:1, chars:""},
"a", false);
testKey({layout:"US", keyCode:65, ctrl:1, shift:1, alt:1, chars:""},
"A", false);
// Greek layout can activate a Latin accesskey
testKey({layout:"Greek", keyCode:65, shift:1, alt:1, chars:"A"},
"a", true);
testKey({layout:"Greek", keyCode:65, shift:1, alt:1, chars:"A"},
"A", true);
// ... and a Greek accesskey!
testKey({layout:"Greek", keyCode:65, shift:1, alt:1, chars:"A"},
"\u03b1", true);
testKey({layout:"Greek", keyCode:65, shift:1, alt:1, chars:"A"},
"\u0391", true);
}
button.removeEventListener("click", onClick, false);
}
function runTest()
{
runPressTests();
runAccessKeyTests();
SimpleTest.finish();
}
]]>
</script>
</window>