CL implementing focus-dismissal of the chrome.experimental.popup set of extension APIs.  

Specifically, these changes cause a displayed pop-up to be dismissed when the focus shifts away from both the pop-up view, and the extension-view that launched the pop-up.I had to do a lot of investigating and trial-and-error to converge to the solution present here.  I was hoping to be able to piggy-back on the existing FocusManager's various listener routines, but because the pop-up is hosted in a BubbleWidget, which is a separate top-level window with its own focus manager, I could not rely on a given focus manager to take care of the focus change notifications. To get around the above issue, I added a new type of focus listener that can listen on native-window focus change events.  I added invocations to this listener throughout the Widget classes in the system so that registered listeners will be notified on focus change.  

I found some of the focus change events problematic, as the system will arbitrarily reassign the focus to the main browser window when shifting activation between chrome windows.  (SeefocusManagerWin::ClearNativeFocus).  To prevent this focus bounce from confusing focus listeners, I added a means to suppress notification of focus change during these operations.

I added GTK and Mac stubs for the new widget functions that will assert when called.  GTK and Cocoa development is not my expertise, so I thought // TODO(port) would be wiser.I'm uncertain of the best means to unit-test these changes.  Direction in this regard would be appreciated.

BUG=None
TEST=None

Review URL: http://codereview.chromium.org/552167

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38685 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_dom_ui.cc b/chrome/browser/extensions/extension_dom_ui.cc
index e9f1996..3428869 100644
--- a/chrome/browser/extensions/extension_dom_ui.cc
+++ b/chrome/browser/extensions/extension_dom_ui.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/browser_list.h"
 #include "chrome/browser/extensions/extensions_service.h"
 #include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
 #include "chrome/browser/tab_contents/tab_contents.h"
 #include "chrome/common/bindings_policy.h"
 #include "chrome/common/pref_service.h"
@@ -90,6 +91,10 @@
   return native_window;
 }
 
+gfx::NativeView ExtensionDOMUI::GetNativeViewOfHost() {
+  return tab_contents()->GetRenderWidgetHostView()->GetNativeView();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // chrome:// URL overrides
 
diff --git a/chrome/browser/extensions/extension_dom_ui.h b/chrome/browser/extensions/extension_dom_ui.h
index bd7a0ac8..c7d2b97 100644
--- a/chrome/browser/extensions/extension_dom_ui.h
+++ b/chrome/browser/extensions/extension_dom_ui.h
@@ -47,6 +47,7 @@
   // ExtensionPopupHost::Delegate
   virtual RenderViewHost* GetRenderViewHost();
   virtual Profile* GetProfile();
+  virtual gfx::NativeView GetNativeViewOfHost();
 
   // BrowserURLHandler
   static bool HandleChromeURLOverride(GURL* url, Profile* profile);
diff --git a/chrome/browser/extensions/extension_host.h b/chrome/browser/extensions/extension_host.h
index 4087338f..2bd79061 100644
--- a/chrome/browser/extensions/extension_host.h
+++ b/chrome/browser/extensions/extension_host.h
@@ -195,6 +195,9 @@
 
   // ExtensionPopupHost::Delegate
   virtual RenderViewHost* GetRenderViewHost() { return render_view_host(); }
+  virtual gfx::NativeView GetNativeViewOfHost() {
+    return view()->native_view();
+  }
 
   // Returns true if we're hosting a background page.
   // This isn't valid until CreateRenderView is called.
diff --git a/chrome/browser/extensions/extension_popup_host.cc b/chrome/browser/extensions/extension_popup_host.cc
index 1a964da7..ec679e33 100644
--- a/chrome/browser/extensions/extension_popup_host.cc
+++ b/chrome/browser/extensions/extension_popup_host.cc
@@ -4,18 +4,88 @@
 
 #include "chrome/browser/extensions/extension_popup_host.h"
 
+#include "base/message_loop.h"
 #if defined(TOOLKIT_VIEWS)
 #include "chrome/browser/extensions/extension_popup_api.h"
 #endif
 #include "chrome/browser/profile.h"
 #include "chrome/browser/browser.h"
 #include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
 #if defined(TOOLKIT_VIEWS)
 #include "chrome/browser/views/extensions/extension_popup.h"
 #endif
 #include "chrome/common/notification_details.h"
 #include "chrome/common/notification_source.h"
 #include "chrome/common/notification_type.h"
+#if defined(TOOLKIT_VIEWS)
+#include "views/focus/focus_manager.h"
+#include "views/widget/root_view.h"
+#endif
+
+#if defined(TOOLKIT_VIEWS)
+// A helper class that monitors native focus change events.  Because the views
+// FocusManager does not listen for view-change notification across
+// native-windows, we need to use native-listener utilities.
+class ExtensionPopupHost::PopupFocusListener
+    : public views::WidgetFocusChangeListener {
+ public:
+  // Constructs and registers a new PopupFocusListener for the given
+  // |popup_host|.
+  explicit PopupFocusListener(ExtensionPopupHost* popup_host)
+      : popup_host_(popup_host) {
+    views::FocusManager::GetWidgetFocusManager()->AddFocusChangeListener(this);
+  }
+
+  virtual ~PopupFocusListener() {
+    views::FocusManager::GetWidgetFocusManager()->
+        RemoveFocusChangeListener(this);
+  }
+
+  virtual void NativeFocusWillChange(gfx::NativeView focused_before,
+                                     gfx::NativeView focused_now) {
+    // If no view is to be focused, then Chrome was deactivated, so hide the
+    // popup.
+    if (!focused_now) {
+      popup_host_->DismissPopupAsync();
+      return;
+    }
+
+    gfx::NativeView host_view = popup_host_->delegate()->GetNativeViewOfHost();
+
+    // If the widget hosting the popup contains the newly focused view, then
+    // don't dismiss the pop-up.
+    views::Widget* popup_root_widget =
+        popup_host_->child_popup()->host()->view()->GetWidget();
+    if (popup_root_widget &&
+        popup_root_widget->ContainsNativeView(focused_now)) {
+      return;
+    }
+
+    // If the widget or RenderWidgetHostView hosting the extension that
+    // launched the pop-up is receiving focus, then don't dismiss the popup.
+    views::Widget* host_widget =
+        views::Widget::GetWidgetFromNativeView(host_view);
+    if (host_widget && host_widget->ContainsNativeView(focused_now)) {
+      return;
+    }
+
+    RenderWidgetHostView* render_host_view =
+        RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView(
+            host_view);
+    if (render_host_view &&
+        render_host_view->ContainsNativeView(focused_now)) {
+      return;
+    }
+
+    popup_host_->DismissPopupAsync();
+  }
+
+ private:
+  ExtensionPopupHost* popup_host_;
+  DISALLOW_COPY_AND_ASSIGN(PopupFocusListener);
+};
+#endif  // if defined(TOOLKIT_VIEWS)
 
 
 ExtensionPopupHost::PopupDelegate::~PopupDelegate() {
@@ -47,9 +117,11 @@
 ExtensionPopupHost::ExtensionPopupHost(PopupDelegate* delegate)
     :  // NO LINT
 #if defined(TOOLKIT_VIEWS)
+      listener_(NULL),
       child_popup_(NULL),
 #endif
-      delegate_(delegate) {
+      delegate_(delegate),
+      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
   DCHECK(delegate_);
 
   // Listen for view close requests, so that we can dismiss a hosted pop-up
@@ -65,22 +137,21 @@
 }
 
 #if defined(TOOLKIT_VIEWS)
-void ExtensionPopupHost::BubbleBrowserWindowMoved(BrowserBubble* bubble) {
+void ExtensionPopupHost::set_child_popup(ExtensionPopup* popup) {
+  // An extension may only have one popup active at a given time.
   DismissPopup();
+  if (popup)
+    listener_.reset(new PopupFocusListener(this));
+
+  child_popup_ = popup;
+}
+
+void ExtensionPopupHost::BubbleBrowserWindowMoved(BrowserBubble* bubble) {
+  DismissPopupAsync();
 }
 
 void ExtensionPopupHost::BubbleBrowserWindowClosing(BrowserBubble* bubble) {
-  DismissPopup();
-}
-
-void ExtensionPopupHost::BubbleGotFocus(BrowserBubble* bubble) {
-}
-
-void ExtensionPopupHost::BubbleLostFocus(BrowserBubble* bubble,
-                                         gfx::NativeView focused_view) {
-  // TODO(twiz):  Dismiss the pop-up upon loss of focus of the bubble, but not
-  // if the focus is transitioning to the host which owns the popup!
-  // DismissPopup();
+  DismissPopupAsync();
 }
 #endif  // defined(TOOLKIT_VIEWS)
 
@@ -103,6 +174,7 @@
 
 void ExtensionPopupHost::DismissPopup() {
 #if defined(TOOLKIT_VIEWS)
+  listener_.reset(NULL);
   if (child_popup_) {
     child_popup_->Hide();
     child_popup_->DetachFromBrowser();
@@ -117,3 +189,14 @@
   }
 #endif  // defined(TOOLKIT_VIEWS)
 }
+
+void ExtensionPopupHost::DismissPopupAsync() {
+  // Dismiss the popup asynchronously, as we could be deep in a message loop
+  // processing activations, and the focus manager may get confused if the
+  // currently focused view is destroyed.
+  method_factory_.RevokeAll();
+  MessageLoop::current()->PostNonNestableTask(
+      FROM_HERE,
+      method_factory_.NewRunnableMethod(
+          &ExtensionPopupHost::DismissPopup));
+}
diff --git a/chrome/browser/extensions/extension_popup_host.h b/chrome/browser/extensions/extension_popup_host.h
index 102c4ad..c11568b 100644
--- a/chrome/browser/extensions/extension_popup_host.h
+++ b/chrome/browser/extensions/extension_popup_host.h
@@ -5,7 +5,9 @@
 #ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_POPUP_HOST_H_
 #define CHROME_BROWSER_EXTENSIONS_EXTENSION_POPUP_HOST_H_
 
+#include "app/gfx/native_widget_types.h"
 #include "base/scoped_ptr.h"
+#include "base/task.h"
 #include "build/build_config.h"
 #if defined(TOOLKIT_VIEWS)
 #include "chrome/browser/views/browser_bubble.h"
@@ -39,6 +41,7 @@
     virtual ~PopupDelegate();
     virtual Browser* GetBrowser() const = 0;
     virtual RenderViewHost* GetRenderViewHost() = 0;
+    virtual gfx::NativeView GetNativeViewOfHost() = 0;
     virtual Profile* GetProfile();
 
     // Constructs, or returns the existing ExtensionPopupHost instance.
@@ -53,6 +56,7 @@
   explicit ExtensionPopupHost(PopupDelegate* delegate);
   virtual ~ExtensionPopupHost();
 
+  PopupDelegate* delegate() { return delegate_; }
   void RevokeDelegate() { delegate_ = NULL; }
 
   // Dismiss the hosted pop-up, if one is present.
@@ -60,11 +64,7 @@
 
 #if defined(TOOLKIT_VIEWS)
   ExtensionPopup* child_popup() const { return child_popup_; }
-  void set_child_popup(ExtensionPopup* popup) {
-    // An extension may only have one popup active at a given time.
-    DismissPopup();
-    child_popup_ = popup;
-  }
+  void set_child_popup(ExtensionPopup* popup);
 
   // BrowserBubble::Delegate implementation.
   // Called when the Browser Window that this bubble is attached to moves.
@@ -73,13 +73,6 @@
   // Called with the Browser Window that this bubble is attached to is
   // about to close.
   virtual void BubbleBrowserWindowClosing(BrowserBubble* bubble);
-
-  // Called when the bubble became active / got focus.
-  virtual void BubbleGotFocus(BrowserBubble* bubble);
-
-  // Called when the bubble became inactive / lost focus.
-  virtual void BubbleLostFocus(BrowserBubble* bubble,
-                               gfx::NativeView focused_view);
 #endif  // defined(TOOLKIT_VIEWS)
 
   // NotificationObserver implementation.
@@ -88,7 +81,16 @@
                        const NotificationDetails& details);
 
  private:
+  // Posts a task to the current thread's message-loop that will dismiss the
+  // popup.
+  void DismissPopupAsync();
+
 #if defined(TOOLKIT_VIEWS)
+  // A native-view focus listener that monitors when the pop-up should be
+  // dismissed due to focus change events.
+  class PopupFocusListener;
+  scoped_ptr<PopupFocusListener> listener_;
+
   // A popup view that is anchored to and owned by this ExtensionHost.  However,
   // the popup contains its own separate ExtensionHost
   ExtensionPopup* child_popup_;
@@ -103,6 +105,8 @@
   // notifications once.
   bool listeners_registered_;
 
+  ScopedRunnableMethodFactory<ExtensionPopupHost> method_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(ExtensionPopupHost);
 };
 
diff --git a/chrome/browser/renderer_host/render_widget_host_view.h b/chrome/browser/renderer_host/render_widget_host_view.h
index f7610537..e2f58d8 100644
--- a/chrome/browser/renderer_host/render_widget_host_view.h
+++ b/chrome/browser/renderer_host/render_widget_host_view.h
@@ -51,6 +51,11 @@
   // going to be a regular RenderWidgetHost or a RenderViewHost (a subclass).
   static RenderWidgetHostView* CreateViewForWidget(RenderWidgetHost* widget);
 
+  // Retrieves the RenderWidgetHostView corresponding to the specified
+  // |native_view|, or NULL if there is no such instance.
+  static RenderWidgetHostView* GetRenderWidgetHostViewFromNativeView(
+      gfx::NativeView native_view);
+
   // Perform all the initialization steps necessary for this object to represent
   // a popup (such as a <select> dropdown), then shows the popup at |pos|.
   virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
@@ -204,6 +209,10 @@
   }
   const SkBitmap& background() const { return background_; }
 
+  // Returns true if the native view, |native_view|, is contained within in the
+  // widget associated with this RenderWidgetHostView.
+  virtual bool ContainsNativeView(gfx::NativeView native_view) const = 0;
+
  protected:
   // Interface class only, do not construct.
   RenderWidgetHostView() : activatable_(true) {}
diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc
index 66cbe529..dffb61fae 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc
+++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc
@@ -43,6 +43,8 @@
 // TODO(brettw) make this a command line option.
 static const bool kUseGPURendering = false;
 
+static const char* kRenderWidgetHostViewKey = "__RENDER_WIDGET_HOST_VIEW__";
+
 using WebKit::WebInputEventFactory;
 
 // This class is a simple convenience wrapper for Gtk functions. It has only
@@ -102,6 +104,9 @@
     g_signal_connect_after(widget, "scroll-event",
                            G_CALLBACK(MouseScrollEvent), host_view);
 
+    g_object_set_data(G_OBJECT(widget), kRenderWidgetHostViewKey,
+                      static_cast<RenderWidgetHostView*>(host_view));
+
     return widget;
   }
 
@@ -744,6 +749,14 @@
   plugin_container_manager_.DestroyPluginContainer(id);
 }
 
+bool RenderWidgetHostViewGtk::ContainsNativeView(
+    gfx::NativeView native_view) const {
+  // TODO(port)
+  NOTREACHED() <<
+    "RenderWidgetHostViewGtk::ContainsNativeView not implemented.";
+  return false;
+}
+
 void RenderWidgetHostViewGtk::ForwardKeyboardEvent(
     const NativeWebKeyboardEvent& event) {
   if (!host_)
@@ -755,3 +768,12 @@
   }
   host_->ForwardKeyboardEvent(event);
 }
+
+// static
+RenderWidgetHostView*
+    RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView(
+        gfx::NativeView widget) {
+  gpointer user_data = g_object_get_data(G_OBJECT(widget),
+                                         kRenderWidgetHostViewKey);
+  return reinterpret_cast<RenderWidgetHostView*>(user_data);
+}
diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.h b/chrome/browser/renderer_host/render_widget_host_view_gtk.h
index f44a92b..fd2b579 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_gtk.h
+++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.h
@@ -71,6 +71,7 @@
   virtual void SetBackground(const SkBitmap& background);
   virtual void CreatePluginContainer(gfx::PluginWindowHandle id);
   virtual void DestroyPluginContainer(gfx::PluginWindowHandle id);
+  virtual bool ContainsNativeView(gfx::NativeView native_view) const;
 
   gfx::NativeView native_view() const { return view_.get(); }
 
diff --git a/chrome/browser/renderer_host/render_widget_host_view_mac.h b/chrome/browser/renderer_host/render_widget_host_view_mac.h
index c7072053..34903f0 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_mac.h
+++ b/chrome/browser/renderer_host/render_widget_host_view_mac.h
@@ -115,6 +115,7 @@
   virtual void SetWindowVisibility(bool visible);
   virtual void WindowFrameChanged();
   virtual void SetBackground(const SkBitmap& background);
+  virtual bool ContainsNativeView(gfx::NativeView native_view) const;
 
   // Methods associated with GPU plugin instances
   virtual gfx::PluginWindowHandle AllocateFakePluginWindowHandle();
diff --git a/chrome/browser/renderer_host/render_widget_host_view_mac.mm b/chrome/browser/renderer_host/render_widget_host_view_mac.mm
index 6e6df703..ac76e90 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/chrome/browser/renderer_host/render_widget_host_view_mac.mm
@@ -93,6 +93,16 @@
   return new RenderWidgetHostViewMac(widget);
 }
 
+// static
+RenderWidgetHostView* RenderWidgetHostView::
+    GetRenderWidgetHostViewFromNativeView(gfx::NativeView native_view) {
+  // TODO(port)
+  NOTREACHED() <<
+      "RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView not"
+      "implemented";
+  return NULL;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // RenderWidgetHostViewMac, public:
 
@@ -645,6 +655,14 @@
         render_widget_host_->routing_id(), background));
 }
 
+bool RenderWidgetHostViewMac::ContainsNativeView(
+    gfx::NativeView native_view) const {
+  // TODO(port)
+  NOTREACHED() <<
+    "RenderWidgetHostViewMac::ContainsNativeView not implemented.";
+  return false;
+}
+
 // EditCommandMatcher ---------------------------------------------------------
 
 // This class is used to capture the shortcuts that a given key event maps to.
diff --git a/chrome/browser/renderer_host/render_widget_host_view_win.cc b/chrome/browser/renderer_host/render_widget_host_view_win.cc
index 8bff4d2..3216c7cc 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_win.cc
+++ b/chrome/browser/renderer_host/render_widget_host_view_win.cc
@@ -35,6 +35,7 @@
 #include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h"
 #include "third_party/WebKit/WebKit/chromium/public/win/WebInputEventFactory.h"
 #include "views/accessibility/view_accessibility.h"
+#include "views/focus/focus_manager.h"
 #include "views/focus/focus_util_win.h"
 // Included for views::kReflectedMessage - TODO(beng): move this to win_util.h!
 #include "views/widget/widget_win.h"
@@ -58,6 +59,8 @@
 // Maximum number of characters we allow in a tooltip.
 const int kMaxTooltipLength = 1024;
 
+const wchar_t* kRenderWidgetHostViewKey = L"__RENDER_WIDGET_HOST_VIEW__";
+
 // A callback function for EnumThreadWindows to enumerate and dismiss
 // any owned popop windows
 BOOL CALLBACK DismissOwnedPopups(HWND window, LPARAM arg) {
@@ -728,6 +731,23 @@
                                  background));
 }
 
+bool RenderWidgetHostViewWin::ContainsNativeView(
+    gfx::NativeView native_view) const {
+  if (m_hWnd == native_view)
+    return true;
+
+  // Traverse the set of parents of the given view to determine if native_view
+  // is a descendant of this window.
+  HWND parent_window = ::GetParent(native_view);
+  while (parent_window) {
+    if (parent_window == m_hWnd)
+      return true;
+    parent_window = ::GetParent(parent_window);
+  }
+
+  return false;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // RenderWidgetHostViewWin, private:
 
@@ -740,7 +760,9 @@
   views::SetWindowSupportsRerouteMouseWheel(m_hWnd);
   // Save away our HWND in the parent window as a property so that the
   // accessibility code can find it.
-  SetProp(GetParent(), kViewsNativeHostPropForAccessibility, m_hWnd);
+  ::SetProp(GetParent(), kViewsNativeHostPropForAccessibility, m_hWnd);
+  ::SetProp(m_hWnd, kRenderWidgetHostViewKey,
+            static_cast<RenderWidgetHostView*>(this));
   return 0;
 }
 
@@ -771,6 +793,8 @@
   // sequence as part of the usual cleanup when the plugin instance goes away.
   EnumChildWindows(m_hWnd, DetachPluginWindowsCallback, NULL);
 
+  ::RemoveProp(m_hWnd, kRenderWidgetHostViewKey);
+
   ResetTooltip();
   TrackMouseLeave(false);
 }
@@ -919,11 +943,17 @@
 }
 
 void RenderWidgetHostViewWin::OnSetFocus(HWND window) {
+  views::FocusManager::GetWidgetFocusManager()->OnWidgetFocusEvent(window,
+                                                                   m_hWnd);
+
   if (render_widget_host_)
     render_widget_host_->GotFocus();
 }
 
 void RenderWidgetHostViewWin::OnKillFocus(HWND window) {
+  views::FocusManager::GetWidgetFocusManager()->OnWidgetFocusEvent(m_hWnd,
+                                                                   window);
+
   if (render_widget_host_)
     render_widget_host_->Blur();
 }
@@ -1516,3 +1546,16 @@
     render_widget_host_->Shutdown();
   // Do not touch any members at this point, |this| has been deleted.
 }
+
+// static
+RenderWidgetHostView*
+    RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView(
+        gfx::NativeView native_view) {
+  if (::IsWindow(native_view)) {
+    HANDLE raw_render_host_view = ::GetProp(native_view,
+                                            kRenderWidgetHostViewKey);
+    if (raw_render_host_view)
+      return reinterpret_cast<RenderWidgetHostView*>(raw_render_host_view);
+  }
+  return NULL;
+}
diff --git a/chrome/browser/renderer_host/render_widget_host_view_win.h b/chrome/browser/renderer_host/render_widget_host_view_win.h
index 5555495..64224fc 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_win.h
+++ b/chrome/browser/renderer_host/render_widget_host_view_win.h
@@ -137,6 +137,7 @@
   virtual void SetTooltipText(const std::wstring& tooltip_text);
   virtual BackingStore* AllocBackingStore(const gfx::Size& size);
   virtual void SetBackground(const SkBitmap& background);
+  virtual bool ContainsNativeView(gfx::NativeView native_view) const;
 
  protected:
   // Windows Message Handlers
diff --git a/chrome/browser/renderer_host/test/test_render_view_host.h b/chrome/browser/renderer_host/test/test_render_view_host.h
index 0521de9..85e6eca 100644
--- a/chrome/browser/renderer_host/test/test_render_view_host.h
+++ b/chrome/browser/renderer_host/test/test_render_view_host.h
@@ -94,6 +94,10 @@
   virtual void DestroyPluginContainer(gfx::PluginWindowHandle id) { }
 #endif
 
+  virtual bool ContainsNativeView(gfx::NativeView native_view) const {
+    return false;
+  }
+
   bool is_showing() const { return is_showing_; }
 
  private:
diff --git a/chrome/browser/tab_contents/tab_contents_delegate.h b/chrome/browser/tab_contents/tab_contents_delegate.h
index 9db2245..e2b00b5 100644
--- a/chrome/browser/tab_contents/tab_contents_delegate.h
+++ b/chrome/browser/tab_contents/tab_contents_delegate.h
@@ -275,7 +275,7 @@
   // Returns the browser in which the tab contents is being displayed.
   virtual Browser* GetBrowser() { return NULL; }
 
-  // Returns the widget framing the view containing the tab contents.
+  // Returns the native window framing the view containing the tab contents.
   virtual gfx::NativeWindow GetFrameNativeWindow() { return NULL; }
 
  protected:
diff --git a/chrome/browser/views/browser_bubble_win.cc b/chrome/browser/views/browser_bubble_win.cc
index 41d671a..2a2a4c24 100644
--- a/chrome/browser/views/browser_bubble_win.cc
+++ b/chrome/browser/views/browser_bubble_win.cc
@@ -48,6 +48,7 @@
   }
 
   void OnActivate(UINT action, BOOL minimized, HWND window) {
+    WidgetWin::OnActivate(action, minimized, window);
     BrowserBubble::Delegate* delegate = bubble_->delegate();
     if (!delegate) {
       if (action == WA_INACTIVE && !closed_) {
@@ -63,6 +64,7 @@
   }
 
   virtual void OnSetFocus(HWND focused_window) {
+    WidgetWin::OnSetFocus(focused_window);
     BrowserBubble::Delegate* delegate = bubble_->delegate();
     if (delegate)
       delegate->BubbleGotFocus(bubble_);
diff --git a/chrome/browser/views/tab_contents/native_tab_contents_container_win.cc b/chrome/browser/views/tab_contents/native_tab_contents_container_win.cc
index 83db8e0..355d776 100644
--- a/chrome/browser/views/tab_contents/native_tab_contents_container_win.cc
+++ b/chrome/browser/views/tab_contents/native_tab_contents_container_win.cc
@@ -114,7 +114,13 @@
   // that should also have focus, RequestFocus() is invoked one the
   // TabContentsContainer.  In order to make sure Focus() is invoked we need to
   // clear the focus before hands.
-  GetFocusManager()->ClearFocus();
+  {
+    // Disable notifications.  Clear focus will assign the focus to the main
+    // browser window.  Because this change of focus was not user requested,
+    // don't send it to listeners.
+    views::AutoNativeNotificationDisabler local_notification_disabler;
+    GetFocusManager()->ClearFocus();
+  }
   View::RequestFocus();
 }
 
diff --git a/views/controls/native/native_view_host.cc b/views/controls/native/native_view_host.cc
index 1274e5c..79abc1d39 100644
--- a/views/controls/native/native_view_host.cc
+++ b/views/controls/native/native_view_host.cc
@@ -7,6 +7,7 @@
 #include "base/logging.h"
 #include "app/gfx/canvas.h"
 #include "views/controls/native/native_view_host_wrapper.h"
+#include "views/widget/root_view.h"
 #include "views/widget/widget.h"
 
 namespace views {
@@ -145,6 +146,20 @@
   native_wrapper_->SetFocus();
 }
 
+bool NativeViewHost::ContainsNativeView(gfx::NativeView native_view) const {
+  if (native_view == native_view_)
+    return true;
+
+  views::Widget* native_widget =
+      views::Widget::GetWidgetFromNativeView(native_view_);
+  views::RootView* root_view =
+      native_widget ? native_widget->GetRootView() : NULL;
+  if (root_view && root_view->ContainsNativeView(native_view))
+    return true;
+
+  return View::ContainsNativeView(native_view);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // NativeViewHost, private:
 
diff --git a/views/controls/native/native_view_host.h b/views/controls/native/native_view_host.h
index d6115b2..5a9a403 100644
--- a/views/controls/native/native_view_host.h
+++ b/views/controls/native/native_view_host.h
@@ -74,6 +74,7 @@
   virtual void Paint(gfx::Canvas* canvas);
   virtual void VisibilityChanged(View* starting_from, bool is_visible);
   virtual void Focus();
+  virtual bool ContainsNativeView(gfx::NativeView native_view) const;
 
  protected:
   virtual void VisibleBoundsInRootChanged();
diff --git a/views/focus/focus_manager.cc b/views/focus/focus_manager.cc
index 9942854..f637e091 100644
--- a/views/focus/focus_manager.cc
+++ b/views/focus/focus_manager.cc
@@ -22,6 +22,46 @@
 
 namespace views {
 
+// FocusManager::WidgetFocusManager ---------------------------------
+
+void FocusManager::WidgetFocusManager::AddFocusChangeListener(
+    WidgetFocusChangeListener* listener) {
+  DCHECK(std::find(focus_change_listeners_.begin(),
+                   focus_change_listeners_.end(), listener) ==
+         focus_change_listeners_.end()) <<
+             "Adding a WidgetFocusChangeListener twice.";
+  focus_change_listeners_.push_back(listener);
+}
+
+void FocusManager::WidgetFocusManager::RemoveFocusChangeListener(
+    WidgetFocusChangeListener* listener) {
+  WidgetFocusChangeListenerList::iterator iter(std::find(
+      focus_change_listeners_.begin(),
+      focus_change_listeners_.end(),
+      listener));
+  if (iter != focus_change_listeners_.end()) {
+    focus_change_listeners_.erase(iter);
+  } else {
+    NOTREACHED() <<
+      "Attempting to remove an unregistered WidgetFocusChangeListener.";
+  }
+}
+
+void FocusManager::WidgetFocusManager::OnWidgetFocusEvent(
+    gfx::NativeView focused_before,
+    gfx::NativeView focused_now) {
+  if (!enabled_)
+    return;
+
+  // Perform a safe iteration over the focus listeners, as the array of
+  // may change during notification.
+  WidgetFocusChangeListenerList local_listeners(focus_change_listeners_);
+  WidgetFocusChangeListenerList::iterator iter(local_listeners.begin());
+  for (;iter != local_listeners.end(); ++iter) {
+    (*iter)->NativeFocusWillChange(focused_before, focused_now);
+  }
+}
+
 // FocusManager -----------------------------------------------------
 
 FocusManager::FocusManager(Widget* widget)
@@ -277,7 +317,15 @@
   view_storage->StoreView(stored_focused_view_storage_id_, focused_view_);
 
   View* v = focused_view_;
-  ClearFocus();
+
+  {
+    // Temporarily disable notification.  ClearFocus() will set the focus to the
+    // main browser window.  This extra focus bounce which happens during
+    // deactivation can confuse registered WidgetFocusListeners, as the focus
+    // is not changing due to a user-initiated event.
+    AutoNativeNotificationDisabler local_notification_disabler;
+    ClearFocus();
+  }
 
   if (v)
     v->SchedulePaint();  // Remove focus border.
diff --git a/views/focus/focus_manager.h b/views/focus/focus_manager.h
index aec8911..00e7a0a 100644
--- a/views/focus/focus_manager.h
+++ b/views/focus/focus_manager.h
@@ -5,12 +5,13 @@
 #ifndef VIEWS_FOCUS_FOCUS_MANAGER_H_
 #define VIEWS_FOCUS_FOCUS_MANAGER_H_
 
-#include <vector>
-#include <map>
 #include <list>
+#include <map>
+#include <vector>
 
 #include "app/gfx/native_widget_types.h"
 #include "base/basictypes.h"
+#include "base/singleton.h"
 #include "views/accelerator.h"
 
 // The FocusManager class is used to handle focus traversal, store/restore
@@ -132,11 +133,61 @@
   virtual void FocusWillChange(View* focused_before, View* focused_now) = 0;
 };
 
+// This interface should be implemented by classes that want to be notified when
+// the native focus is about to change.  Listeners implementing this interface
+// will be invoked for all native focus changes across the entire Chrome
+// application.  FocusChangeListeners are only called for changes within the
+// children of a single top-level native-view.
+class WidgetFocusChangeListener {
+ public:
+  virtual void NativeFocusWillChange(gfx::NativeView focused_before,
+                                     gfx::NativeView focused_now) = 0;
+};
+
 class FocusManager {
  public:
+  class WidgetFocusManager {
+   public:
+    // Adds/removes a WidgetFocusChangeListener |listener| to the set of
+    // active listeners.
+    void AddFocusChangeListener(WidgetFocusChangeListener* listener);
+    void RemoveFocusChangeListener(WidgetFocusChangeListener* listener);
+
+    // To be called when native-focus shifts from |focused_before| to
+    // |focused_now|.
+    // TODO(port) : Invocations to this routine are only implemented for
+    // the Win32 platform.  Calls need to be placed appropriately for
+    // non-Windows environments.
+    void OnWidgetFocusEvent(gfx::NativeView focused_before,
+                            gfx::NativeView focused_now);
+
+    // Enable/Disable notification of registered listeners during calls
+    // to OnWidgetFocusEvent.  Used to prevent unwanted focus changes from
+    // propagating notifications.
+    void EnableNotifications() { enabled_ = true; }
+    void DisableNotifications() { enabled_ = false; }
+
+   private:
+    WidgetFocusManager() : enabled_(true) {}
+
+    typedef std::vector<WidgetFocusChangeListener*>
+      WidgetFocusChangeListenerList;
+    WidgetFocusChangeListenerList focus_change_listeners_;
+
+    bool enabled_;
+
+    friend struct DefaultSingletonTraits<WidgetFocusManager>;
+    DISALLOW_COPY_AND_ASSIGN(WidgetFocusManager);
+  };
+
   explicit FocusManager(Widget* widget);
   ~FocusManager();
 
+  // Returns the global WidgetFocusManager instance for the running application.
+  static WidgetFocusManager* GetWidgetFocusManager() {
+    return Singleton<WidgetFocusManager>::get();
+  }
+
   // Processes the passed key event for accelerators and tab traversal.
   // Returns false if the event has been consumed and should not be processed
   // further.
@@ -261,6 +312,21 @@
   DISALLOW_COPY_AND_ASSIGN(FocusManager);
 };
 
+// A basic helper class that is used to disable native focus change
+// notifications within a scope.
+class AutoNativeNotificationDisabler {
+ public:
+  AutoNativeNotificationDisabler() {
+    FocusManager::GetWidgetFocusManager()->DisableNotifications();
+  }
+
+  ~AutoNativeNotificationDisabler() {
+    FocusManager::GetWidgetFocusManager()->EnableNotifications();
+  }
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AutoNativeNotificationDisabler);
+};
+
 }  // namespace views
 
 #endif  // VIEWS_FOCUS_FOCUS_MANAGER_H_
diff --git a/views/view.cc b/views/view.cc
index 53f1102..3b823a8 100644
--- a/views/view.cc
+++ b/views/view.cc
@@ -788,6 +788,14 @@
   return widget ? widget->GetWindow() : NULL;
 }
 
+bool View::ContainsNativeView(gfx::NativeView native_view) const {
+  for (int i = 0, count = GetChildViewCount(); i < count; ++i) {
+    if (GetChildViewAt(i)->ContainsNativeView(native_view))
+      return true;
+  }
+  return false;
+}
+
 // Get the containing RootView
 RootView* View::GetRootView() {
   Widget* widget = GetWidget();
diff --git a/views/view.h b/views/view.h
index 5ae8be53..b44c04d 100644
--- a/views/view.h
+++ b/views/view.h
@@ -455,6 +455,10 @@
   // level windows (as is done for popups, bubbles and menus).
   virtual Window* GetWindow() const;
 
+  // Returns true if the native view |native_view| is contained in the view
+  // hierarchy beneath this view.
+  virtual bool ContainsNativeView(gfx::NativeView native_view) const;
+
   // Get the containing RootView
   virtual RootView* GetRootView();
 
diff --git a/views/widget/widget.h b/views/widget/widget.h
index fa8111eec..a3959b4 100644
--- a/views/widget/widget.h
+++ b/views/widget/widget.h
@@ -193,6 +193,10 @@
   // Forwarded from the RootView so that the widget can do any cleanup.
   virtual void ViewHierarchyChanged(bool is_add, View *parent,
                                     View *child) = 0;
+
+  // Returns true if the native view |native_view| is contained in the
+  // views::View hierarchy rooted at this widget.
+  virtual bool ContainsNativeView(gfx::NativeView native_view) = 0;
 };
 
 }  // namespace views
diff --git a/views/widget/widget_gtk.cc b/views/widget/widget_gtk.cc
index 3f9b3c4..44bc82d 100644
--- a/views/widget/widget_gtk.cc
+++ b/views/widget/widget_gtk.cc
@@ -590,6 +590,12 @@
     drop_target_->ResetTargetViewIfEquals(child);
 }
 
+bool WidgetGtk::ContainsNativeView(gfx::NativeView native_view) {
+  // TODO(port)  See implementation in WidgetWin::ContainsNativeView.
+  NOTREACHED() << "WidgetGtk::ContainsNativeView is not implemented.";
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // WidgetGtk, MessageLoopForUI::Observer implementation:
 
@@ -1239,7 +1245,8 @@
     gtk_fixed_set_has_window(GTK_FIXED(window_contents_), true);
     gtk_container_add(GTK_CONTAINER(widget_), window_contents_);
     gtk_widget_show(window_contents_);
-    g_object_set_data(G_OBJECT(window_contents_), kWidgetKey, this);
+    g_object_set_data(G_OBJECT(window_contents_), kWidgetKey,
+                      static_cast<Widget*>(this));
 
     if (transparent_)
       ConfigureWidgetForTransparentBackground();
diff --git a/views/widget/widget_gtk.h b/views/widget/widget_gtk.h
index 9d73bf0..0c0096c 100644
--- a/views/widget/widget_gtk.h
+++ b/views/widget/widget_gtk.h
@@ -168,6 +168,8 @@
   virtual FocusManager* GetFocusManager();
   virtual void ViewHierarchyChanged(bool is_add, View *parent,
                                     View *child);
+  virtual bool ContainsNativeView(gfx::NativeView native_view);
+
 
   // Overridden from MessageLoopForUI::Observer:
   virtual void WillProcessEvent(GdkEvent* event);
diff --git a/views/widget/widget_win.cc b/views/widget/widget_win.cc
index 6f75506..ea54036 100644
--- a/views/widget/widget_win.cc
+++ b/views/widget/widget_win.cc
@@ -383,6 +383,28 @@
     drop_target_->ResetTargetViewIfEquals(child);
 }
 
+
+bool WidgetWin::ContainsNativeView(gfx::NativeView native_view) {
+  if (hwnd() == native_view)
+    return true;
+
+  // Traverse the set of parents of the given view to determine if native_view
+  // is a descendant of this window.
+  HWND parent_window = ::GetParent(native_view);
+  HWND previous_child = native_view;
+  while (parent_window && parent_window != previous_child) {
+    if (hwnd() == parent_window)
+      return true;
+    previous_child = parent_window;
+    parent_window = ::GetParent(parent_window);
+  }
+
+  // A views::NativeViewHost may contain the given native view, without it being
+  // an ancestor of hwnd(), so traverse the views::View hierarchy looking for
+  // such views.
+  return GetRootView()->ContainsNativeView(native_view);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // MessageLoop::Observer
 
@@ -595,6 +617,13 @@
   SetMsgHandled(root_view->ProcessKeyEvent(event));
 }
 
+void WidgetWin::OnKillFocus(HWND focused_window) {
+  GetFocusManager()->GetWidgetFocusManager()->OnWidgetFocusEvent(
+      this->GetNativeView(),
+      focused_window);
+  SetMsgHandled(FALSE);
+}
+
 // TODO(pkasting): ORing the pressed/released button into the flags is _wrong_.
 // It makes it impossible to tell which button was modified when multiple
 // buttons are/were held down.  We need to instead put the modified button into
@@ -800,6 +829,9 @@
 }
 
 void WidgetWin::OnSetFocus(HWND focused_window) {
+  GetFocusManager()->GetWidgetFocusManager()->OnWidgetFocusEvent(
+      focused_window,
+      this->GetNativeView());
   SetMsgHandled(FALSE);
 }
 
@@ -1236,7 +1268,6 @@
     root_views->push_back(*it);
 }
 
-
 ////////////////////////////////////////////////////////////////////////////////
 // Widget, public:
 
diff --git a/views/widget/widget_win.h b/views/widget/widget_win.h
index 28a3782..ed233d4 100644
--- a/views/widget/widget_win.h
+++ b/views/widget/widget_win.h
@@ -130,6 +130,7 @@
     MSG_WM_INITMENUPOPUP(OnInitMenuPopup)
     MSG_WM_KEYDOWN(OnKeyDown)
     MSG_WM_KEYUP(OnKeyUp)
+    MSG_WM_KILLFOCUS(OnKillFocus)
     MSG_WM_SYSKEYDOWN(OnKeyDown)
     MSG_WM_SYSKEYUP(OnKeyUp)
     MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk)
@@ -207,6 +208,7 @@
   virtual FocusManager* GetFocusManager();
   virtual void ViewHierarchyChanged(bool is_add, View *parent,
                                     View *child);
+  virtual bool ContainsNativeView(gfx::NativeView native_view);
 
   // Overridden from MessageLoop::Observer:
   void WillProcessMessage(const MSG& msg);
@@ -335,6 +337,7 @@
   virtual void OnInitMenuPopup(HMENU menu, UINT position, BOOL is_system_menu);
   virtual void OnKeyDown(TCHAR c, UINT rep_cnt, UINT flags);
   virtual void OnKeyUp(TCHAR c, UINT rep_cnt, UINT flags);
+  virtual void OnKillFocus(HWND focused_window);
   virtual void OnLButtonDblClk(UINT flags, const CPoint& point);
   virtual void OnLButtonDown(UINT flags, const CPoint& point);
   virtual void OnLButtonUp(UINT flags, const CPoint& point);