extra measure of security for password textfields on Mac OS X. b=394107 r=smorgan sr=roc
authorjoshmoz@gmail.com
Thu, 27 Sep 2007 09:01:32 -0700
changeset 6348 77def2bf8513f901d00db8fae6790962178ff8a3
parent 6347 46385c4404ed71f7a3e90fb01bc59f5f4e42ac24
child 6349 ba904ebeae0d566f8d4ac40f13748c8f4f51deaf
push idunknown
push userunknown
push dateunknown
reviewerssmorgan, roc
bugs394107
milestone1.9a9pre
extra measure of security for password textfields on Mac OS X. b=394107 r=smorgan sr=roc
layout/forms/nsTextControlFrame.cpp
layout/forms/nsTextControlFrame.h
widget/public/nsIWidget.h
widget/src/cocoa/nsChildView.h
widget/src/cocoa/nsChildView.mm
widget/src/cocoa/nsCocoaWindow.h
widget/src/cocoa/nsCocoaWindow.mm
widget/src/xpwidgets/nsBaseWidget.cpp
widget/src/xpwidgets/nsBaseWidget.h
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -354,31 +354,38 @@ nsTextInputListener::Focus(nsIDOMEvent* 
     return NS_OK;
 
   nsCOMPtr<nsIEditor> editor;
   mFrame->GetEditor(getter_AddRefs(editor));
   if (editor) {
     editor->AddEditorObserver(this);
   }
 
-  return mFrame->InitFocusedValue();
+  nsresult rv = mFrame->InitFocusedValue();
+
+  if (NS_SUCCEEDED(rv))
+    rv = mFrame->MaybeBeginSecureKeyboardInput();
+
+  return rv;
 }
 
 NS_IMETHODIMP
 nsTextInputListener::Blur(nsIDOMEvent* aEvent)
 {
   if (!mFrame)
     return NS_OK;
 
   nsCOMPtr<nsIEditor> editor;
   mFrame->GetEditor(getter_AddRefs(editor));
   if (editor) {
     editor->RemoveEditorObserver(this);
   }
 
+  mFrame->MaybeEndSecureKeyboardInput();
+
   return NS_OK;
 }
 
 // END nsIFocusListener
 
 // BEGIN nsIDOMKeyListener
 
 static void
@@ -1035,16 +1042,17 @@ NS_IMETHODIMP nsTextControlFrame::GetAcc
 
 nsTextControlFrame::nsTextControlFrame(nsIPresShell* aShell, nsStyleContext* aContext)
   : nsStackFrame(aShell, aContext)
   , mUseEditor(PR_FALSE)
   , mIsProcessing(PR_FALSE)
   , mNotifyOnInput(PR_TRUE)
   , mDidPreDestroy(PR_FALSE)
   , mFireChangeEventState(PR_FALSE)
+  , mInSecureKeyboardInputMode(PR_FALSE)
   , mTextListener(nsnull)
 #ifdef DEBUG
   , mCreateFrameForCalled(PR_FALSE)
 #endif
 {
 }
 
 nsTextControlFrame::~nsTextControlFrame()
@@ -1167,16 +1175,19 @@ nsTextControlFrame::PreDestroy()
   }
 
   mDidPreDestroy = PR_TRUE; 
 }
 
 void
 nsTextControlFrame::Destroy()
 {
+  if (mInSecureKeyboardInputMode) {
+    MaybeEndSecureKeyboardInput();
+  }
   if (!mDidPreDestroy) {
     PreDestroy();
   }
   if (mFrameSel) {
     mFrameSel->SetScrollableViewProvider(nsnull);
   }
   nsContentUtils::DestroyAnonymousContent(&mAnonymousDiv);
   nsBoxFrame::Destroy();
@@ -1215,16 +1226,39 @@ PRBool nsTextControlFrame::IsTextArea() 
 
 // XXX: wouldn't it be nice to get this from the style context!
 PRBool nsTextControlFrame::IsPlainTextControl() const
 {
   // need to check HTML attribute of mContent and/or CSS.
   return PR_TRUE;
 }
 
+nsresult nsTextControlFrame::MaybeBeginSecureKeyboardInput()
+{
+  nsresult rv = NS_OK;
+  if (IsPasswordTextControl() && !mInSecureKeyboardInputMode) {
+    nsIWidget* window = GetWindow();
+    NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+    rv = window->BeginSecureKeyboardInput();
+    mInSecureKeyboardInputMode = NS_SUCCEEDED(rv);
+  }
+  return rv;
+}
+
+void nsTextControlFrame::MaybeEndSecureKeyboardInput()
+{
+  if (mInSecureKeyboardInputMode) {
+    nsIWidget* window = GetWindow();
+    if (!window)
+      return;
+    window->EndSecureKeyboardInput();
+    mInSecureKeyboardInputMode = PR_FALSE;
+  }
+}
+
 PRBool nsTextControlFrame::IsPasswordTextControl() const
 {
   nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mContent);
   return formControl && formControl->GetType() == NS_FORM_INPUT_PASSWORD;
 }
 
 
 PRInt32
--- a/layout/forms/nsTextControlFrame.h
+++ b/layout/forms/nsTextControlFrame.h
@@ -208,17 +208,21 @@ public: //for methods who access nsTextC
 
   PRBool GetFireChangeEventState() const
   {
     return mFireChangeEventState;
   }    
 
   /* called to free up native keybinding services */
   static NS_HIDDEN_(void) ShutDown();
-  
+
+  // called by the focus listener
+  nsresult MaybeBeginSecureKeyboardInput();
+  void MaybeEndSecureKeyboardInput();
+
 protected:
   /**
    * Find out whether this control is scrollable (i.e. if it is not a single
    * line text control)
    * @return whether this control is scrollable
    */
   PRBool IsScrollable() const;
   /**
@@ -291,16 +295,17 @@ private:
   // these packed bools could instead use the high order bits on mState, saving 4 bytes 
   PRPackedBool mUseEditor;
   PRPackedBool mIsProcessing;
   PRPackedBool mNotifyOnInput;//default this to off to stop any notifications until setup is complete
   PRPackedBool mDidPreDestroy; // has PreDestroy been called
   // Calls to SetValue will be treated as user values (i.e. trigger onChange
   // eventually) when mFireChangeEventState==true, this is used by nsFileControlFrame.
   PRPackedBool mFireChangeEventState;
+  PRPackedBool mInSecureKeyboardInputMode;
 
   nsCOMPtr<nsISelectionController> mSelCon;
   nsCOMPtr<nsFrameSelection> mFrameSel;
   nsTextInputListener* mTextListener;
   nsString mFocusedValue;
 
 #ifdef DEBUG
   PRBool mCreateFrameForCalled;
--- a/widget/public/nsIWidget.h
+++ b/widget/public/nsIWidget.h
@@ -90,21 +90,21 @@ typedef nsEventStatus (*PR_CALLBACK EVEN
 #define NS_NATIVE_PLUGIN_PORT 8
 #define NS_NATIVE_SCREEN      9
 #define NS_NATIVE_SHELLWIDGET 10      // Get the shell GtkWidget
 #ifdef XP_MACOSX
 #define NS_NATIVE_PLUGIN_PORT_QD    100
 #define NS_NATIVE_PLUGIN_PORT_CG    101
 #endif
 
-// f60fa720-a9bc-4fd3-b863-812496fa85e6
+// 15800FBD-650A-4F67-81FB-186E73F45BE1
 
 #define NS_IWIDGET_IID \
-{ 0xf60fa720, 0xa9bc, 0x4fd3, \
-  { 0xb8, 0x63, 0x81, 0x24, 0x96, 0xfa, 0x85, 0xe6 } }
+{ 0x15800FBD, 0x650A, 0x4F67, \
+  { 0x81, 0xFB, 0x18, 0x6E, 0x73, 0xF4, 0x5B, 0xE1 } }
 
 // Hide the native window systems real window type so as to avoid
 // including native window system types and api's. This is necessary
 // to ensure cross-platform code.
 typedef void* nsNativeWidget;
 
 /**
  * Border styles
@@ -1008,16 +1008,34 @@ class nsIWidget : public nsISupports {
      * @param aTime Last user input time in milliseconds. This value can be used to compare
      * durations but can not be used for determining wall clock time. The value returned 
      * is platform dependent, but is compatible with the expression 
      * PR_IntervalToMicroseconds(PR_IntervalNow()).
      */
     NS_IMETHOD GetLastInputEventTime(PRUint32& aTime) = 0;
 
     /**
+     * Called when when we need to begin secure keyboard input, such as when a password field
+     * gets focus.
+     *
+     * NOTE: Calls to this method may not be nested and you can only enable secure keyboard input
+     * for one widget at a time.
+     */
+    NS_IMETHOD BeginSecureKeyboardInput() = 0;
+
+    /**
+     * Called when when we need to end secure keyboard input, such as when a password field
+     * loses focus.
+     *
+     * NOTE: Calls to this method may not be nested and you can only enable secure keyboard input
+     * for one widget at a time.
+     */
+    NS_IMETHOD EndSecureKeyboardInput() = 0;
+
+    /**
      * Get the Thebes surface associated with this widget.
      */
     virtual gfxASurface *GetThebesSurface() = 0;
 
 protected:
     // keep the list of children.  We also keep track of our siblings.
     // The ownership model is as follows: parent holds a strong ref to
     // the first element of the list, and each element holds a strong
--- a/widget/src/cocoa/nsChildView.h
+++ b/widget/src/cocoa/nsChildView.h
@@ -307,16 +307,19 @@ public:
   void              LiveResizeEnded();
   
 #ifdef ACCESSIBILITY
   void              GetDocumentAccessible(nsIAccessible** aAccessible);
 #endif
 
   virtual gfxASurface* GetThebesSurface();
 
+  NS_IMETHOD BeginSecureKeyboardInput();
+  NS_IMETHOD EndSecureKeyboardInput();
+
 protected:
 
   PRBool            ReportDestroyEvent();
   PRBool            ReportMoveEvent();
   PRBool            ReportSizeEvent();
 
   NS_IMETHOD        CalcOffset(PRInt32 &aX,PRInt32 &aY);
 
--- a/widget/src/cocoa/nsChildView.mm
+++ b/widget/src/cocoa/nsChildView.mm
@@ -1739,16 +1739,36 @@ nsChildView::GetThebesSurface()
   if (!mTempThebesSurface) {
     mTempThebesSurface = new gfxQuartzSurface(gfxSize(1, 1), gfxASurface::ImageFormatARGB32);
   }
 
   return mTempThebesSurface;
 }
 
 
+NS_IMETHODIMP
+nsChildView::BeginSecureKeyboardInput()
+{
+  nsresult rv = nsBaseWidget::BeginSecureKeyboardInput();
+  if (NS_SUCCEEDED(rv))
+    ::EnableSecureEventInput();
+  return rv;
+}
+
+
+NS_IMETHODIMP
+nsChildView::EndSecureKeyboardInput()
+{
+  nsresult rv = nsBaseWidget::EndSecureKeyboardInput();
+  if (NS_SUCCEEDED(rv))
+    ::DisableSecureEventInput();
+  return rv;
+}
+
+
 #ifdef ACCESSIBILITY
 void
 nsChildView::GetDocumentAccessible(nsIAccessible** aAccessible)
 {
   *aAccessible = nsnull;
   
   nsCOMPtr<nsIAccessible> accessible = do_QueryReferent(mAccessible);
   if (!mAccessible) {
--- a/widget/src/cocoa/nsCocoaWindow.h
+++ b/widget/src/cocoa/nsCocoaWindow.h
@@ -198,16 +198,19 @@ public:
     // Helpers to prevent recursive resizing during live-resize
     PRBool IsResizing () const { return mIsResizing; }
     void StartResizing () { mIsResizing = PR_TRUE; }
     void StopResizing () { mIsResizing = PR_FALSE; }
     
     // nsIKBStateControl interface
     NS_IMETHOD ResetInputState();
 
+    NS_IMETHOD BeginSecureKeyboardInput();
+    NS_IMETHOD EndSecureKeyboardInput();
+
 protected:
   
   nsIWidget*           mParent;         // if we're a popup, this is our parent [WEAK]
   NSWindow*            mWindow;         // our cocoa window [STRONG]
   WindowDelegate*      mDelegate;       // our delegate for processing window msgs [STRONG]
   nsCOMPtr<nsIMenuBar> 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
--- a/widget/src/cocoa/nsCocoaWindow.mm
+++ b/widget/src/cocoa/nsCocoaWindow.mm
@@ -1052,16 +1052,34 @@ NS_IMETHODIMP nsCocoaWindow::GetAttentio
 gfxASurface* nsCocoaWindow::GetThebesSurface()
 {
   if (mPopupContentView)
     return mPopupContentView->GetThebesSurface();
   return nsnull;
 }
 
 
+NS_IMETHODIMP nsCocoaWindow::BeginSecureKeyboardInput()
+{
+  nsresult rv = nsBaseWidget::BeginSecureKeyboardInput();
+  if (NS_SUCCEEDED(rv))
+    ::EnableSecureEventInput();
+  return rv;
+}
+
+
+NS_IMETHODIMP nsCocoaWindow::EndSecureKeyboardInput()
+{
+  nsresult rv = nsBaseWidget::EndSecureKeyboardInput();
+  if (NS_SUCCEEDED(rv))
+    ::DisableSecureEventInput();
+  return rv;
+}
+
+
 @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
 {  
--- a/widget/src/xpwidgets/nsBaseWidget.cpp
+++ b/widget/src/xpwidgets/nsBaseWidget.cpp
@@ -51,16 +51,17 @@
 #ifdef DEBUG
 #include "nsIServiceManager.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch2.h"
 #include "nsIObserver.h"
 
 static void debug_RegisterPrefCallbacks();
 
+static PRBool debug_InSecureKeyboardInputMode = PR_FALSE;
 #endif
 
 #ifdef NOISY_WIDGET_LEAKS
 static PRInt32 gNumWidgets;
 #endif
 
 // nsBaseWidget
 NS_IMPL_ISUPPORTS1(nsBaseWidget, nsIWidget)
@@ -832,16 +833,36 @@ nsBaseWidget::GetLastInputEventTime(PRUi
 }
 
 NS_IMETHODIMP
 nsBaseWidget::SetIcon(const nsAString&)
 {
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsBaseWidget::BeginSecureKeyboardInput()
+{
+#ifdef DEBUG
+  NS_ASSERTION(!debug_InSecureKeyboardInputMode, "Attempting to nest call to BeginSecureKeyboardInput!");
+  debug_InSecureKeyboardInputMode = PR_TRUE;
+#endif
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseWidget::EndSecureKeyboardInput()
+{
+#ifdef DEBUG
+  NS_ASSERTION(debug_InSecureKeyboardInputMode, "Calling EndSecureKeyboardInput when it hasn't been enabled!");
+  debug_InSecureKeyboardInputMode = PR_FALSE;
+#endif
+  return NS_OK;
+}
+
 /**
  * Modifies aFile to point at an icon file with the given name and suffix.  The
  * suffix may correspond to a file extension with leading '.' if appropriate.
  * Returns true if the icon file exists and can be read.
  */
 static PRBool
 ResolveIconNameHelper(nsILocalFile *aFile,
                       const nsAString &aIconName,
--- a/widget/src/xpwidgets/nsBaseWidget.h
+++ b/widget/src/xpwidgets/nsBaseWidget.h
@@ -126,18 +126,20 @@ public:
   NS_IMETHOD              GetScreenBounds(nsRect &aRect);
   NS_IMETHOD              GetBorderSize(PRInt32 &aWidth, PRInt32 &aHeight);
   NS_IMETHOD              ScrollRect(nsRect &aRect, PRInt32 aDx, PRInt32 aDy);
   NS_IMETHOD              ScrollWidgets(PRInt32 aDx, PRInt32 aDy);
   NS_IMETHOD              EnableDragDrop(PRBool aEnable);
   NS_IMETHOD              GetAttention(PRInt32 aCycleCount);
   NS_IMETHOD              GetLastInputEventTime(PRUint32& aTime);
   NS_IMETHOD              SetIcon(const nsAString &anIconSpec);
+  NS_IMETHOD              BeginSecureKeyboardInput();
+  NS_IMETHOD              EndSecureKeyboardInput();
   virtual void            ConvertToDeviceCoordinates(nscoord  &aX,nscoord &aY) {}
-  virtual void            FreeNativeData(void * data, PRUint32 aDataType) {}//~~~
+  virtual void            FreeNativeData(void * data, PRUint32 aDataType) {}
 
 protected:
 
   virtual void            ResolveIconName(const nsAString &aIconName,
                                           const nsAString &aIconSuffix,
                                           nsILocalFile **aResult);
   virtual void            OnDestroy();
   virtual void            BaseCreate(nsIWidget *aParent,