Skip to content

Commit

Permalink
[mac] Implement a windowed extension install/permissions prompt.
Browse files Browse the repository at this point in the history
This allows extension install dialogs, and permissions upgrade prompts,
to be shown on mac without requiring a parent WebContents to attach a
sheet to. Currently, they hit NOTIMPLEMENTED().

The window is a titled NSPanel, centred on screen using [NSWindow
center]. The implementation mostly uses the existing
ExtensionInstallViewController, but puts it in a new, windowed-version
of ExtensionInstallDialogController.

Adds a test:
WindowedInstallDialogControllerBrowserTest.ShowInstallDialog that mimics
the way extension install and upgrade prompts are invoked via the app
launcher.

Screenshot at http://crbug.com/325030#c2

BUG=325030, 229094, 271809, 269151

Review URL: https://codereview.chromium.org/65043015

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@238542 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
tapted@chromium.org committed Dec 4, 2013
1 parent 496dbbe commit 0610ab5
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 57 deletions.
11 changes: 11 additions & 0 deletions base/mac/sdk_forward_declarations.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ enum {
};
typedef NSUInteger NSEventSwipeTrackingOptions;

enum {
NSWindowAnimationBehaviorDefault = 0,
NSWindowAnimationBehaviorNone = 2,
NSWindowAnimationBehaviorDocumentWindow = 3,
NSWindowAnimationBehaviorUtilityWindow = 4,
NSWindowAnimationBehaviorAlertPanel = 5
};
typedef NSInteger NSWindowAnimationBehavior;

@interface NSEvent (LionSDK)
+ (BOOL)isSwipeTrackingFromScrollEventsEnabled;

Expand Down Expand Up @@ -61,6 +70,8 @@ typedef NSUInteger NSEventSwipeTrackingOptions;

@interface NSWindow (LionSDK)
- (CGFloat)backingScaleFactor;
- (NSWindowAnimationBehavior)animationBehavior;
- (void)setAnimationBehavior:(NSWindowAnimationBehavior)newAnimationBehavior;
@end
#endif // MAC_OS_X_VERSION_10_7

Expand Down
21 changes: 1 addition & 20 deletions chrome/browser/ui/cocoa/browser_window_cocoa.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "base/command_line.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#import "base/mac/sdk_forward_declarations.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/strings/sys_string_conversions.h"
Expand Down Expand Up @@ -67,26 +68,6 @@
using content::SSLStatus;
using content::WebContents;

// Replicate specific 10.7 SDK declarations for building with prior SDKs.
#if !defined(MAC_OS_X_VERSION_10_7) || \
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7

enum {
NSWindowAnimationBehaviorDefault = 0,
NSWindowAnimationBehaviorNone = 2,
NSWindowAnimationBehaviorDocumentWindow = 3,
NSWindowAnimationBehaviorUtilityWindow = 4,
NSWindowAnimationBehaviorAlertPanel = 5
};
typedef NSInteger NSWindowAnimationBehavior;

@interface NSWindow (LionSDKDeclarations)
- (NSWindowAnimationBehavior)animationBehavior;
- (void)setAnimationBehavior:(NSWindowAnimationBehavior)newAnimationBehavior;
@end

#endif // MAC_OS_X_VERSION_10_7

namespace {

NSPoint GetPointForBubble(content::WebContents* web_contents,
Expand Down
11 changes: 1 addition & 10 deletions chrome/browser/ui/cocoa/browser_window_controller.mm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "base/command_line.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/mac_util.h"
#import "base/mac/sdk_forward_declarations.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h" // IDC_*
Expand Down Expand Up @@ -180,15 +181,6 @@ - (NSRect)_growBoxRect;
#if !defined(MAC_OS_X_VERSION_10_7) || \
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7

enum {
NSWindowAnimationBehaviorDefault = 0,
NSWindowAnimationBehaviorNone = 2,
NSWindowAnimationBehaviorDocumentWindow = 3,
NSWindowAnimationBehaviorUtilityWindow = 4,
NSWindowAnimationBehaviorAlertPanel = 5
};
typedef NSInteger NSWindowAnimationBehavior;

enum {
NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8
Expand All @@ -200,7 +192,6 @@ - (NSRect)_growBoxRect;

@interface NSWindow (LionSDKDeclarations)
- (void)setRestorable:(BOOL)flag;
- (void)setAnimationBehavior:(NSWindowAnimationBehavior)newAnimationBehavior;
@end

#endif // MAC_OS_X_VERSION_10_7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
#include "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_window.h"
#import "chrome/browser/ui/cocoa/extensions/extension_install_view_controller.h"
#import "chrome/browser/ui/cocoa/extensions/windowed_install_dialog_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "content/public/browser/web_contents.h"

Expand All @@ -21,13 +22,12 @@ void ShowExtensionInstallDialogImpl(
const ExtensionInstallPrompt::ShowParams& show_params,
ExtensionInstallPrompt::Delegate* delegate,
const ExtensionInstallPrompt::Prompt& prompt) {
// These objects will delete themselves when the dialog closes.
if (!show_params.parent_web_contents) {
// TODO(sail): Add support for showing the dialog without a parent window.
NOTIMPLEMENTED();
new WindowedInstallDialogController(show_params, delegate, prompt);
return;
}

// This object will delete itself when the dialog closes.
new ExtensionInstallDialogController(show_params, delegate, prompt);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ class MockExtensionInstallPromptDelegate
int abort_count_;
};

// Loads the install prompt test extension.
// Loads the test extension from the given test directory and manifest file.
scoped_refptr<extensions::Extension> LoadInstallPromptExtension(
const char* extension_dir_name,
const char* manifest_file);

// Loads the default install_prompt test extension.
scoped_refptr<extensions::Extension> LoadInstallPromptExtension();

// Loads the icon for the install prompt extension.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@
++abort_count_;
}

scoped_refptr<Extension> LoadInstallPromptExtension() {
scoped_refptr<extensions::Extension> LoadInstallPromptExtension(
const char* extension_dir_name,
const char* manifest_file) {
scoped_refptr<Extension> extension;

base::FilePath path;
PathService::Get(chrome::DIR_TEST_DATA, &path);
path = path.AppendASCII("extensions")
.AppendASCII("install_prompt")
.AppendASCII("extension.json");
.AppendASCII(extension_dir_name)
.AppendASCII(manifest_file);

std::string error;
JSONFileValueSerializer serializer(path);
Expand All @@ -50,6 +52,10 @@
return extension;
}

scoped_refptr<Extension> LoadInstallPromptExtension() {
return LoadInstallPromptExtension("install_prompt", "extension.json");
}

gfx::Image LoadInstallPromptIcon() {
base::FilePath path;
PathService::Get(chrome::DIR_TEST_DATA, &path);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2013 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.

#ifndef CHROME_BROWSER_UI_COCOA_EXTENSIONS_WINDOWED_INSTALL_DIALOG_CONTROLLER_H_
#define CHROME_BROWSER_UI_COCOA_EXTENSIONS_WINDOWED_INSTALL_DIALOG_CONTROLLER_H_

#import <Cocoa/Cocoa.h>

#include "base/gtest_prod_util.h"
#include "base/mac/scoped_nsobject.h"
#include "chrome/browser/extensions/extension_install_prompt.h"

@class ExtensionInstallViewController;
@class WindowedInstallController;

// Displays an app or extension install or permissions prompt as a standalone
// NSPanel.
class WindowedInstallDialogController
: public ExtensionInstallPrompt::Delegate {
public:
// Initializes the ExtensionInstallViewController and shows the window. This
// object will delete itself when the window is closed.
WindowedInstallDialogController(
const ExtensionInstallPrompt::ShowParams& show_params,
ExtensionInstallPrompt::Delegate* delegate,
const ExtensionInstallPrompt::Prompt& prompt);
virtual ~WindowedInstallDialogController();

// Invoked by the -[NSWindow windowWillClose:] notification after a dialog
// choice is invoked. Releases owned resources, then deletes |this|.
void OnWindowClosing();

// ExtensionInstallPrompt::Delegate:
virtual void InstallUIProceed() OVERRIDE;
virtual void InstallUIAbort(bool user_initiated) OVERRIDE;

private:
FRIEND_TEST_ALL_PREFIXES(WindowedInstallDialogControllerBrowserTest,
ShowInstallDialog);
ExtensionInstallViewController* GetViewController();

ExtensionInstallPrompt::Delegate* delegate_;
base::scoped_nsobject<WindowedInstallController> install_controller_;

DISALLOW_COPY_AND_ASSIGN(WindowedInstallDialogController);
};

#endif // CHROME_BROWSER_UI_COCOA_EXTENSIONS_WINDOWED_INSTALL_DIALOG_CONTROLLER_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2013 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 "chrome/browser/ui/cocoa/extensions/windowed_install_dialog_controller.h"

#import "base/mac/sdk_forward_declarations.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/sys_string_conversions.h"
#import "chrome/browser/ui/cocoa/extensions/extension_install_view_controller.h"
#include "ui/base/cocoa/window_size_constants.h"

@interface WindowedInstallController
: NSWindowController<NSWindowDelegate> {
@private
base::scoped_nsobject<ExtensionInstallViewController> installViewController_;
WindowedInstallDialogController* dialogController_; // Weak. Owns us.
}

@property(readonly, nonatomic) ExtensionInstallViewController* viewController;

- (id)initWithNavigator:(content::PageNavigator*)navigator
delegate:(WindowedInstallDialogController*)delegate
prompt:(const ExtensionInstallPrompt::Prompt&)prompt;

@end

WindowedInstallDialogController::WindowedInstallDialogController(
const ExtensionInstallPrompt::ShowParams& show_params,
ExtensionInstallPrompt::Delegate* delegate,
const ExtensionInstallPrompt::Prompt& prompt)
: delegate_(delegate) {
install_controller_.reset([[WindowedInstallController alloc]
initWithNavigator:show_params.navigator
delegate:this
prompt:prompt]);
[[install_controller_ window] makeKeyAndOrderFront:nil];
}

WindowedInstallDialogController::~WindowedInstallDialogController() {
DCHECK(!install_controller_);
DCHECK(!delegate_);
}

void WindowedInstallDialogController::OnWindowClosing() {
install_controller_.reset();
if (delegate_) {
delegate_->InstallUIAbort(false);
delegate_ = NULL;
}
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}

ExtensionInstallViewController*
WindowedInstallDialogController::GetViewController() {
return [install_controller_ viewController];
}

void WindowedInstallDialogController::InstallUIProceed() {
delegate_->InstallUIProceed();
delegate_ = NULL;
[[install_controller_ window] close];
}

void WindowedInstallDialogController::InstallUIAbort(bool user_initiated) {
delegate_->InstallUIAbort(user_initiated);
delegate_ = NULL;
[[install_controller_ window] close];
}

@implementation WindowedInstallController

- (id)initWithNavigator:(content::PageNavigator*)navigator
delegate:(WindowedInstallDialogController*)delegate
prompt:(const ExtensionInstallPrompt::Prompt&)prompt {
base::scoped_nsobject<NSWindow> controlledPanel(
[[NSPanel alloc] initWithContentRect:ui::kWindowSizeDeterminedLater
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO]);
if ((self = [super initWithWindow:controlledPanel])) {
dialogController_ = delegate;
installViewController_.reset([[ExtensionInstallViewController alloc]
initWithNavigator:navigator
delegate:delegate
prompt:prompt]);
NSWindow* window = [self window];

// Ensure the window does not display behind the app launcher window, and is
// otherwise hard to lose behind other windows (since it is not modal).
[window setLevel:NSDockWindowLevel];

// Animate the window when ordered in, the same way as an NSAlert.
if ([window respondsToSelector:@selector(setAnimationBehavior:)])
[window setAnimationBehavior:NSWindowAnimationBehaviorAlertPanel];

[window setTitle:base::SysUTF16ToNSString(prompt.GetDialogTitle())];
NSRect viewFrame = [[installViewController_ view] frame];
[window setFrame:[window frameRectForContentRect:viewFrame]
display:NO];
[window setContentView:[installViewController_ view]];
[window setDelegate:self];
[window center];
}
return self;
}

- (ExtensionInstallViewController*)viewController {
return installViewController_;
}

- (void)windowWillClose:(NSNotification*)notification {
[[self window] setDelegate:nil];
dialogController_->OnWindowClosing();
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2013 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 "chrome/browser/ui/cocoa/extensions/windowed_install_dialog_controller.h"

#include "base/run_loop.h"
#include "chrome/browser/ui/browser.h"
#import "chrome/browser/ui/cocoa/extensions/extension_install_prompt_test_utils.h"
#import "chrome/browser/ui/cocoa/extensions/extension_install_view_controller.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/common/extension.h"

namespace {

// Similar to ShowExtensionInstallDialogImpl except this allows the created
// dialog controller to be captured and manipulated for tests.
void TestingShowAppListInstallDialogController(
WindowedInstallDialogController** controller,
const ExtensionInstallPrompt::ShowParams& show_params,
ExtensionInstallPrompt::Delegate* delegate,
const ExtensionInstallPrompt::Prompt& prompt) {
*controller =
new WindowedInstallDialogController(show_params, delegate, prompt);
}

typedef InProcessBrowserTest WindowedInstallDialogControllerBrowserTest;

} // namespace

// Test for showing an extension install prompt with no parent WebContents.
IN_PROC_BROWSER_TEST_F(WindowedInstallDialogControllerBrowserTest,
ShowInstallDialog) {
// Construct a prompt with a NULL parent window, the way ExtensionEnableFlow
// will for the Mac app list. For testing, sets a NULL PageNavigator as well.
scoped_ptr<ExtensionInstallPrompt> prompt(
new ExtensionInstallPrompt(browser()->profile(), NULL, NULL));

WindowedInstallDialogController* controller = NULL;
chrome::MockExtensionInstallPromptDelegate delegate;
scoped_refptr<extensions::Extension> extension =
chrome::LoadInstallPromptExtension("permissions", "many-apis.json");
prompt->ConfirmInstall(
&delegate,
extension.get(),
base::Bind(&TestingShowAppListInstallDialogController, &controller));

// The prompt needs to load the image, which happens on the blocking pool.
content::BrowserThread::GetBlockingPool()->FlushForTesting();
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(controller);

base::scoped_nsobject<NSWindow> window(
[[[controller->GetViewController() view] window] retain]);
EXPECT_TRUE([window isVisible]);
EXPECT_TRUE([window delegate]);
EXPECT_EQ(0, delegate.abort_count());

// Press cancel to close the window.
[[controller->GetViewController() cancelButton] performClick:nil];
EXPECT_FALSE([window delegate]);
EXPECT_EQ(1, delegate.abort_count());

// Ensure the window is closed.
EXPECT_FALSE([window isVisible]);
}
Loading

0 comments on commit 0610ab5

Please sign in to comment.