Bug 98160 - Add support for platform native text switching keyboard shortcuts in bidi UI on Windows; r=roc
authorEhsan Akhgari <ehsan@mozilla.com>
Thu, 15 Sep 2011 10:54:50 -0400
changeset 77014 66db4ae2f2c7
parent 77013 cffa5e5bb0f6
child 77015 2c064df91e2f
push id21169
push usereakhgari@mozilla.com
push dateFri, 16 Sep 2011 13:32:09 +0000
treeherdermozilla-central@88b093bae951 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs98160
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 98160 - Add support for platform native text switching keyboard shortcuts in bidi UI on Windows; r=roc
editor/libeditor/base/nsEditor.cpp
editor/libeditor/base/nsEditor.h
editor/libeditor/base/nsEditorEventListener.cpp
editor/libeditor/base/nsEditorEventListener.h
widget/public/nsIBidiKeyboard.idl
widget/src/cocoa/nsBidiKeyboard.mm
widget/src/gtk2/nsBidiKeyboard.cpp
widget/src/os2/nsBidiKeyboard.cpp
widget/src/qt/nsBidiKeyboard.cpp
widget/src/windows/nsBidiKeyboard.cpp
--- a/editor/libeditor/base/nsEditor.cpp
+++ b/editor/libeditor/base/nsEditor.cpp
@@ -5194,18 +5194,18 @@ nsEditor::GetRoot()
 
     // Let GetRootElement() do the work
     GetRootElement(getter_AddRefs(root));
   }
 
   return mRootElement;
 }
 
-NS_IMETHODIMP
-nsEditor::SwitchTextDirection()
+nsresult
+nsEditor::DetermineCurrentDirection()
 {
   // Get the current root direction from its frame
   nsIDOMElement *rootElement = GetRoot();
 
   nsresult rv;
 
   // If we don't have an explicit direction, determine our direction
   // from the content's direction
@@ -5221,16 +5221,28 @@ nsEditor::SwitchTextDirection()
     // It will be flipped before returning from the function.
     if (frame->GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
       mFlags |= nsIPlaintextEditor::eEditorRightToLeft;
     } else {
       mFlags |= nsIPlaintextEditor::eEditorLeftToRight;
     }
   }
 
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEditor::SwitchTextDirection()
+{
+  // Get the current root direction from its frame
+  nsIDOMElement *rootElement = GetRoot();
+
+  nsresult rv = DetermineCurrentDirection();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // Apply the opposite direction
   if (mFlags & nsIPlaintextEditor::eEditorRightToLeft) {
     NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorLeftToRight),
                  "Unexpected mutually exclusive flag");
     mFlags &= ~nsIPlaintextEditor::eEditorRightToLeft;
     mFlags |= nsIPlaintextEditor::eEditorLeftToRight;
     rv = rootElement->SetAttribute(NS_LITERAL_STRING("dir"), NS_LITERAL_STRING("ltr"));
   } else if (mFlags & nsIPlaintextEditor::eEditorLeftToRight) {
@@ -5239,16 +5251,43 @@ nsEditor::SwitchTextDirection()
     mFlags |= nsIPlaintextEditor::eEditorRightToLeft;
     mFlags &= ~nsIPlaintextEditor::eEditorLeftToRight;
     rv = rootElement->SetAttribute(NS_LITERAL_STRING("dir"), NS_LITERAL_STRING("rtl"));
   }
 
   return rv;
 }
 
+void
+nsEditor::SwitchTextDirectionTo(PRUint32 aDirection)
+{
+  // Get the current root direction from its frame
+  nsIDOMElement *rootElement = GetRoot();
+
+  nsresult rv = DetermineCurrentDirection();
+  NS_ENSURE_SUCCESS(rv, );
+
+  // Apply the requested direction
+  if (aDirection == nsIPlaintextEditor::eEditorLeftToRight &&
+      (mFlags & nsIPlaintextEditor::eEditorRightToLeft)) {
+    NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorLeftToRight),
+                 "Unexpected mutually exclusive flag");
+    mFlags &= ~nsIPlaintextEditor::eEditorRightToLeft;
+    mFlags |= nsIPlaintextEditor::eEditorLeftToRight;
+    rootElement->SetAttribute(NS_LITERAL_STRING("dir"), NS_LITERAL_STRING("ltr"));
+  } else if (aDirection == nsIPlaintextEditor::eEditorRightToLeft &&
+             (mFlags & nsIPlaintextEditor::eEditorLeftToRight)) {
+    NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorRightToLeft),
+                 "Unexpected mutually exclusive flag");
+    mFlags |= nsIPlaintextEditor::eEditorRightToLeft;
+    mFlags &= ~nsIPlaintextEditor::eEditorLeftToRight;
+    rootElement->SetAttribute(NS_LITERAL_STRING("dir"), NS_LITERAL_STRING("rtl"));
+  }
+}
+
 #if DEBUG_JOE
 void
 nsEditor::DumpNode(nsIDOMNode *aNode, PRInt32 indent)
 {
   PRInt32 i;
   for (i=0; i<indent; i++)
     printf("  ");
   
--- a/editor/libeditor/base/nsEditor.h
+++ b/editor/libeditor/base/nsEditor.h
@@ -200,16 +200,18 @@ public:
   nsresult CreateHTMLContent(const nsAString& aTag, nsIContent** aContent);
 
   // IME event handlers
   virtual nsresult BeginIMEComposition();
   virtual nsresult UpdateIMEComposition(const nsAString &aCompositionString,
                                         nsIPrivateTextRangeList *aTextRange)=0;
   nsresult EndIMEComposition();
 
+  void SwitchTextDirectionTo(PRUint32 aDirection);
+
   void BeginKeypressHandling() { mLastKeypressEventWasTrusted = eTriTrue; }
   void BeginKeypressHandling(nsIDOMNSEvent* aEvent);
   void EndKeypressHandling() { mLastKeypressEventWasTrusted = eTriUnset; }
 
   class FireTrustedInputEvent {
   public:
     explicit FireTrustedInputEvent(nsEditor* aSelf, PRBool aActive = PR_TRUE)
       : mEditor(aSelf)
@@ -226,16 +228,18 @@ public:
   private:
     nsEditor* mEditor;
     PRBool mShouldAct;
   };
 
 protected:
   nsCString mContentMIMEType;       // MIME type of the doc we are editing.
 
+  nsresult DetermineCurrentDirection();
+
   /** create a transaction for setting aAttribute to aValue on aElement
     */
   NS_IMETHOD CreateTxnForSetAttribute(nsIDOMElement *aElement, 
                                       const nsAString &  aAttribute, 
                                       const nsAString &  aValue,
                                       ChangeAttributeTxn ** aTxn);
 
   /** create a transaction for removing aAttribute on aElement
--- a/editor/libeditor/base/nsEditorEventListener.cpp
+++ b/editor/libeditor/base/nsEditorEventListener.cpp
@@ -66,16 +66,17 @@
 #include "nsISupportsPrimitives.h"
 #include "nsIDOMNSRange.h"
 #include "nsEditorUtils.h"
 #include "nsISelectionPrivate.h"
 #include "nsIDOMDragEvent.h"
 #include "nsIFocusManager.h"
 #include "nsIDOMWindow.h"
 #include "nsContentUtils.h"
+#include "nsIBidiKeyboard.h"
 
 using namespace mozilla;
 
 class nsAutoEditorKeypressOperation {
 public:
   nsAutoEditorKeypressOperation(nsEditor *aEditor, nsIDOMNSEvent *aEvent)
     : mEditor(aEditor) {
     mEditor->BeginKeypressHandling(aEvent);
@@ -86,32 +87,46 @@ public:
 
 private:
   nsEditor *mEditor;
 };
 
 nsEditorEventListener::nsEditorEventListener() :
   mEditor(nsnull), mCommitText(PR_FALSE),
   mInTransaction(PR_FALSE)
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+  , mHaveBidiKeyboards(PR_FALSE)
+  , mShouldSwitchTextDirection(PR_FALSE)
+  , mSwitchToRTL(PR_FALSE)
+#endif
 {
 }
 
 nsEditorEventListener::~nsEditorEventListener() 
 {
   if (mEditor) {
     NS_WARNING("We're not uninstalled");
     Disconnect();
   }
 }
 
 nsresult
 nsEditorEventListener::Connect(nsEditor* aEditor)
 {
   NS_ENSURE_ARG(aEditor);
 
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+  nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+  if (bidiKeyboard) {
+    PRBool haveBidiKeyboards = PR_FALSE;
+    bidiKeyboard->GetHaveBidiKeyboards(&haveBidiKeyboards);
+    mHaveBidiKeyboards = haveBidiKeyboards;
+  }
+#endif
+
   mEditor = aEditor;
 
   nsresult rv = InstallToEditor();
   if (NS_FAILED(rv)) {
     Disconnect();
   }
   return rv;
 }
@@ -123,16 +138,26 @@ nsEditorEventListener::InstallToEditor()
 
   nsCOMPtr<nsIDOMEventTarget> piTarget = mEditor->GetDOMEventTarget();
   NS_ENSURE_TRUE(piTarget, NS_ERROR_FAILURE);
 
   // register the event listeners with the listener manager
   nsEventListenerManager* elmP = piTarget->GetListenerManager(PR_TRUE);
   NS_ENSURE_STATE(elmP);
 
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+  elmP->AddEventListenerByType(this,
+                               NS_LITERAL_STRING("keydown"),
+                               NS_EVENT_FLAG_BUBBLE |
+                               NS_EVENT_FLAG_SYSTEM_EVENT);
+  elmP->AddEventListenerByType(this,
+                               NS_LITERAL_STRING("keyup"),
+                               NS_EVENT_FLAG_BUBBLE |
+                               NS_EVENT_FLAG_SYSTEM_EVENT);
+#endif
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("keypress"),
                                NS_EVENT_FLAG_BUBBLE |
                                NS_PRIV_EVENT_UNTRUSTED_PERMITTED |
                                NS_EVENT_FLAG_SYSTEM_EVENT);
   // See bug 455215, we cannot use the standard dragstart event yet
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("draggesture"),
@@ -203,16 +228,26 @@ nsEditorEventListener::UninstallFromEdit
   }
 
   nsEventListenerManager* elmP =
     piTarget->GetListenerManager(PR_TRUE);
   if (!elmP) {
     return;
   }
 
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+  elmP->RemoveEventListenerByType(this,
+                                  NS_LITERAL_STRING("keydown"),
+                                  NS_EVENT_FLAG_BUBBLE |
+                                  NS_EVENT_FLAG_SYSTEM_EVENT);
+  elmP->RemoveEventListenerByType(this,
+                                  NS_LITERAL_STRING("keyup"),
+                                  NS_EVENT_FLAG_BUBBLE |
+                                  NS_EVENT_FLAG_SYSTEM_EVENT);
+#endif
   elmP->RemoveEventListenerByType(this,
                                   NS_LITERAL_STRING("keypress"),
                                   NS_EVENT_FLAG_BUBBLE |
                                   NS_EVENT_FLAG_SYSTEM_EVENT);
   elmP->RemoveEventListenerByType(this,
                                   NS_LITERAL_STRING("draggesture"),
                                   NS_EVENT_FLAG_BUBBLE |
                                   NS_EVENT_FLAG_SYSTEM_EVENT);
@@ -293,16 +328,22 @@ nsEditorEventListener::HandleEvent(nsIDO
     if (eventType.EqualsLiteral("dragover"))
       return DragOver(dragEvent);
     if (eventType.EqualsLiteral("dragexit"))
       return DragExit(dragEvent);
     if (eventType.EqualsLiteral("drop"))
       return Drop(dragEvent);
   }
 
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+  if (eventType.EqualsLiteral("keydown"))
+    return KeyDown(aEvent);
+  if (eventType.EqualsLiteral("keyup"))
+    return KeyUp(aEvent);
+#endif
   if (eventType.EqualsLiteral("keypress"))
     return KeyPress(aEvent);
   if (eventType.EqualsLiteral("mousedown"))
     return MouseDown(aEvent);
   if (eventType.EqualsLiteral("mouseup"))
     return MouseUp(aEvent);
   if (eventType.EqualsLiteral("click"))
     return MouseClick(aEvent);
@@ -315,16 +356,127 @@ nsEditorEventListener::HandleEvent(nsIDO
   if (eventType.EqualsLiteral("compositionstart"))
     return HandleStartComposition(aEvent);
   if (eventType.EqualsLiteral("compositionend"))
     return HandleEndComposition(aEvent);
 
   return NS_OK;
 }
 
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+#include <windows.h>
+
+namespace {
+
+// This function is borrowed from Chromium's ImeInput::IsCtrlShiftPressed
+bool IsCtrlShiftPressed(bool& isRTL)
+{
+  BYTE keystate[256];
+  if (!::GetKeyboardState(keystate)) {
+    return false;
+  }
+
+  // To check if a user is pressing only a control key and a right-shift key
+  // (or a left-shift key), we use the steps below:
+  // 1. Check if a user is pressing a control key and a right-shift key (or
+  //    a left-shift key).
+  // 2. If the condition 1 is true, we should check if there are any other
+  //    keys pressed at the same time.
+  //    To ignore the keys checked in 1, we set their status to 0 before
+  //    checking the key status.
+  const int kKeyDownMask = 0x80;
+  if ((keystate[VK_CONTROL] & kKeyDownMask) == 0)
+    return false;
+
+  if (keystate[VK_RSHIFT] & kKeyDownMask) {
+    keystate[VK_RSHIFT] = 0;
+    isRTL = true;
+  } else if (keystate[VK_LSHIFT] & kKeyDownMask) {
+    keystate[VK_LSHIFT] = 0;
+    isRTL = false;
+  } else {
+    return false;
+  }
+
+  // Scan the key status to find pressed keys. We should abandon changing the
+  // text direction when there are other pressed keys.
+  // This code is executed only when a user is pressing a control key and a
+  // right-shift key (or a left-shift key), i.e. we should ignore the status of
+  // the keys: VK_SHIFT, VK_CONTROL, VK_RCONTROL, and VK_LCONTROL.
+  // So, we reset their status to 0 and ignore them.
+  keystate[VK_SHIFT] = 0;
+  keystate[VK_CONTROL] = 0;
+  keystate[VK_RCONTROL] = 0;
+  keystate[VK_LCONTROL] = 0;
+  for (int i = 0; i <= VK_PACKET; ++i) {
+    if (keystate[i] & kKeyDownMask)
+      return false;
+  }
+  return true;
+}
+
+}
+
+// This logic is mostly borrowed from Chromium's
+// RenderWidgetHostViewWin::OnKeyEvent.
+
+NS_IMETHODIMP
+nsEditorEventListener::KeyUp(nsIDOMEvent* aKeyEvent)
+{
+  if (mHaveBidiKeyboards) {
+    nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
+    if (!keyEvent) {
+      // non-key event passed to keyup.  bad things.
+      return NS_OK;
+    }
+
+    PRUint32 keyCode = 0;
+    keyEvent->GetKeyCode(&keyCode);
+    if (keyCode == nsIDOMKeyEvent::DOM_VK_SHIFT ||
+        keyCode == nsIDOMKeyEvent::DOM_VK_CONTROL) {
+      if (mShouldSwitchTextDirection && mEditor->IsPlaintextEditor()) {
+        mEditor->SwitchTextDirectionTo(mSwitchToRTL ?
+          nsIPlaintextEditor::eEditorRightToLeft :
+          nsIPlaintextEditor::eEditorLeftToRight);
+        mShouldSwitchTextDirection = PR_FALSE;
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEditorEventListener::KeyDown(nsIDOMEvent* aKeyEvent)
+{
+  if (mHaveBidiKeyboards) {
+    nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
+    if (!keyEvent) {
+      // non-key event passed to keydown.  bad things.
+      return NS_OK;
+    }
+
+    PRUint32 keyCode = 0;
+    keyEvent->GetKeyCode(&keyCode);
+    if (keyCode == nsIDOMKeyEvent::DOM_VK_SHIFT) {
+      bool switchToRTL;
+      if (IsCtrlShiftPressed(switchToRTL)) {
+        mShouldSwitchTextDirection = PR_TRUE;
+        mSwitchToRTL = switchToRTL;
+      }
+    } else if (keyCode != nsIDOMKeyEvent::DOM_VK_CONTROL) {
+      // In case the user presses any other key besides Ctrl and Shift
+      mShouldSwitchTextDirection = PR_FALSE;
+    }
+  }
+
+  return NS_OK;
+}
+#endif
+
 NS_IMETHODIMP
 nsEditorEventListener::KeyPress(nsIDOMEvent* aKeyEvent)
 {
   NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
 
   if (!mEditor->IsAcceptableInputEvent(aKeyEvent)) {
     return NS_OK;
   }
--- a/editor/libeditor/base/nsEditorEventListener.h
+++ b/editor/libeditor/base/nsEditorEventListener.h
@@ -46,32 +46,41 @@
 
 #include "nsCaret.h"
 
 // X.h defines KeyPress
 #ifdef KeyPress
 #undef KeyPress
 #endif
 
+#ifdef XP_WIN
+// On Windows, we support switching the text direction by pressing Ctrl+Shift
+#define HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+#endif
+
 class nsEditor;
 class nsIDOMDragEvent;
 
 class nsEditorEventListener : public nsIDOMEventListener
 {
 public:
   nsEditorEventListener();
   virtual ~nsEditorEventListener();
 
   virtual nsresult Connect(nsEditor* aEditor);
 
   void Disconnect();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMEVENTLISTENER
 
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+  NS_IMETHOD KeyDown(nsIDOMEvent* aKeyEvent);
+  NS_IMETHOD KeyUp(nsIDOMEvent* aKeyEvent);
+#endif
   NS_IMETHOD KeyPress(nsIDOMEvent* aKeyEvent);
   NS_IMETHOD HandleText(nsIDOMEvent* aTextEvent);
   NS_IMETHOD HandleStartComposition(nsIDOMEvent* aCompositionEvent);
   NS_IMETHOD HandleEndComposition(nsIDOMEvent* aCompositionEvent);
   NS_IMETHOD MouseDown(nsIDOMEvent* aMouseEvent);
   NS_IMETHOD MouseUp(nsIDOMEvent* aMouseEvent) { return NS_OK; }
   NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent);
   NS_IMETHOD Focus(nsIDOMEvent* aEvent);
@@ -90,11 +99,16 @@ protected:
   void CleanupDragDropCaret();
   already_AddRefed<nsIPresShell> GetPresShell();
 
 protected:
   nsEditor* mEditor; // weak
   nsRefPtr<nsCaret> mCaret;
   PRPackedBool mCommitText;
   PRPackedBool mInTransaction;
+#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
+  PRPackedBool mHaveBidiKeyboards;
+  PRPackedBool mShouldSwitchTextDirection;
+  PRPackedBool mSwitchToRTL;
+#endif
 };
 
 #endif // nsEditorEventListener_h__
--- a/widget/public/nsIBidiKeyboard.idl
+++ b/widget/public/nsIBidiKeyboard.idl
@@ -34,25 +34,34 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(0D8F8F10-C92D-4A6A-B2BB-E1921F3F4DDA)]
+[scriptable, uuid(99957506-f21b-4a61-ad64-5b641cf508e2)]
 interface nsIBidiKeyboard : nsISupports
 {
   /**
    * Determines if the current keyboard language is right-to-left
    * @throws NS_ERROR_FAILURE if no right-to-left keyboards are installed
    */
   boolean isLangRTL();
 
   /**
    * Sets the keyboard language to left-to-right or right-to-left
    * @param aLevel - if odd set the keyboard to RTL, if even set LTR 
    * @throws NS_ERROR_FAILURE if no right-to-left keyboards are installed
    */
   void setLangFromBidiLevel(in PRUint8 aLevel);
+
+  /**
+   * Determines whether the system has at least one keyboard of each direction
+   * installed.
+   *
+   * @throws NS_ERROR_NOT_IMPLEMENTED if the widget layer does not provide this
+   * information.
+   */
+  readonly attribute boolean haveBidiKeyboards;
 };
 
--- a/widget/src/cocoa/nsBidiKeyboard.mm
+++ b/widget/src/cocoa/nsBidiKeyboard.mm
@@ -61,8 +61,14 @@ NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(
   return NS_OK;
 }
 
 NS_IMETHODIMP nsBidiKeyboard::SetLangFromBidiLevel(PRUint8 aLevel)
 {
   // XXX Insert platform specific code to set keyboard language
   return NS_OK;
 }
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(PRBool* aResult)
+{
+  // not implemented yet
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
--- a/widget/src/gtk2/nsBidiKeyboard.cpp
+++ b/widget/src/gtk2/nsBidiKeyboard.cpp
@@ -105,8 +105,13 @@ nsBidiKeyboard::SetHaveBidiKeyboards()
 
 NS_IMETHODIMP
 nsBidiKeyboard::SetLangFromBidiLevel(PRUint8 aLevel)
 {
     // XXX Insert platform specific code to set keyboard language
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(PRBool* aResult)
+{
+  // not implemented yet
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
--- a/widget/src/os2/nsBidiKeyboard.cpp
+++ b/widget/src/os2/nsBidiKeyboard.cpp
@@ -56,8 +56,14 @@ NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP nsBidiKeyboard::SetLangFromBidiLevel(PRUint8 aLevel)
 {
   // XXX Insert platform specific code to set keyboard language
   return NS_ERROR_NOT_IMPLEMENTED;
 }
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(PRBool* aResult)
+{
+  // not implemented yet
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
--- a/widget/src/qt/nsBidiKeyboard.cpp
+++ b/widget/src/qt/nsBidiKeyboard.cpp
@@ -63,8 +63,14 @@ NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(
     
     return NS_OK;
 }
 
 NS_IMETHODIMP nsBidiKeyboard::SetLangFromBidiLevel(PRUint8 aLevel)
 {
     return NS_OK;
 }
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(PRBool* aResult)
+{
+  // not implemented yet
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
--- a/widget/src/windows/nsBidiKeyboard.cpp
+++ b/widget/src/windows/nsBidiKeyboard.cpp
@@ -116,16 +116,28 @@ NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(
 
   NS_ASSERTION((wcslen(mRTLKeyboard) < KL_NAMELENGTH), 
     "mLTRKeyboard has string length >= KL_NAMELENGTH");
   NS_ASSERTION((wcslen(mLTRKeyboard) < KL_NAMELENGTH), 
     "mRTLKeyboard has string length >= KL_NAMELENGTH");
   return NS_OK;
 }
 
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(PRBool* aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+
+  nsresult result = SetupBidiKeyboards();
+  if (NS_FAILED(result))
+    return result;
+
+  *aResult = mHaveBidiKeyboards;
+  return NS_OK;
+}
+
 
 // Get the list of keyboard layouts available in the system
 // Set mLTRKeyboard to the first LTR keyboard in the list and mRTLKeyboard to the first RTL keyboard in the list
 // These defaults will be used unless the user explicitly sets something else.
 nsresult nsBidiKeyboard::SetupBidiKeyboards()
 {
   if (mInitialized)
     return mHaveBidiKeyboards ? NS_OK : NS_ERROR_FAILURE;