Windowed mode mouse lock addded to fullscreen controller.

Allows mouse lock to be entered outside of fullscreen.

The root changes are in
chrome/browser/ui/fullscreen_controller

They require the user interface bubble to be more tolerant of state changes. Three versions of that code are modified:
Views:
chrome/browser/ui/views/frame/browser_view.cc
GTK:
chrome/browser/ui/gtk/browser_window_gtk.cc
Mac:
chrome/browser/ui/cocoa/...

Testing is expanded in:
chrome/browser/ui/browser_browsertest.cc

BUG=107013
TEST=Entering and exiting of mouse lock and fullscreen. chrome/test/data/fullscreen_mouselock/fullscreen_mouselock.html

Review URL: https://chromiumcodereview.appspot.com/10261011

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@138150 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/ui/browser_browsertest.cc b/chrome/browser/ui/browser_browsertest.cc
index 26e7ece5..d2fe41b 100644
--- a/chrome/browser/ui/browser_browsertest.cc
+++ b/chrome/browser/ui/browser_browsertest.cc
@@ -53,6 +53,7 @@
 #include "content/public/browser/notification_source.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/page_transition_types.h"
@@ -97,6 +98,9 @@
 
 const FilePath::CharType kDocRoot[] = FILE_PATH_LITERAL("chrome/test/data");
 
+const char* kFullscreenMouseLockHTML =
+    "files/fullscreen_mouselock/fullscreen_mouselock.html";
+
 // Given a page title, returns the expected window caption string.
 string16 WindowCaptionFromPageTitle(const string16& page_title) {
 #if defined(OS_MACOSX) || defined(OS_CHROMEOS)
@@ -179,7 +183,6 @@
   InterstitialPage* interstitial_page_;  // Owns us.
 };
 
-// Fullscreen transition notification observer simplifies test code.
 class FullscreenNotificationObserver
     : public ui_test_utils::WindowedNotificationObserver {
  public:
@@ -188,10 +191,23 @@
       content::NotificationService::AllSources()) {}
 };
 
+class MouseLockNotificationObserver
+    : public ui_test_utils::WindowedNotificationObserver {
+ public:
+  MouseLockNotificationObserver() : WindowedNotificationObserver(
+      chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+      content::NotificationService::AllSources()) {}
+};
+
 }  // namespace
 
 class BrowserTest : public ExtensionBrowserTest {
  protected:
+  virtual void SetUpCommandLine(CommandLine* command_line) {
+    ExtensionBrowserTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(switches::kEnablePointerLock);
+  }
+
   // In RTL locales wrap the page title with RTL embedding characters so that it
   // matches the value returned by GetWindowTitle().
   string16 LocaleWindowCaptionFromPageTitle(const string16& expected_title) {
@@ -255,6 +271,10 @@
     browser()->LostMouseLock();
   }
 
+  bool SendEscapeToFullscreenController() {
+    return browser()->fullscreen_controller_->HandleUserPressedEscape();
+  }
+
   bool IsFullscreenForBrowser() {
     return browser()->fullscreen_controller_->IsFullscreenForBrowser();
   }
@@ -264,6 +284,11 @@
   }
 
   bool IsMouseLocked() {
+    // Verify that IsMouseLocked is consistent between the
+    // Fullscreen Controller and the Render View Host View.
+    EXPECT_TRUE(browser()->IsMouseLocked() ==
+                browser()->GetSelectedWebContents()->
+                    GetRenderViewHost()->GetView()->IsMouseLocked());
     return browser()->IsMouseLocked();
   }
 
@@ -290,8 +315,7 @@
   bool IsFullscreenBubbleDisplayed() {
     FullscreenExitBubbleType type =
         browser()->fullscreen_controller_->GetFullscreenExitBubbleType();
-    // TODO(scheib): Should be FEB_TYPE_NONE, crbug.com/107013 will include fix.
-    return type != FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION;
+    return type != FEB_TYPE_NONE;
   }
 
   bool IsFullscreenBubbleDisplayingButtons() {
@@ -307,6 +331,12 @@
     browser()->OnAcceptFullscreenPermission(fullscreen_tab->GetURL(), type);
   }
 
+  void DenyCurrentFullscreenOrMouseLockRequest() {
+    FullscreenExitBubbleType type =
+        browser()->fullscreen_controller_->GetFullscreenExitBubbleType();
+    browser()->OnDenyFullscreenPermission(type);
+  }
+
   // Helper method to be called by multiple tests.
   void TestFullscreenMouseLockContentSettings();
 };
@@ -1037,17 +1067,222 @@
   ASSERT_FALSE(IsFullscreenBubbleDisplayingButtons());
 }
 
-// Tests mouse lock fails before fullscreen is entered.
+// Tests mouse lock then fullscreen.
 IN_PROC_BROWSER_TEST_F(BrowserTest, MouseLockThenFullscreen) {
+  ASSERT_TRUE(test_server()->Start());
+  ui_test_utils::NavigateToURL(browser(),
+                               test_server()->GetURL(kFullscreenMouseLockHTML));
+
   WebContents* tab = browser()->GetSelectedWebContents();
+
   ASSERT_FALSE(IsFullscreenBubbleDisplayed());
 
-  RequestToLockMouse(tab, true);
+  // Lock the mouse without a user gesture, expect no response.
+  ASSERT_TRUE(ui_test_utils::SendKeyPressAndWait(
+      browser(), ui::VKEY_D, false, false, false, false,
+      chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+      content::NotificationService::AllSources()));
   ASSERT_FALSE(IsFullscreenBubbleDisplayed());
+  ASSERT_FALSE(IsMouseLocked());
 
+  // Lock the mouse with a user gesture.
+  ASSERT_TRUE(ui_test_utils::SendKeyPressAndWait(
+      browser(), ui::VKEY_1, false, false, false, false,
+      chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+      content::NotificationService::AllSources()));
+  ASSERT_TRUE(IsFullscreenBubbleDisplayed());
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+  ASSERT_TRUE(IsMouseLockPermissionRequested());
+  ASSERT_FALSE(IsMouseLocked());
+
+  // Accept mouse lock.
+  AcceptCurrentFullscreenOrMouseLockRequest();
+  ASSERT_TRUE(IsMouseLocked());
+  ASSERT_FALSE(IsFullscreenBubbleDisplayingButtons());
+
+  // Enter fullscreen mode, mouse lock should be dropped to present buttons.
   ASSERT_NO_FATAL_FAILURE(ToggleTabFullscreen(tab, true));
   ASSERT_TRUE(IsFullscreenPermissionRequested());
   ASSERT_FALSE(IsMouseLockPermissionRequested());
+  ASSERT_FALSE(IsMouseLocked());
+
+  // Request mouse lock also, expect fullscreen and mouse lock buttons.
+  ASSERT_TRUE(ui_test_utils::SendKeyPressAndWait(
+      browser(), ui::VKEY_1, false, false, false, false,
+      chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+      content::NotificationService::AllSources()));
+  ASSERT_TRUE(IsFullscreenPermissionRequested());
+  ASSERT_TRUE(IsMouseLockPermissionRequested());
+  ASSERT_FALSE(IsMouseLocked());
+
+  // Accept fullscreen and mouse lock.
+  AcceptCurrentFullscreenOrMouseLockRequest();
+  ASSERT_TRUE(IsMouseLocked());
+  ASSERT_TRUE(IsFullscreenForTabOrPending());
+  ASSERT_FALSE(IsFullscreenBubbleDisplayingButtons());
+}
+
+// Tests mouse lock then fullscreen in same request.
+IN_PROC_BROWSER_TEST_F(BrowserTest, MouseLockAndFullscreen) {
+  ASSERT_TRUE(test_server()->Start());
+  ui_test_utils::NavigateToURL(browser(),
+                               test_server()->GetURL(kFullscreenMouseLockHTML));
+
+  ASSERT_FALSE(IsFullscreenBubbleDisplayed());
+
+  // Request to lock the mouse and enter fullscreen.
+  {
+    FullscreenNotificationObserver fullscreen_observer;
+    ASSERT_TRUE(ui_test_utils::SendKeyPressAndWait(
+        browser(), ui::VKEY_B, false, true, false, false,
+        chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+        content::NotificationService::AllSources()));
+    fullscreen_observer.Wait();
+  }
+  ASSERT_TRUE(IsFullscreenBubbleDisplayed());
+  ASSERT_TRUE(IsFullscreenPermissionRequested());
+  ASSERT_TRUE(IsMouseLockPermissionRequested());
+  ASSERT_FALSE(IsMouseLocked());
+  ASSERT_TRUE(IsFullscreenForTabOrPending());
+
+  // Deny both first, to make sure we can.
+  {
+    FullscreenNotificationObserver fullscreen_observer;
+    DenyCurrentFullscreenOrMouseLockRequest();
+    fullscreen_observer.Wait();
+  }
+  ASSERT_FALSE(IsMouseLocked());
+  ASSERT_FALSE(IsFullscreenForTabOrPending());
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+
+  // Request to lock the mouse and enter fullscreen.
+  {
+    FullscreenNotificationObserver fullscreen_observer;
+    ASSERT_TRUE(ui_test_utils::SendKeyPressAndWait(
+        browser(), ui::VKEY_B, false, true, false, false,
+        chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+        content::NotificationService::AllSources()));
+    fullscreen_observer.Wait();
+  }
+  ASSERT_TRUE(IsFullscreenBubbleDisplayed());
+  ASSERT_TRUE(IsFullscreenPermissionRequested());
+  ASSERT_TRUE(IsMouseLockPermissionRequested());
+  ASSERT_FALSE(IsMouseLocked());
+  ASSERT_TRUE(IsFullscreenForTabOrPending());
+
+  // Accept both, confirm they are enabled and there is no prompt.
+  AcceptCurrentFullscreenOrMouseLockRequest();
+  ASSERT_TRUE(IsMouseLocked());
+  ASSERT_TRUE(IsFullscreenForTabOrPending());
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+}
+
+// Tests mouse lock can be escaped with ESC key.
+IN_PROC_BROWSER_TEST_F(BrowserTest, EscapingMouseLock) {
+  ASSERT_TRUE(test_server()->Start());
+  ui_test_utils::NavigateToURL(browser(),
+                               test_server()->GetURL(kFullscreenMouseLockHTML));
+
+  ASSERT_FALSE(IsFullscreenBubbleDisplayed());
+
+  // Request to lock the mouse.
+  {
+    ASSERT_TRUE(ui_test_utils::SendKeyPressAndWait(
+        browser(), ui::VKEY_1, false, false, false, false,
+        chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+        content::NotificationService::AllSources()));
+  }
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+  ASSERT_TRUE(IsMouseLockPermissionRequested());
+
+  // Escape, no prompts should remain.
+  SendEscapeToFullscreenController();
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+  ASSERT_FALSE(IsMouseLockPermissionRequested());
+
+  // Request to lock the mouse.
+  {
+    ASSERT_TRUE(ui_test_utils::SendKeyPressAndWait(
+        browser(), ui::VKEY_1, false, false, false, false,
+        chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+        content::NotificationService::AllSources()));
+  }
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+  ASSERT_TRUE(IsMouseLockPermissionRequested());
+
+  // Accept mouse lock, confirm it and that there is no prompt.
+  AcceptCurrentFullscreenOrMouseLockRequest();
+  ASSERT_TRUE(IsMouseLocked());
+  ASSERT_FALSE(IsFullscreenForTabOrPending());
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+  ASSERT_FALSE(IsMouseLockPermissionRequested());
+
+  // Escape, confirm we are out of mouse lock with no prompts.
+  SendEscapeToFullscreenController();
+  ASSERT_FALSE(IsMouseLocked());
+  ASSERT_FALSE(IsFullscreenForTabOrPending());
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+  ASSERT_FALSE(IsMouseLockPermissionRequested());
+}
+
+// Tests mouse lock and fullscreen modes can be escaped with ESC key.
+IN_PROC_BROWSER_TEST_F(BrowserTest, EscapingMouseLockAndFullscreen) {
+  ASSERT_TRUE(test_server()->Start());
+  ui_test_utils::NavigateToURL(browser(),
+                               test_server()->GetURL(kFullscreenMouseLockHTML));
+
+  ASSERT_FALSE(IsFullscreenBubbleDisplayed());
+
+  // Request to lock the mouse and enter fullscreen.
+  {
+    FullscreenNotificationObserver fullscreen_observer;
+    ASSERT_TRUE(ui_test_utils::SendKeyPressAndWait(
+        browser(), ui::VKEY_B, false, true, false, false,
+        chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+        content::NotificationService::AllSources()));
+    fullscreen_observer.Wait();
+  }
+  ASSERT_TRUE(IsFullscreenPermissionRequested());
+  ASSERT_TRUE(IsMouseLockPermissionRequested());
+
+  // Escape, no prompts should remain.
+  {
+    FullscreenNotificationObserver fullscreen_observer;
+    SendEscapeToFullscreenController();
+    fullscreen_observer.Wait();
+  }
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+  ASSERT_FALSE(IsMouseLockPermissionRequested());
+
+  // Request to lock the mouse and enter fullscreen.
+  {
+    FullscreenNotificationObserver fullscreen_observer;
+    ASSERT_TRUE(ui_test_utils::SendKeyPressAndWait(
+        browser(), ui::VKEY_B, false, true, false, false,
+        chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+        content::NotificationService::AllSources()));
+    fullscreen_observer.Wait();
+  }
+  ASSERT_TRUE(IsFullscreenPermissionRequested());
+  ASSERT_TRUE(IsMouseLockPermissionRequested());
+
+  // Accept both, confirm mouse lock and fullscreen and no prompts.
+  AcceptCurrentFullscreenOrMouseLockRequest();
+  ASSERT_TRUE(IsMouseLocked());
+  ASSERT_TRUE(IsFullscreenForTabOrPending());
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+  ASSERT_FALSE(IsMouseLockPermissionRequested());
+
+  // Escape, confirm we are out of mouse lock and fullscreen with no prompts.
+  {
+    FullscreenNotificationObserver fullscreen_observer;
+    SendEscapeToFullscreenController();
+    fullscreen_observer.Wait();
+  }
+  ASSERT_FALSE(IsMouseLocked());
+  ASSERT_FALSE(IsFullscreenForTabOrPending());
+  ASSERT_FALSE(IsFullscreenPermissionRequested());
+  ASSERT_FALSE(IsMouseLockPermissionRequested());
 }
 
 // Helper method to be called by multiple tests.
diff --git a/chrome/browser/ui/cocoa/browser_window_controller.mm b/chrome/browser/ui/cocoa/browser_window_controller.mm
index 8c7546e..586a829 100644
--- a/chrome/browser/ui/cocoa/browser_window_controller.mm
+++ b/chrome/browser/ui/cocoa/browser_window_controller.mm
@@ -1983,7 +1983,16 @@
 
 - (void)updateFullscreenExitBubbleURL:(const GURL&)url
                            bubbleType:(FullscreenExitBubbleType)bubbleType {
-  [fullscreenExitBubbleController_.get() updateURL:url bubbleType:bubbleType];
+  fullscreenUrl_ = url;
+  fullscreenBubbleType_ = bubbleType;
+  if (bubbleType == FEB_TYPE_NONE) {
+    [self destroyFullscreenExitBubbleIfNecessary];
+  } else {
+    if (!fullscreenExitBubbleController_.get()) {
+      [self showFullscreenExitBubbleIfNecessary];
+    }
+    [fullscreenExitBubbleController_.get() updateURL:url bubbleType:bubbleType];
+  }
 }
 
 - (BOOL)isFullscreen {
diff --git a/chrome/browser/ui/cocoa/browser_window_controller_private.mm b/chrome/browser/ui/cocoa/browser_window_controller_private.mm
index dedd849..347ce958 100644
--- a/chrome/browser/ui/cocoa/browser_window_controller_private.mm
+++ b/chrome/browser/ui/cocoa/browser_window_controller_private.mm
@@ -791,23 +791,15 @@
 }
 
 - (void)showFullscreenExitBubbleIfNecessary {
-  if (!browser_->IsFullscreenForTabOrPending()) {
-    return;
-  }
-
   [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
 
+  [fullscreenExitBubbleController_ closeImmediately];
   fullscreenExitBubbleController_.reset(
       [[FullscreenExitBubbleController alloc]
           initWithOwner:self
                 browser:browser_.get()
                     url:fullscreenUrl_
              bubbleType:fullscreenBubbleType_]);
-  NSView* contentView = [[self window] contentView];
-  CGFloat maxWidth = NSWidth([contentView frame]);
-  CGFloat maxY = NSMaxY([[[self window] contentView] frame]);
-  [fullscreenExitBubbleController_
-      positionInWindowAtTop:maxY width:maxWidth];
   [fullscreenExitBubbleController_ showWindow];
 }
 
diff --git a/chrome/browser/ui/cocoa/fullscreen_exit_bubble_controller.mm b/chrome/browser/ui/cocoa/fullscreen_exit_bubble_controller.mm
index dbd1815..5683b1bc 100644
--- a/chrome/browser/ui/cocoa/fullscreen_exit_bubble_controller.mm
+++ b/chrome/browser/ui/cocoa/fullscreen_exit_bubble_controller.mm
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -120,10 +120,9 @@
     [self showButtons:NO];
     [self hideSoon];
   }
-  NSRect windowFrame = [owner_ window].frame;
   [tweaker_ tweakUI:info_bubble];
-  [self positionInWindowAtTop:NSHeight(windowFrame) width:NSWidth(windowFrame)];
   [[owner_ window] addChildWindow:info_bubble ordered:NSWindowAbove];
+  [owner_ layoutSubviews];
 
   [info_bubble orderFront:self];
 }
@@ -136,9 +135,11 @@
 
 - (void)positionInWindowAtTop:(CGFloat)maxY width:(CGFloat)maxWidth {
   NSRect windowFrame = [self window].frame;
+  NSRect ownerWindowFrame = [owner_ window].frame;
   NSPoint origin;
-  origin.x = (int)(maxWidth/2 - NSWidth(windowFrame)/2);
-  origin.y = maxY - NSHeight(windowFrame);
+  origin.x = ownerWindowFrame.origin.x +
+      (int)(NSWidth(ownerWindowFrame)/2 - NSWidth(windowFrame)/2);
+  origin.y = ownerWindowFrame.origin.y + maxY - NSHeight(windowFrame);
   [[self window] setFrameOrigin:origin];
 }
 
@@ -169,8 +170,7 @@
 
   // Relayout. A bit jumpy, but functional.
   [tweaker_ tweakUI:[self window]];
-  NSRect windowFrame = [owner_ window].frame;
-  [self positionInWindowAtTop:NSHeight(windowFrame) width:NSWidth(windowFrame)];
+  [owner_ layoutSubviews];
 }
 
 // Called when someone clicks on the embedded link.
@@ -271,6 +271,8 @@
 }
 
 - (NSString*)getLabelText {
+  if (bubbleType_ == FEB_TYPE_NONE)
+    return @"";
   return SysUTF16ToNSString(fullscreen_bubble::GetLabelTextForType(
           bubbleType_, url_, browser_->profile()->GetExtensionService()));
 }
diff --git a/chrome/browser/ui/fullscreen_controller.cc b/chrome/browser/ui/fullscreen_controller.cc
index 8f50aff..088437a 100644
--- a/chrome/browser/ui/fullscreen_controller.cc
+++ b/chrome/browser/ui/fullscreen_controller.cc
@@ -18,6 +18,7 @@
 #include "chrome/common/extensions/extension.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/user_metrics.h"
 #include "content/public/browser/web_contents.h"
 
@@ -34,6 +35,8 @@
     fullscreened_tab_(NULL),
     tab_caused_fullscreen_(false),
     tab_fullscreen_accepted_(false),
+    toggled_into_fullscreen_(false),
+    mouse_lock_tab_(NULL),
     mouse_lock_state_(MOUSELOCK_NOT_REQUESTED) {
 }
 
@@ -64,29 +67,39 @@
 }
 
 void FullscreenController::RequestToLockMouse(WebContents* tab,
-                                              bool /* user_gesture */) {
-  // TODO(scheib) user_gesture required for Mouse Lock in Windowed Mode.
-  // See http://crbug.com/107013, which will land in multiple patches.
-
+                                              bool user_gesture) {
   DCHECK(!IsMouseLocked());
+  NotifyMouseLockChange();
 
-  // Mouse Lock is only permitted when browser is in tab fullscreen.
-  if (!IsFullscreenForTabOrPending(tab)) {
+  // Must have a user gesture, or we must already be in tab fullscreen.
+  if (!user_gesture && !IsFullscreenForTabOrPending(tab)) {
     tab->GotResponseToLockMouseRequest(false);
     return;
   }
 
+  mouse_lock_tab_ = TabContentsWrapper::GetCurrentWrapperForContents(tab);
+  FullscreenExitBubbleType bubble_type = GetFullscreenExitBubbleType();
+
   switch (GetMouseLockSetting(tab->GetURL())) {
     case CONTENT_SETTING_ALLOW:
-      if (tab_fullscreen_accepted_) {
-        if (tab->GotResponseToLockMouseRequest(true))
-          mouse_lock_state_ = MOUSELOCK_ACCEPTED;
-      } else {
+      // If bubble already displaying buttons we must not lock the mouse yet,
+      // or it would prevent pressing those buttons. Instead, merge the request.
+      if (fullscreen_bubble::ShowButtonsForType(bubble_type)) {
         mouse_lock_state_ = MOUSELOCK_REQUESTED;
+      } else {
+        // Lock mouse.
+        if (tab->GotResponseToLockMouseRequest(true)) {
+          mouse_lock_state_ = MOUSELOCK_ACCEPTED;
+        } else {
+          mouse_lock_tab_ = NULL;
+          mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
+        }
       }
       break;
     case CONTENT_SETTING_BLOCK:
       tab->GotResponseToLockMouseRequest(false);
+      mouse_lock_tab_ = NULL;
+      mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
       break;
     case CONTENT_SETTING_ASK:
       mouse_lock_state_ = MOUSELOCK_REQUESTED;
@@ -142,7 +155,7 @@
         // mode), we need to switch back to "browser fullscreen" mode. In this
         // case, all we have to do is notifying the tab that it has exited "tab
         // fullscreen" mode.
-        NotifyTabOfFullscreenExitIfNecessary();
+        NotifyTabOfExitIfNecessary();
       }
     }
   }
@@ -169,24 +182,26 @@
 
 void FullscreenController::LostMouseLock() {
   mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
+  mouse_lock_tab_ = NULL;
+  NotifyMouseLockChange();
   UpdateFullscreenExitBubbleContent();
 }
 
 void FullscreenController::OnTabClosing(WebContents* web_contents) {
   if (IsFullscreenForTabOrPending(web_contents)) {
-    ExitTabbedFullscreenModeIfNecessary();
+    ExitTabFullscreenOrMouseLockIfNecessary();
     // The call to exit fullscreen may result in asynchronous notification of
     // fullscreen state change (e.g., on Linux). We don't want to rely on it
-    // to call NotifyTabOfFullscreenExitIfNecessary(), because at that point
-    // |fullscreen_tab_| may not be valid. Instead, we call it here to clean up
-    // tab fullscreen related state.
-    NotifyTabOfFullscreenExitIfNecessary();
+    // to call NotifyTabOfExitIfNecessary(), because at that point
+    // |fullscreened_tab_| may not be valid. Instead, we call it here to clean
+    // up tab fullscreen related state.
+    NotifyTabOfExitIfNecessary();
   }
 }
 
 void FullscreenController::OnTabDeactivated(TabContentsWrapper* contents) {
   if (contents == fullscreened_tab_)
-    ExitTabbedFullscreenModeIfNecessary();
+    ExitTabFullscreenOrMouseLockIfNecessary();
 }
 
 void FullscreenController::OnAcceptFullscreenPermission(
@@ -196,12 +211,13 @@
   bool fullscreen = false;
   fullscreen_bubble::PermissionRequestedByType(bubble_type, &fullscreen,
                                                &mouse_lock);
-  DCHECK(fullscreened_tab_);
-  DCHECK_NE(tab_fullscreen_accepted_, fullscreen);
+  DCHECK(!(fullscreen && tab_fullscreen_accepted_));
+  DCHECK(!(mouse_lock && IsMouseLocked()));
 
   HostContentSettingsMap* settings_map = profile_->GetHostContentSettingsMap();
   ContentSettingsPattern pattern = ContentSettingsPattern::FromURL(url);
-  if (mouse_lock) {
+
+  if (mouse_lock && !IsMouseLocked()) {
     DCHECK(IsMouseLockRequested());
     // TODO(markusheintz): We should allow patterns for all possible URLs here.
     if (pattern.IsValid()) {
@@ -210,11 +226,20 @@
           CONTENT_SETTINGS_TYPE_MOUSELOCK, std::string(),
           CONTENT_SETTING_ALLOW);
     }
-    mouse_lock_state_ =
-        fullscreened_tab_->web_contents()->GotResponseToLockMouseRequest(true) ?
-        MOUSELOCK_ACCEPTED : MOUSELOCK_NOT_REQUESTED;
+
+    if (mouse_lock_tab_ &&
+        mouse_lock_tab_->web_contents() &&
+        mouse_lock_tab_->web_contents()->GotResponseToLockMouseRequest(true)) {
+      mouse_lock_state_ = MOUSELOCK_ACCEPTED;
+    } else {
+      mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
+      mouse_lock_tab_ = NULL;
+    }
+    NotifyMouseLockChange();
   }
-  if (!tab_fullscreen_accepted_) {
+
+  if (fullscreen && !tab_fullscreen_accepted_) {
+    DCHECK(fullscreened_tab_);
     if (pattern.IsValid()) {
       settings_map->SetContentSetting(
           pattern, ContentSettingsPattern::Wildcard(),
@@ -232,19 +257,27 @@
   bool fullscreen = false;
   fullscreen_bubble::PermissionRequestedByType(bubble_type, &fullscreen,
                                                &mouse_lock);
-  DCHECK(fullscreened_tab_);
-  DCHECK_NE(tab_fullscreen_accepted_, fullscreen);
+  DCHECK(fullscreened_tab_ || mouse_lock_tab_);
+  DCHECK(!(fullscreen && tab_fullscreen_accepted_));
+  DCHECK(!(mouse_lock && IsMouseLocked()));
 
   if (mouse_lock) {
     DCHECK(IsMouseLockRequested());
     mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
-    fullscreened_tab_->web_contents()->GotResponseToLockMouseRequest(false);
+    if (mouse_lock_tab_ && mouse_lock_tab_->web_contents())
+      mouse_lock_tab_->web_contents()->GotResponseToLockMouseRequest(false);
+    mouse_lock_tab_ = NULL;
+    NotifyMouseLockChange();
+
+    // UpdateFullscreenExitBubbleContent() must be called, but to avoid
+    // duplicate calls we do so only if not adjusting the fullscreen state
+    // below, which also calls UpdateFullscreenExitBubbleContent().
     if (!fullscreen)
       UpdateFullscreenExitBubbleContent();
   }
 
   if (fullscreen)
-    ExitTabbedFullscreenModeIfNecessary();
+    ExitTabFullscreenOrMouseLockIfNecessary();
 }
 
 void FullscreenController::WindowFullscreenStateChanged() {
@@ -257,7 +290,7 @@
   exiting_fullscreen = !window_->IsFullscreen();
 #endif
   if (exiting_fullscreen)
-    NotifyTabOfFullscreenExitIfNecessary();
+    NotifyTabOfExitIfNecessary();
   if (exiting_fullscreen)
     window_->GetDownloadShelf()->Unhide();
   else
@@ -265,47 +298,72 @@
 }
 
 bool FullscreenController::HandleUserPressedEscape() {
-  if (!IsFullscreenForTabOrPending())
-    return false;
-  ExitTabbedFullscreenModeIfNecessary();
-  return true;
+  if (IsFullscreenForTabOrPending() ||
+      IsMouseLocked() || IsMouseLockRequested()) {
+    ExitTabFullscreenOrMouseLockIfNecessary();
+    return true;
+  }
+
+  return false;
 }
 
 FullscreenController::~FullscreenController() {}
 
-void FullscreenController::NotifyTabOfFullscreenExitIfNecessary() {
+void FullscreenController::NotifyTabOfExitIfNecessary() {
   if (fullscreened_tab_) {
     RenderViewHost* rvh =
         fullscreened_tab_->web_contents()->GetRenderViewHost();
     fullscreened_tab_ = NULL;
     tab_caused_fullscreen_ = false;
     tab_fullscreen_accepted_ = false;
-    mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
     if (rvh)
       rvh->ExitFullscreen();
-  } else {
-    DCHECK_EQ(mouse_lock_state_, MOUSELOCK_NOT_REQUESTED);
+  }
+
+  if (mouse_lock_tab_) {
+    WebContents* web_contents = mouse_lock_tab_->web_contents();
+    if (IsMouseLockRequested()) {
+      web_contents->GotResponseToLockMouseRequest(false);
+    } else if (web_contents->GetRenderViewHost() &&
+               web_contents->GetRenderViewHost()->GetView()) {
+      web_contents->GetRenderViewHost()->GetView()->UnlockMouse();
+    }
+    mouse_lock_tab_ = NULL;
+    mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
   }
 
   UpdateFullscreenExitBubbleContent();
 }
 
-void FullscreenController::ExitTabbedFullscreenModeIfNecessary() {
+void FullscreenController::ExitTabFullscreenOrMouseLockIfNecessary() {
   if (tab_caused_fullscreen_)
     ToggleFullscreenMode();
   else
-    NotifyTabOfFullscreenExitIfNecessary();
+    NotifyTabOfExitIfNecessary();
 }
 
 void FullscreenController::UpdateFullscreenExitBubbleContent() {
   GURL url;
   if (fullscreened_tab_)
     url = fullscreened_tab_->web_contents()->GetURL();
+  else if (mouse_lock_tab_)
+    url = mouse_lock_tab_->web_contents()->GetURL();
   else if (!extension_caused_fullscreen_.is_empty())
     url = extension_caused_fullscreen_;
 
-  window_->UpdateFullscreenExitBubbleContent(url,
-                                             GetFullscreenExitBubbleType());
+  FullscreenExitBubbleType bubble_type = GetFullscreenExitBubbleType();
+
+  // If bubble displays buttons, unlock mouse to allow pressing them.
+  if (fullscreen_bubble::ShowButtonsForType(bubble_type) &&
+      IsMouseLocked() &&
+      mouse_lock_tab_->web_contents()) {
+    WebContents* web_contents = mouse_lock_tab_->web_contents();
+    if (web_contents && web_contents->GetRenderViewHost() &&
+        web_contents->GetRenderViewHost()->GetView())
+      web_contents->GetRenderViewHost()->GetView()->UnlockMouse();
+  }
+
+  window_->UpdateFullscreenExitBubbleContent(url, bubble_type);
 }
 
 void FullscreenController::NotifyFullscreenChange() {
@@ -315,24 +373,55 @@
       content::NotificationService::NoDetails());
 }
 
+void FullscreenController::NotifyMouseLockChange() {
+  content::NotificationService::current()->Notify(
+      chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
+      content::Source<FullscreenController>(this),
+      content::NotificationService::NoDetails());
+}
+
 FullscreenExitBubbleType FullscreenController::GetFullscreenExitBubbleType()
     const {
-  if (!fullscreened_tab_) {
-    DCHECK_EQ(MOUSELOCK_NOT_REQUESTED, mouse_lock_state_);
-    return !extension_caused_fullscreen_.is_empty() ?
-        FEB_TYPE_BROWSER_EXTENSION_FULLSCREEN_EXIT_INSTRUCTION :
-        FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION;
+  // In kiosk mode we always want to be fullscreen and do not want to show
+  // exit instructions for browser mode fullscreen.
+  bool kiosk = false;
+#if !defined(OS_MACOSX)  // Kiosk mode not available on Mac.
+  kiosk = CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
+#endif
+
+  if (fullscreened_tab_) {
+    if (tab_fullscreen_accepted_) {
+      if (IsMouseLocked()) {
+        return FEB_TYPE_FULLSCREEN_MOUSELOCK_EXIT_INSTRUCTION;
+      } else if (IsMouseLockRequested()) {
+        return FEB_TYPE_MOUSELOCK_BUTTONS;
+      } else {
+        return FEB_TYPE_FULLSCREEN_EXIT_INSTRUCTION;
+      }
+    } else {  // Full screen not yet accepted.
+      if (IsMouseLockRequested()) {
+        return FEB_TYPE_FULLSCREEN_MOUSELOCK_BUTTONS;
+      } else {
+        return FEB_TYPE_FULLSCREEN_BUTTONS;
+      }
+    }
+  } else {  // Not tab full screen.
+    if (IsMouseLocked()) {
+      return FEB_TYPE_MOUSELOCK_EXIT_INSTRUCTION;
+    } else if (IsMouseLockRequested()) {
+      return FEB_TYPE_MOUSELOCK_BUTTONS;
+    } else {
+      if (!extension_caused_fullscreen_.is_empty()) {
+        return FEB_TYPE_BROWSER_EXTENSION_FULLSCREEN_EXIT_INSTRUCTION;
+      } else if (toggled_into_fullscreen_ && !kiosk) {
+        return FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION;
+      } else {
+        return FEB_TYPE_NONE;
+      }
+    }
   }
-  if (fullscreened_tab_ && !tab_fullscreen_accepted_) {
-    DCHECK_NE(MOUSELOCK_ACCEPTED, mouse_lock_state_);
-    return mouse_lock_state_ == MOUSELOCK_REQUESTED ?
-        FEB_TYPE_FULLSCREEN_MOUSELOCK_BUTTONS : FEB_TYPE_FULLSCREEN_BUTTONS;
-  }
-  if (mouse_lock_state_ == MOUSELOCK_REQUESTED)
-    return FEB_TYPE_MOUSELOCK_BUTTONS;
-  return mouse_lock_state_ == MOUSELOCK_ACCEPTED ?
-      FEB_TYPE_FULLSCREEN_MOUSELOCK_EXIT_INSTRUCTION :
-      FEB_TYPE_FULLSCREEN_EXIT_INSTRUCTION;
+  NOTREACHED();
+  return FEB_TYPE_NONE;
 }
 
 ContentSetting
@@ -357,17 +446,18 @@
 
 #if defined(OS_MACOSX)
 void FullscreenController::TogglePresentationModeInternal(bool for_tab) {
-  bool entering_fullscreen = !window_->InPresentationMode();
+  toggled_into_fullscreen_ = !window_->InPresentationMode();
   GURL url;
   if (for_tab) {
     url = browser_->GetSelectedWebContents()->GetURL();
-    tab_fullscreen_accepted_ = entering_fullscreen &&
+    tab_fullscreen_accepted_ = toggled_into_fullscreen_ &&
         GetFullscreenSetting(url) == CONTENT_SETTING_ALLOW;
   }
-  if (entering_fullscreen)
+  if (toggled_into_fullscreen_)
     window_->EnterPresentationMode(url, GetFullscreenExitBubbleType());
   else
     window_->ExitPresentationMode();
+  UpdateFullscreenExitBubbleContent();
 
   // WindowFullscreenStateChanged will be called by BrowserWindowController
   // when the transition completes.
@@ -376,11 +466,11 @@
 
 // TODO(koz): Change |for_tab| to an enum.
 void FullscreenController::ToggleFullscreenModeInternal(bool for_tab) {
-  bool entering_fullscreen = !window_->IsFullscreen();
+  toggled_into_fullscreen_ = !window_->IsFullscreen();
 
-#if !defined(OS_MACOSX)
-  // In kiosk mode, we always want to be fullscreen. When the browser first
-  // starts we're not yet fullscreen, so let the initial toggle go through.
+ // In kiosk mode, we always want to be fullscreen. When the browser first
+ // starts we're not yet fullscreen, so let the initial toggle go through.
+#if !defined(OS_MACOSX)  // Kiosk mode not available on Mac.
   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode) &&
       window_->IsFullscreen())
     return;
@@ -389,19 +479,20 @@
   GURL url;
   if (for_tab) {
     url = browser_->GetSelectedWebContents()->GetURL();
-    tab_fullscreen_accepted_ = entering_fullscreen &&
+    tab_fullscreen_accepted_ = toggled_into_fullscreen_ &&
         GetFullscreenSetting(url) == CONTENT_SETTING_ALLOW;
   } else {
     if (!extension_caused_fullscreen_.is_empty())
       url = extension_caused_fullscreen_;
     content::RecordAction(UserMetricsAction("ToggleFullscreen"));
   }
-  if (entering_fullscreen) {
+  if (toggled_into_fullscreen_) {
     window_->EnterFullscreen(url, GetFullscreenExitBubbleType());
   } else {
     window_->ExitFullscreen();
     extension_caused_fullscreen_ = GURL();
   }
+  UpdateFullscreenExitBubbleContent();
 
   // Once the window has become fullscreen it'll call back to
   // WindowFullscreenStateChanged(). We don't do this immediately as
diff --git a/chrome/browser/ui/fullscreen_controller.h b/chrome/browser/ui/fullscreen_controller.h
index ea79a51..99ca65176 100644
--- a/chrome/browser/ui/fullscreen_controller.h
+++ b/chrome/browser/ui/fullscreen_controller.h
@@ -92,13 +92,15 @@
 
   virtual ~FullscreenController();
 
-  // Notifies the tab that it has been forced out of fullscreen mode if
-  // necessary.
-  void NotifyTabOfFullscreenExitIfNecessary();
-  // Make the current tab exit fullscreen mode if it is in it.
-  void ExitTabbedFullscreenModeIfNecessary();
+  // Notifies the tab that it has been forced out of fullscreen and mouse lock
+  // mode if necessary.
+  void NotifyTabOfExitIfNecessary();
+
+  // Make the current tab exit fullscreen mode or mouse lock if it is in it.
+  void ExitTabFullscreenOrMouseLockIfNecessary();
   void UpdateFullscreenExitBubbleContent();
   void NotifyFullscreenChange();
+  void NotifyMouseLockChange();
   ContentSetting GetFullscreenSetting(const GURL& url) const;
   ContentSetting GetMouseLockSetting(const GURL& url) const;
 
@@ -125,6 +127,12 @@
   // clicking the allow button on the fullscreen infobar.
   bool tab_fullscreen_accepted_;
 
+  // True if this controller has toggled into tab OR browser fullscreen.
+  bool toggled_into_fullscreen_;
+
+  // Wrapper for current tab requesting or currently in mouse lock.
+  TabContentsWrapper* mouse_lock_tab_;
+
   MouseLockState mouse_lock_state_;
 
   DISALLOW_COPY_AND_ASSIGN(FullscreenController);
diff --git a/chrome/browser/ui/gtk/browser_window_gtk.cc b/chrome/browser/ui/gtk/browser_window_gtk.cc
index 85ba20ee72..60fe1a7 100644
--- a/chrome/browser/ui/gtk/browser_window_gtk.cc
+++ b/chrome/browser/ui/gtk/browser_window_gtk.cc
@@ -926,10 +926,19 @@
 }
 
 void BrowserWindowGtk::UpdateFullscreenExitBubbleContent(
-      const GURL& url,
+     const GURL& url,
       FullscreenExitBubbleType bubble_type) {
-  if (fullscreen_exit_bubble_.get())
-    fullscreen_exit_bubble_->UpdateContent(url, bubble_type);
+  if (bubble_type == FEB_TYPE_NONE) {
+   fullscreen_exit_bubble_.reset();
+  } else if (fullscreen_exit_bubble_.get()) {
+   fullscreen_exit_bubble_->UpdateContent(url, bubble_type);
+  } else {
+    fullscreen_exit_bubble_.reset(new FullscreenExitBubbleGtk(
+        GTK_FLOATING_CONTAINER(render_area_floating_container_),
+        browser(),
+        url,
+        bubble_type));
+  }
 }
 
 void BrowserWindowGtk::ExitFullscreen() {
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 79846e2..4a3e84e1 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -768,8 +768,14 @@
 void BrowserView::UpdateFullscreenExitBubbleContent(
     const GURL& url,
     FullscreenExitBubbleType bubble_type) {
-  if (fullscreen_bubble_.get())
+  if (bubble_type == FEB_TYPE_NONE) {
+    fullscreen_bubble_.reset();
+  } else if (fullscreen_bubble_.get()) {
     fullscreen_bubble_->UpdateContent(url, bubble_type);
+  } else {
+    fullscreen_bubble_.reset(new FullscreenExitBubbleViews(
+        GetWidget(), browser_.get(), url, bubble_type));
+  }
 }
 
 bool BrowserView::IsFullscreen() const {
diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h
index b513a67d..edb7d9e 100644
--- a/chrome/common/chrome_notification_types.h
+++ b/chrome/common/chrome_notification_types.h
@@ -1050,6 +1050,10 @@
   // Sent when the browser enters or exits fullscreen mode.
   NOTIFICATION_FULLSCREEN_CHANGED,
 
+  // Sent when the FullscreenController changes, confirms, or denies mouse lock.
+  // The source is the browser's FullscreenController, no details.
+  NOTIFICATION_MOUSE_LOCK_CHANGED,
+
   // Sent by the PluginPrefs when there is a change of plugin enable/disable
   // status. The source is the profile.
   NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED,
diff --git a/chrome/test/data/fullscreen_mouselock/fullscreen_mouselock.html b/chrome/test/data/fullscreen_mouselock/fullscreen_mouselock.html
index 1694335..298f3b9 100644
--- a/chrome/test/data/fullscreen_mouselock/fullscreen_mouselock.html
+++ b/chrome/test/data/fullscreen_mouselock/fullscreen_mouselock.html
@@ -96,19 +96,32 @@
 </head>
 <body title="This tooltip should not be shown if the mouse is locked.">
   <div id="container">
-    <button id="enterFullscreen" onclick="enterFullscreen();">enterFullscreen()</button><br>
-    <button id="exitFullscreen" onclick="exitFullscreen();">exitFullscreen()</button><br>
-    <button id="lockMouse1" onclick="lockMouse1();">lockMouse1()</button><br>
-    <button id="lockMouse2" onclick="lockMouse2();">lockMouse2()</button><br>
-    <button id="delayedLockMouse1" onclick="delayedLockMouse1();">delayedLockMouse1()</button><br>
+    <button id="enterFullscreen" onclick="enterFullscreen();">enterFullscreen() [f]</button><br>
+    <button id="exitFullscreen" onclick="exitFullscreen();">exitFullscreen() [x]</button><br>
+    <button id="lockMouse1" onclick="lockMouse1();">lockMouse1() [1]</button><br>
+    <button id="lockMouse2" onclick="lockMouse2();">lockMouse2() [2]</button><br>
+    <button id="delayedLockMouse1" onclick="delayedLockMouse1();">delayedLockMouse1() [d]</button><br>
     <button id="spamLockMouse2" onclick="spamLockMouse2();">spamLockMouse2()</button><br>
-    <button id="unlockMouse" onclick="unlockMouse();">unlockMouse()</button><br>
-    <button id="enterFullscreenAndLockMouse1" onclick="enterFullscreenAndLockMouse1()">enterFullscreenAndLockMouse1()</button><br>
-    <button id="lockMouse1AndEnterFullscreen" onclick="lockMouse1AndEnterFullscreen()">lockMouse1AndEnterFullscreen()</button><br>
+    <button id="unlockMouse" onclick="unlockMouse();">unlockMouse() [u]</button><br>
+    <button id="enterFullscreenAndLockMouse1" onclick="enterFullscreenAndLockMouse1()">enterFullscreenAndLockMouse1() [b]</button><br>
+    <button id="lockMouse1AndEnterFullscreen" onclick="lockMouse1AndEnterFullscreen()">lockMouse1AndEnterFullscreen() [B]</button><br>
     <div id="lockTarget1">lockTarget1</div>
     <div id="lockTarget2">lockTarget2</div>
   </div>
   This text is outside of the container that is made fullscreen. This text should not be visible when fullscreen.
 </body>
-</html>
+<script>
+document.body.onkeypress = function (e) {
+  switch (String.fromCharCode(e.charCode)) {
+  case "f": enterFullscreen(); break;
+  case "x": exitFullscreen(); break;
+  case "1": lockMouse1(); break;
+  case "2": lockMouse2(); break;
+  case "d": delayedLockMouse1(); break;
+  case "u": unlockMouse(); break;
+  case "b": enterFullscreenAndLockMouse1(); break;
+  case "B": lockMouse1AndEnterFullscreen(); break;
+  }
+}
+</script>
 </html>