summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/app/generated_resources.grd10
-rw-r--r--chrome/app/nibs/ConfirmQuitPanel.xib333
-rw-r--r--chrome/browser/about_flags.cc7
-rw-r--r--chrome/browser/app_controller_mac.mm93
-rw-r--r--chrome/browser/cocoa/confirm_quit_panel_controller.h24
-rw-r--r--chrome/browser/cocoa/confirm_quit_panel_controller.mm69
-rw-r--r--chrome/browser/cocoa/confirm_quit_panel_controller_unittest.mm26
-rw-r--r--chrome/chrome_browser.gypi3
-rw-r--r--chrome/chrome_dll.gypi1
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/chrome_switches.cc3
-rw-r--r--chrome/common/chrome_switches.h1
12 files changed, 571 insertions, 0 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 1981024..90bb121 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4100,6 +4100,12 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_FLAGS_DNS_SERVER_DESCRIPTION" desc="">
User specified DNS server, which Chrome will use for DNS resolutions rather than the system defaults.
</message>
+ <message name="IDS_FLAGS_CONFIRM_TO_QUIT_NAME" desc="Name of the 'Confirm to Quit' lab.">
+ Confirm to Quit
+ </message>
+ <message name="IDS_FLAGS_CONFIRM_TO_QUIT_DESCRIPTION" desc="Description of the 'Confirm to Quit' lab.">
+ Receive a prompt before quitting to confirm the action.
+ </message>
<!-- Instant -->
<message name="IDS_INSTANT_OPT_IN_ENABLE" desc="Button shown in the omnibox dropdown for enabling instant">
@@ -4629,6 +4635,10 @@ Keep your key file in a safe place. You will need it to create new versions of y
Cancel Anyway
</message>
+ <!-- Confirm to quit panel -->
+ <message name="IDS_CONFIRM_TO_QUIT_DESCRIPTION" desc="Instructions for how the user should confirm quitting.">
+ Hold ⌘Q to Quit.
+ </message>
<!-- Importer Lock Dialog -->
<message name="IDS_IMPORTER_LOCK_TITLE" desc="Dialog title for importer lock dialog">
diff --git a/chrome/app/nibs/ConfirmQuitPanel.xib b/chrome/app/nibs/ConfirmQuitPanel.xib
new file mode 100644
index 0000000..e98882f
--- /dev/null
+++ b/chrome/app/nibs/ConfirmQuitPanel.xib
@@ -0,0 +1,333 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
+ <data>
+ <int key="IBDocument.SystemTarget">1060</int>
+ <string key="IBDocument.SystemVersion">10F569</string>
+ <string key="IBDocument.InterfaceBuilderVersion">804</string>
+ <string key="IBDocument.AppKitVersion">1038.29</string>
+ <string key="IBDocument.HIToolboxVersion">461.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="NS.object.0">804</string>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <integer value="2"/>
+ </object>
+ <object class="NSArray" key="IBDocument.PluginDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys" id="0">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSCustomObject" id="1001">
+ <string key="NSClassName">ConfirmQuitPanelController</string>
+ </object>
+ <object class="NSCustomObject" id="1003">
+ <string key="NSClassName">FirstResponder</string>
+ </object>
+ <object class="NSCustomObject" id="1004">
+ <string key="NSClassName">NSApplication</string>
+ </object>
+ <object class="NSWindowTemplate" id="417776770">
+ <int key="NSWindowStyleMask">8337</int>
+ <int key="NSWindowBacking">2</int>
+ <string key="NSWindowRect">{{235, 336}, {365, 69}}</string>
+ <int key="NSWTFlags">611844096</int>
+ <string key="NSWindowTitle">^IDS_PRODUCT_NAME</string>
+ <string key="NSWindowClass">NSPanel</string>
+ <nil key="NSViewClass"/>
+ <string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string>
+ <object class="NSView" key="NSWindowView" id="632563843">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">256</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSTextField" id="369287154">
+ <reference key="NSNextResponder" ref="632563843"/>
+ <int key="NSvFlags">298</int>
+ <string key="NSFrame">{{17, 20}, {331, 29}}</string>
+ <reference key="NSSuperview" ref="632563843"/>
+ <bool key="NSEnabled">YES</bool>
+ <object class="NSTextFieldCell" key="NSCell" id="354055869">
+ <int key="NSCellFlags">68288064</int>
+ <int key="NSCellFlags2">138413056</int>
+ <string key="NSContents">^IDS_CONFIRM_TO_QUIT_DESCRIPTION</string>
+ <object class="NSFont" key="NSSupport">
+ <string key="NSName">LucidaGrande-Bold</string>
+ <double key="NSSize">24</double>
+ <int key="NSfFlags">16</int>
+ </object>
+ <reference key="NSControlView" ref="369287154"/>
+ <object class="NSColor" key="NSBackgroundColor">
+ <int key="NSColorSpace">6</int>
+ <string key="NSCatalogName">System</string>
+ <string key="NSColorName">controlColor</string>
+ <object class="NSColor" key="NSColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
+ </object>
+ </object>
+ <object class="NSColor" key="NSTextColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MSAxIDEAA</bytes>
+ </object>
+ </object>
+ </object>
+ </object>
+ <string key="NSFrameSize">{365, 69}</string>
+ <reference key="NSSuperview"/>
+ </object>
+ <string key="NSScreenRect">{{0, 0}, {1920, 1178}}</string>
+ <string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string>
+ </object>
+ <object class="NSCustomObject" id="421673775">
+ <string key="NSClassName">ChromeUILocalizer</string>
+ </object>
+ </object>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <object class="NSMutableArray" key="connectionRecords">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">window</string>
+ <reference key="source" ref="1001"/>
+ <reference key="destination" ref="417776770"/>
+ </object>
+ <int key="connectionID">8</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">delegate</string>
+ <reference key="source" ref="417776770"/>
+ <reference key="destination" ref="1001"/>
+ </object>
+ <int key="connectionID">9</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">owner_</string>
+ <reference key="source" ref="421673775"/>
+ <reference key="destination" ref="1001"/>
+ </object>
+ <int key="connectionID">11</int>
+ </object>
+ </object>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <object class="NSArray" key="orderedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <reference key="object" ref="0"/>
+ <reference key="children" ref="1000"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="1001"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="1003"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">First Responder</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-3</int>
+ <reference key="object" ref="1004"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">Application</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">1</int>
+ <reference key="object" ref="417776770"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="632563843"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">2</int>
+ <reference key="object" ref="632563843"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="369287154"/>
+ </object>
+ <reference key="parent" ref="417776770"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">3</int>
+ <reference key="object" ref="369287154"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="354055869"/>
+ </object>
+ <reference key="parent" ref="632563843"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">6</int>
+ <reference key="object" ref="354055869"/>
+ <reference key="parent" ref="369287154"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">10</int>
+ <reference key="object" ref="421673775"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="flattenedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>-1.IBPluginDependency</string>
+ <string>-2.IBPluginDependency</string>
+ <string>-3.IBPluginDependency</string>
+ <string>1.IBEditorWindowLastContentRect</string>
+ <string>1.IBPluginDependency</string>
+ <string>1.IBWindowTemplateEditedContentRect</string>
+ <string>1.NSWindowTemplate.visibleAtLaunch</string>
+ <string>10.IBPluginDependency</string>
+ <string>2.IBPluginDependency</string>
+ <string>3.IBPluginDependency</string>
+ <string>3.IBViewBoundsToFrameTransform</string>
+ <string>6.IBPluginDependency</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>{{787, 702}, {365, 69}}</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>{{787, 702}, {365, 69}}</string>
+ <boolean value="NO"/>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <object class="NSAffineTransform">
+ <bytes key="NSTransformStruct">P4AAAL+AAABA4AAAwhgAAA</bytes>
+ </object>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="unlocalizedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
+ <nil key="activeLocalization"/>
+ <object class="NSMutableDictionary" key="localizations">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ </object>
+ <nil key="sourceID"/>
+ <int key="maxID">11</int>
+ </object>
+ <object class="IBClassDescriber" key="IBDocument.Classes">
+ <object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.1+">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">ChromeUILocalizer</string>
+ <string key="superclassName">GTMUILocalizer</string>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBDocumentRelativeSource</string>
+ <string key="minorKey">../../browser/cocoa/ui_localizer.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">ConfirmQuitPanelController</string>
+ <string key="superclassName">NSWindowController</string>
+ <object class="NSMutableDictionary" key="outlets">
+ <string key="NS.key.0">infoField_</string>
+ <string key="NS.object.0">NSTextField</string>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <string key="NS.key.0">infoField_</string>
+ <object class="IBToOneOutletInfo" key="NS.object.0">
+ <string key="name">infoField_</string>
+ <string key="candidateClassName">NSTextField</string>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBDocumentRelativeSource</string>
+ <string key="minorKey">../../browser/cocoa/confirm_quit_panel_controller.h</string>
+ </object>
+ </object>
+ <object class="IBPartialClassDescription">
+ <string key="className">GTMUILocalizer</string>
+ <string key="superclassName">NSObject</string>
+ <object class="NSMutableDictionary" key="outlets">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>otherObjectToLocalize_</string>
+ <string>owner_</string>
+ <string>yetAnotherObjectToLocalize_</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>otherObjectToLocalize_</string>
+ <string>owner_</string>
+ <string>yetAnotherObjectToLocalize_</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBToOneOutletInfo">
+ <string key="name">otherObjectToLocalize_</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">owner_</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">yetAnotherObjectToLocalize_</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBDocumentRelativeSource</string>
+ <string key="minorKey">../../../third_party/GTM/AppKit/GTMUILocalizer.h</string>
+ </object>
+ </object>
+ </object>
+ </object>
+ <int key="IBDocument.localizationMode">0</int>
+ <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
+ <integer value="3000" key="NS.object.0"/>
+ </object>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <nil key="IBDocument.LastKnownRelativeProjectPath"/>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ </data>
+</archive>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 8f552e5..5b5fd7ce 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -150,6 +150,13 @@ const Experiment kExperiments[] = {
kOsLinux,
switches::kDnsServer
},
+ {
+ "confirm-to-quit", // Do not change; see above.
+ IDS_FLAGS_CONFIRM_TO_QUIT_NAME,
+ IDS_FLAGS_CONFIRM_TO_QUIT_DESCRIPTION,
+ kOsMac,
+ switches::kEnableConfirmToQuit
+ },
};
const Experiment* experiments = kExperiments;
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 22de42f..aa5af1f 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -27,6 +27,7 @@
#import "chrome/browser/cocoa/browser_window_controller.h"
#import "chrome/browser/cocoa/bug_report_window_controller.h"
#import "chrome/browser/cocoa/clear_browsing_data_controller.h"
+#import "chrome/browser/cocoa/confirm_quit_panel_controller.h"
#import "chrome/browser/cocoa/encoding_menu_controller_delegate_mac.h"
#import "chrome/browser/cocoa/history_menu_bridge.h"
#import "chrome/browser/cocoa/import_settings_dialog.h"
@@ -242,6 +243,12 @@ void RecordLastRunAppBundlePath() {
// them in, but I'm not sure about UX; we'd also want to disable other things
// though.) http://crbug.com/40861
+ // Check if the user really wants to quit by employing the confirm-to-quit
+ // mechanism.
+ if (!browser_shutdown::IsTryingToQuit() &&
+ [self applicationShouldTerminate:app] != NSTerminateNow)
+ return NO;
+
size_t num_browsers = BrowserList::size();
// Give any print jobs in progress time to finish.
@@ -267,6 +274,92 @@ void RecordLastRunAppBundlePath() {
}
}
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app {
+ // Check if the experiment is enabled.
+ const CommandLine* commandLine(CommandLine::ForCurrentProcess());
+ if (!commandLine->HasSwitch(switches::kEnableConfirmToQuit))
+ return NSTerminateNow;
+
+ // If the application is going to terminate as the result of a Cmd+Q
+ // invocation, use the special sauce to prevent accidental quitting.
+ // http://dev.chromium.org/developers/design-documents/confirm-to-quit-experiment
+ NSEvent* currentEvent = [app currentEvent];
+ if ([currentEvent type] == NSKeyDown) {
+ ConfirmQuitPanelController* quitPanel =
+ [[ConfirmQuitPanelController alloc] init]; // Releases self.
+ // Show the info panel that explains what the user must to do confirm quit.
+ [quitPanel showWindow:self];
+
+ // How long the user must hold down Cmd+Q to confirm the quit.
+ const NSTimeInterval kTimeToConfirmQuit = 1.5;
+ // Leeway between the |targetDate| and the current time that will confirm a
+ // quit.
+ const NSTimeInterval kTimeDeltaFuzzFactor = 1.0;
+ // Duration of the window fade out animation.
+ const NSTimeInterval kWindowFadeAnimationDuration = 0.2;
+
+ // Spin a nested run loop until the |targetDate| is reached or a KeyUp event
+ // is sent.
+ NSDate* targetDate =
+ [NSDate dateWithTimeIntervalSinceNow:kTimeToConfirmQuit];
+ BOOL willQuit = NO;
+ NSEvent* nextEvent = nil;
+ do {
+ // Dequeue events until a key up is received.
+ nextEvent = [app nextEventMatchingMask:NSKeyUpMask
+ untilDate:nil
+ inMode:NSEventTrackingRunLoopMode
+ dequeue:YES];
+
+ // Wait for the time expiry to happen. Once past the hold threshold,
+ // commit to quitting and hide all the open windows.
+ if (!willQuit) {
+ NSDate* now = [NSDate date];
+ NSTimeInterval difference = [targetDate timeIntervalSinceDate:now];
+ if (difference < kTimeDeltaFuzzFactor) {
+ willQuit = YES;
+
+ // At this point, the quit has been confirmed and windows should all
+ // fade out to convince the user to release the key combo to finalize
+ // the quit.
+ [NSAnimationContext beginGrouping];
+ [[NSAnimationContext currentContext] setDuration:
+ kWindowFadeAnimationDuration];
+ for (NSWindow* aWindow in [app windows]) {
+ // Windows that are set to animate and have a delegate do not
+ // expect to be animated by other things and could result in an
+ // invalid state. If a window is set up like so, just force the
+ // alpha value to 0. Otherwise, animate all pretty and stuff.
+ if (![[aWindow animationForKey:@"alphaValue"] delegate]) {
+ [[aWindow animator] setAlphaValue:0.0];
+ } else {
+ [aWindow setAlphaValue:0.0];
+ }
+ }
+ [NSAnimationContext endGrouping];
+ }
+ }
+ } while (!nextEvent);
+
+ // The user has released the key combo. Discard any events (i.e. the
+ // repeated KeyDown Cmd+Q).
+ [app discardEventsMatchingMask:NSAnyEventMask beforeEvent:nextEvent];
+ if (willQuit) {
+ // The user held down the combination long enough that quitting should
+ // happen.
+ return NSTerminateNow;
+ } else {
+ // Slowly fade the confirm window out in case the user doesn't
+ // understand what they have to do to quit.
+ [quitPanel dismissPanel];
+ return NSTerminateCancel;
+ }
+ } // if event type is KeyDown
+
+ // Default case: terminate.
+ return NSTerminateNow;
+}
+
// Called when the app is shutting down. Clean-up as appropriate.
- (void)applicationWillTerminate:(NSNotification*)aNotification {
NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
diff --git a/chrome/browser/cocoa/confirm_quit_panel_controller.h b/chrome/browser/cocoa/confirm_quit_panel_controller.h
new file mode 100644
index 0000000..c518222
--- /dev/null
+++ b/chrome/browser/cocoa/confirm_quit_panel_controller.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2010 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.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/cocoa_protocols_mac.h"
+
+// The ConfirmQuitPanelController manages the black HUD window that tells users
+// to "Hold Cmd+Q to Quit".
+@interface ConfirmQuitPanelController : NSWindowController<NSWindowDelegate> {
+}
+
+// Designated initializer. Loads window from NIB but does not show it.
+- (id)init;
+
+// Shows the window.
+- (void)showWindow:(id)sender;
+
+// If the user did not confirm quit, send this message to give the user
+// instructions on how to quit.
+- (void)dismissPanel;
+
+@end
diff --git a/chrome/browser/cocoa/confirm_quit_panel_controller.mm b/chrome/browser/cocoa/confirm_quit_panel_controller.mm
new file mode 100644
index 0000000..5b624dd
--- /dev/null
+++ b/chrome/browser/cocoa/confirm_quit_panel_controller.mm
@@ -0,0 +1,69 @@
+// Copyright (c) 2010 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.
+
+#import <Cocoa/Cocoa.h>
+#import <QuartzCore/QuartzCore.h>
+
+#include "base/logging.h"
+#include "base/mac_util.h"
+#include "base/scoped_nsobject.h"
+#import "chrome/browser/cocoa/confirm_quit_panel_controller.h"
+
+@interface ConfirmQuitPanelController (Private)
+- (void)animateFadeOut;
+@end
+
+@implementation ConfirmQuitPanelController
+
+- (id)init {
+ NSString* nibPath =
+ [mac_util::MainAppBundle() pathForResource:@"ConfirmQuitPanel"
+ ofType:@"nib"];
+ if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
+ }
+ return self;
+}
+
+- (void)awakeFromNib {
+ DCHECK([self window]);
+ DCHECK_EQ(self, [[self window] delegate]);
+}
+
+- (void)windowWillClose:(NSNotification*)notif {
+ // Release all animations because CAAnimation retains its delegate (self),
+ // which will cause a retain cycle. Break it!
+ [[self window] setAnimations:[NSDictionary dictionary]];
+ [self autorelease];
+}
+
+- (void)showWindow:(id)sender {
+ [[self window] center];
+ [[self window] setAlphaValue:1.0];
+ [super showWindow:sender];
+}
+
+- (void)dismissPanel {
+ [self performSelector:@selector(animateFadeOut)
+ withObject:nil
+ afterDelay:1.0];
+}
+
+- (void)animateFadeOut {
+ NSWindow* window = [self window];
+ scoped_nsobject<CAAnimation> animation(
+ [[window animationForKey:@"alphaValue"] copy]);
+ [animation setDelegate:self];
+ [animation setDuration:0.2];
+ NSMutableDictionary* dictionary =
+ [NSMutableDictionary dictionaryWithDictionary:[window animations]];
+ [dictionary setObject:animation forKey:@"alphaValue"];
+ [window setAnimations:dictionary];
+ [[window animator] setAlphaValue:0.0];
+}
+
+- (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag {
+ [self close];
+}
+
+@end
diff --git a/chrome/browser/cocoa/confirm_quit_panel_controller_unittest.mm b/chrome/browser/cocoa/confirm_quit_panel_controller_unittest.mm
new file mode 100644
index 0000000..fddf164
--- /dev/null
+++ b/chrome/browser/cocoa/confirm_quit_panel_controller_unittest.mm
@@ -0,0 +1,26 @@
+// Copyright (c) 2010 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.
+
+#include "chrome/browser/cocoa/confirm_quit_panel_controller.h"
+
+#import "chrome/browser/cocoa/cocoa_test_helper.h"
+
+namespace {
+
+class ConfirmQuitPanelControllerTest : public CocoaTest {
+ public:
+ ConfirmQuitPanelControllerTest() : controller_(nil) {
+ }
+
+ ConfirmQuitPanelController* controller_; // Weak, owns self.
+};
+
+
+TEST_F(ConfirmQuitPanelControllerTest, ShowAndDismiss) {
+ controller_ = [[ConfirmQuitPanelController alloc] init];
+ [controller_ showWindow:nil];
+ [controller_ dismissPanel]; // Releases self.
+}
+
+} // namespace
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 98afc2d..8797a88 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -904,6 +904,8 @@
'browser/cocoa/collected_cookies_mac.mm',
'browser/cocoa/command_observer_bridge.h',
'browser/cocoa/command_observer_bridge.mm',
+ 'browser/cocoa/confirm_quit_panel_controller.h',
+ 'browser/cocoa/confirm_quit_panel_controller.mm',
'browser/cocoa/constrained_html_delegate_mac.mm',
'browser/cocoa/constrained_window_mac.h',
'browser/cocoa/constrained_window_mac.mm',
@@ -3618,6 +3620,7 @@
'app/nibs/CollectedCookies.xib',
'app/nibs/Cookies.xib',
'app/nibs/CookieDetailsView.xib',
+ 'app/nibs/ConfirmQuitPanel.xib',
'app/nibs/ContentBlockedCookies.xib',
'app/nibs/ContentBlockedImages.xib',
'app/nibs/ContentBlockedJavaScript.xib',
diff --git a/chrome/chrome_dll.gypi b/chrome/chrome_dll.gypi
index 0a08369..6149a39 100644
--- a/chrome/chrome_dll.gypi
+++ b/chrome/chrome_dll.gypi
@@ -207,6 +207,7 @@
'app/nibs/CollectedCookies.xib',
'app/nibs/Cookies.xib',
'app/nibs/CookieDetailsView.xib',
+ 'app/nibs/ConfirmQuitPanel.xib',
'app/nibs/ContentBlockedCookies.xib',
'app/nibs/ContentBlockedImages.xib',
'app/nibs/ContentBlockedJavaScript.xib',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 6b2c4e9..bcc0f91 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1138,6 +1138,7 @@
'browser/cocoa/cocoa_test_helper.mm',
'browser/cocoa/collected_cookies_mac_unittest.mm',
'browser/cocoa/command_observer_bridge_unittest.mm',
+ 'browser/cocoa/confirm_quit_panel_controller_unittest.mm',
'browser/cocoa/content_exceptions_window_controller_unittest.mm',
'browser/cocoa/content_setting_bubble_cocoa_unittest.mm',
'browser/cocoa/content_settings_dialog_controller_unittest.mm',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index ccbba43..8f0e242 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -387,6 +387,9 @@ const char kEnableCloudPrintProxy[] = "enable-cloud-print-proxy";
// Enables the Cloud Print dialog hosting code.
const char kEnableCloudPrint[] = "enable-cloud-print";
+// Enable the Confirm to Quit experiment.
+const char kEnableConfirmToQuit[] = "enable-confirm-to-quit";
+
// Enables establishing a backup TCP connection if a specified timeout is
// exceeded.
const char kEnableConnectBackupJobs[] = "enable-connect-backup-jobs";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 5b3c984..5e63f1b 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -121,6 +121,7 @@ extern const char kEnableBenchmarking[];
extern const char kEnableClearServerData[];
extern const char kEnableCloudPrintProxy[];
extern const char kEnableCloudPrint[];
+extern const char kEnableConfirmToQuit[];
extern const char kEnableConnectBackupJobs[];
extern const char kEnableContentPrefetch[];
extern const char kEnableDefaultApps[];