summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa
diff options
context:
space:
mode:
authormirandac@chromium.org <mirandac@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-31 21:45:17 +0000
committermirandac@chromium.org <mirandac@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-01-31 21:45:17 +0000
commit54bc0925f6430d3fd94fbfc8cabff0a41128701a (patch)
tree0596552395a069054716171e111bb5c971367290 /chrome/browser/cocoa
parent007cb731cd1d0af76a0688faa5d1e125c6f57db3 (diff)
downloadchromium_src-54bc0925f6430d3fd94fbfc8cabff0a41128701a.zip
chromium_src-54bc0925f6430d3fd94fbfc8cabff0a41128701a.tar.gz
chromium_src-54bc0925f6430d3fd94fbfc8cabff0a41128701a.tar.bz2
ExtensionInstalledBubble for Mac.
Adds ExtensionInstalledBubble.xib, which contains the framework for the bubble itself (icon view, close button, and three message fields). BUG= 26974 TEST= Install an extension. Bubble should show same information as windows bubble. Review URL: http://codereview.chromium.org/527012 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37671 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r--chrome/browser/cocoa/autocomplete_text_field_cell.mm13
-rw-r--r--chrome/browser/cocoa/autocomplete_text_field_cell_unittest.mm7
-rw-r--r--chrome/browser/cocoa/extension_installed_bubble_bridge.h27
-rw-r--r--chrome/browser/cocoa/extension_installed_bubble_bridge.mm25
-rw-r--r--chrome/browser/cocoa/extension_installed_bubble_controller.h99
-rw-r--r--chrome/browser/cocoa/extension_installed_bubble_controller.mm343
-rw-r--r--chrome/browser/cocoa/extension_installed_bubble_controller_unittest.mm201
-rw-r--r--chrome/browser/cocoa/extensions/browser_actions_controller.h3
-rw-r--r--chrome/browser/cocoa/extensions/browser_actions_controller.mm9
-rw-r--r--chrome/browser/cocoa/location_bar_view_mac.h29
-rw-r--r--chrome/browser/cocoa/location_bar_view_mac.mm65
-rw-r--r--chrome/browser/cocoa/toolbar_controller.h3
-rw-r--r--chrome/browser/cocoa/toolbar_controller.mm8
13 files changed, 823 insertions, 9 deletions
diff --git a/chrome/browser/cocoa/autocomplete_text_field_cell.mm b/chrome/browser/cocoa/autocomplete_text_field_cell.mm
index 8f54193..654742b 100644
--- a/chrome/browser/cocoa/autocomplete_text_field_cell.mm
+++ b/chrome/browser/cocoa/autocomplete_text_field_cell.mm
@@ -307,7 +307,12 @@ CGFloat WidthForKeyword(NSAttributedString* keywordString) {
LocationBarViewMac::PageActionImageView* view =
page_action_views_->ViewAt(index);
const NSImage* icon = view->GetImage();
- if (!icon || !view->IsVisible()) {
+
+ // If we are calculating space for a preview page action, the icon is still
+ // loading. We use this function only to get the correct x value for the
+ // extension installed bubble arrow.
+ if (!view->preview_enabled() &&
+ (!icon || !view->IsVisible())) {
return NSZeroRect;
}
@@ -333,7 +338,11 @@ CGFloat WidthForKeyword(NSAttributedString* keywordString) {
}
widthUsed += kIconHorizontalPad;
- return [self rightJustifyImage:[icon size]
+ // If we are calculating frame space for a preview, the icon is still
+ // loading -- use maximum size as a placeholder.
+ NSSize iconSize = view->GetImageSize();
+
+ return [self rightJustifyImage:iconSize
inRect:cellFrame
withMargin:widthUsed];
}
diff --git a/chrome/browser/cocoa/autocomplete_text_field_cell_unittest.mm b/chrome/browser/cocoa/autocomplete_text_field_cell_unittest.mm
index 56ab1f5..b1610b3 100644
--- a/chrome/browser/cocoa/autocomplete_text_field_cell_unittest.mm
+++ b/chrome/browser/cocoa/autocomplete_text_field_cell_unittest.mm
@@ -371,6 +371,13 @@ TEST_F(AutocompleteTextFieldCellTest, PageActionImageFrame) {
EXPECT_TRUE(NSIsEmptyRect([cell pageActionFrameForIndex:0 inFrame:bounds]));
EXPECT_TRUE(NSIsEmptyRect([cell pageActionFrameForIndex:1 inFrame:bounds]));
+ // Test preview page actions, as used by the extension installed bubble.
+ TestPageActionImageView preview_view;
+ list.Add(&preview_view);
+ EXPECT_TRUE(NSIsEmptyRect([cell pageActionFrameForIndex:2 inFrame:bounds]));
+ preview_view.set_preview_enabled(true);
+ EXPECT_FALSE(NSIsEmptyRect([cell pageActionFrameForIndex:2 inFrame:bounds]));
+
// One page action, no security icon.
page_action_view.SetVisible(true);
NSRect iconRect0 = [cell pageActionFrameForIndex:0 inFrame:bounds];
diff --git a/chrome/browser/cocoa/extension_installed_bubble_bridge.h b/chrome/browser/cocoa/extension_installed_bubble_bridge.h
new file mode 100644
index 0000000..7f933f3
--- /dev/null
+++ b/chrome/browser/cocoa/extension_installed_bubble_bridge.h
@@ -0,0 +1,27 @@
+// 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.
+
+// C++ bridge function to connect ExtensionInstallUI to the Cocoa-based
+// extension installed bubble.
+
+#ifndef CHROME_BROWSER_COCOA_EXTENSION_INSTALLED_BUBBLE_BRIDGE_H_
+#define CHROME_BROWSER_COCOA_EXTENSION_INSTALLED_BUBBLE_BRIDGE_H_
+
+#include "app/gfx/native_widget_types.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+class Browser;
+class Extension;
+
+namespace ExtensionInstalledBubbleCocoa {
+
+// This function is called by the ExtensionInstallUI when an extension has been
+// installed.
+void ShowExtensionInstalledBubble(gfx::NativeWindow window,
+ Extension* extension,
+ Browser* browser,
+ SkBitmap icon);
+}
+
+#endif // CHROME_BROWSER_COCOA_EXTENSION_INSTALLED_BUBBLE_BRIDGE_H_
diff --git a/chrome/browser/cocoa/extension_installed_bubble_bridge.mm b/chrome/browser/cocoa/extension_installed_bubble_bridge.mm
new file mode 100644
index 0000000..e43b956
--- /dev/null
+++ b/chrome/browser/cocoa/extension_installed_bubble_bridge.mm
@@ -0,0 +1,25 @@
+// 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 "extension_installed_bubble_bridge.h"
+
+#include "chrome/browser/browser.h"
+#import "chrome/browser/cocoa/extension_installed_bubble_controller.h"
+#include "chrome/common/extensions/extension.h"
+
+void ExtensionInstalledBubbleCocoa::ShowExtensionInstalledBubble(
+ gfx::NativeWindow window,
+ Extension* extension,
+ Browser* browser,
+ SkBitmap icon) {
+ // The controller is deallocated when the window is closed, so no need to
+ // worry about it here.
+ [[ExtensionInstalledBubbleController alloc]
+ initWithParentWindow:window
+ extension:extension
+ browser:browser
+ icon:icon];
+}
diff --git a/chrome/browser/cocoa/extension_installed_bubble_controller.h b/chrome/browser/cocoa/extension_installed_bubble_controller.h
new file mode 100644
index 0000000..880db3f
--- /dev/null
+++ b/chrome/browser/cocoa/extension_installed_bubble_controller.h
@@ -0,0 +1,99 @@
+// 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.
+
+#ifndef CHROME_BROWSER_COCOA_EXTENSION_INSTALLED_BUBBLE_CONTROLLER_H_
+#define CHROME_BROWSER_COCOA_EXTENSION_INSTALLED_BUBBLE_CONTROLLER_H_
+
+#import <Cocoa/Cocoa.h>
+#import "base/cocoa_protocols_mac.h"
+#include "base/scoped_ptr.h"
+#import "chrome/browser/cocoa/browser_window_controller.h"
+#import "chrome/browser/cocoa/info_bubble_view.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+class Browser;
+class Extension;
+class ExtensionLoadedNotificationObserver;
+
+@class HoverCloseButton;
+
+namespace extension_installed_bubble {
+
+// Maximum height or width of extension's icon (corresponds to Windows & GTK).
+const int kIconSize = 43;
+
+// Outer vertical margin for text, icon, and closing x.
+const int kOuterVerticalMargin = 15;
+
+// Inner vertical margin for text messages.
+const int kInnerVerticalMargin = 10;
+
+// We use a different kind of notification for each of these extension types.
+typedef enum {
+ kBrowserAction,
+ kPageAction,
+ kGeneric
+} ExtensionType;
+
+}
+
+// Controller for the extension installed bubble. This bubble pops up after
+// an extension has been installed to inform the user that the install happened
+// properly, and to let the user know how to manage this extension in the
+// future.
+@interface ExtensionInstalledBubbleController :
+ NSWindowController<NSWindowDelegate> {
+ @private
+ NSWindow* parentWindow_; // weak
+ Extension* extension_; // weak
+ Browser* browser_; // weak
+ scoped_nsobject<NSImage> icon_;
+
+ extension_installed_bubble::ExtensionType type_;
+
+ // Lets us register for EXTENSION_LOADED notifications. The actual
+ // notifications are sent to the observer object, which proxies them
+ // back to the controller.
+ scoped_ptr<ExtensionLoadedNotificationObserver> extensionObserver_;
+
+ // References below are weak, being obtained from the nib.
+ IBOutlet InfoBubbleView* infoBubbleView_;
+ IBOutlet HoverCloseButton* closeButton_;
+ IBOutlet NSImageView* iconImage_;
+ IBOutlet NSTextField* extensionInstalledMsg_;
+ IBOutlet NSTextField* pageActionInfoMsg_; // Only shown for page actions.
+ IBOutlet NSTextField* extensionInstalledInfoMsg_;
+}
+
+@property (readonly) Extension* extension;
+
+// Initialize the window, and then create observers to wait for the extension
+// to complete loading, or the browser window to close.
+- (id)initWithParentWindow:(NSWindow*)parentWindow
+ extension:(Extension*)extension
+ browser:(Browser*)browser
+ icon:(SkBitmap)icon;
+
+// Action for close button.
+- (IBAction)closeWindow:(id)sender;
+
+// Displays the extension installed bubble. This callback is triggered by
+// the extensionObserver when the extension has completed loading.
+- (void)showWindow:(id)sender;
+
+@end
+
+@interface ExtensionInstalledBubbleController(ExposedForTesting)
+
+- (void)removePageActionPreview;
+- (NSWindow*)initializeWindow;
+- (int)calculateWindowHeight;
+- (void)setMessageFrames:(int)newWindowHeight;
+- (NSRect)getExtensionInstalledMsgFrame;
+- (NSRect)getPageActionInfoMsgFrame;
+- (NSRect)getExtensionInstalledInfoMsgFrame;
+
+@end // ExtensionInstalledBubbleController(ExposedForTesting)
+
+#endif // CHROME_BROWSER_COCOA_EXTENSION_INSTALLED_BUBBLE_CONTROLLER_H_
diff --git a/chrome/browser/cocoa/extension_installed_bubble_controller.mm b/chrome/browser/cocoa/extension_installed_bubble_controller.mm
new file mode 100644
index 0000000..eba39e4
--- /dev/null
+++ b/chrome/browser/cocoa/extension_installed_bubble_controller.mm
@@ -0,0 +1,343 @@
+// 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 "extension_installed_bubble_controller.h"
+
+#include "app/l10n_util.h"
+#include "base/mac_util.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_window.h"
+#include "chrome/browser/cocoa/autocomplete_text_field_cell.h"
+#include "chrome/browser/cocoa/browser_window_cocoa.h"
+#include "chrome/browser/cocoa/browser_window_controller.h"
+#include "chrome/browser/cocoa/extensions/browser_actions_controller.h"
+#include "chrome/browser/cocoa/hover_close_button.h"
+#include "chrome/browser/cocoa/info_bubble_view.h"
+#include "chrome/browser/cocoa/location_bar_view_mac.h"
+#include "chrome/browser/cocoa/toolbar_controller.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_service.h"
+#include "grit/generated_resources.h"
+#import "skia/ext/skia_utils_mac.h"
+#import "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
+
+
+
+// C++ class that receives EXTENSION_LOADED notifications and proxies them back
+// to |controller|.
+class ExtensionLoadedNotificationObserver : public NotificationObserver {
+ public:
+ ExtensionLoadedNotificationObserver(
+ ExtensionInstalledBubbleController* controller)
+ : controller_(controller) {
+ // Create a registrar and add ourselves to it.
+ registrar_.Add(this, NotificationType::EXTENSION_LOADED,
+ NotificationService::AllSources());
+ }
+
+ private:
+ // NotificationObserver implementation. Tells the controller to start showing
+ // its window on the main thread when the extension has finished loading.
+ void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NotificationType::EXTENSION_LOADED) {
+ Extension* extension = Details<Extension>(details).ptr();
+ if (extension == [controller_ extension]) {
+ [controller_ performSelectorOnMainThread:@selector(showWindow:)
+ withObject:controller_
+ waitUntilDone:NO];
+ }
+ } else {
+ NOTREACHED() << "Received unexpected notification.";
+ }
+ }
+
+ NotificationRegistrar registrar_;
+ ExtensionInstalledBubbleController* controller_; // weak, owns us
+};
+
+@implementation ExtensionInstalledBubbleController
+
+@synthesize extension = extension_;
+
+- (id)initWithParentWindow:(NSWindow*)parentWindow
+ extension:(Extension*)extension
+ browser:(Browser*)browser
+ icon:(SkBitmap)icon {
+ NSString* nibPath =
+ [mac_util::MainAppBundle() pathForResource:@"ExtensionInstalledBubble"
+ ofType:@"nib"];
+ if ((self = [super initWithWindowNibPath:nibPath owner:self])) {
+ parentWindow_ = parentWindow;
+ extension_ = extension;
+ browser_ = browser;
+ icon_.reset([gfx::SkBitmapToNSImage(icon) retain]);
+
+ if (extension->browser_action()) {
+ type_ = extension_installed_bubble::kBrowserAction;
+ } else if (extension->page_action() &&
+ !extension->page_action()->default_icon_path().empty()) {
+ type_ = extension_installed_bubble::kPageAction;
+ } else {
+ NOTREACHED(); // kGeneric installs handled in the extension_install_ui.
+ }
+
+ // Start showing window only after extension has fully loaded.
+ extensionObserver_.reset(new ExtensionLoadedNotificationObserver(self));
+
+ // Watch to see if the parent window closes, and close this one if so.
+ NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(parentWindowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:parentWindow_];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (void)close {
+ [parentWindow_ removeChildWindow:[self window]];
+ [super close];
+}
+
+- (void)parentWindowWillClose:(NSNotification*)notification {
+ [self close];
+}
+
+- (void)windowWillClose:(NSNotification*)notification {
+ // Turn off page action icon preview when the window closes.
+ if (extension_->page_action()) {
+ [self removePageActionPreview];
+ }
+ // We caught a close so we don't need to watch for the parent closing.
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [self autorelease];
+}
+
+// The controller is the delegate of the window, so it receives "did resign
+// key" notifications. When key is resigned, close the window.
+- (void)windowDidResignKey:(NSNotification*)notification {
+ NSWindow* window = [self window];
+ DCHECK_EQ([notification object], window);
+ DCHECK([window isVisible]);
+ [self close];
+}
+
+- (IBAction)closeWindow:(id)sender {
+ DCHECK([[self window] isVisible]);
+ [self close];
+}
+
+// Extracted to a function here so that it can be overwritten for unit
+// testing.
+- (void)removePageActionPreview {
+ BrowserWindowCocoa* window = static_cast<BrowserWindowCocoa*>(
+ browser_->window());
+ LocationBarViewMac* locationBarView = static_cast<LocationBarViewMac*>(
+ [[window->cocoa_controller() toolbarController] locationBarBridge]);
+
+ locationBarView->SetPreviewEnabledPageAction(extension_->page_action(),
+ false); // disables preview.
+ [locationBarView->GetAutocompleteTextField() setNeedsDisplay:YES];
+}
+
+// The extension installed bubble points at the browser action icon or the
+// page action icon (shown as a preview), depending on the extension type.
+// We need to calculate the location of these icons and the size of the
+// message itself (which varies with the title of the extension) in order
+// to figure out the origin point for the extension installed bubble.
+// TODO(mirandac): add framework to easily test extension UI components!
+- (NSPoint)calculateArrowPoint {
+ BrowserWindowCocoa* window =
+ static_cast<BrowserWindowCocoa*>(browser_->window());
+ NSPoint arrowPoint;
+
+ switch(type_) {
+ case extension_installed_bubble::kBrowserAction: {
+ // Find the center of the bottom of the browser action icon.
+ NSView* button = [[[window->cocoa_controller() toolbarController]
+ browserActionsController] browserActionViewForExtension:extension_];
+ DCHECK(button);
+ NSRect boundsRect = [[[button window] contentView]
+ convertRect:[button frame]
+ fromView:[button superview]];
+ arrowPoint =
+ NSMakePoint(NSMinX(boundsRect) + NSWidth([button frame]) / 2,
+ NSMinY(boundsRect));
+ break;
+ }
+ case extension_installed_bubble::kPageAction: {
+ LocationBarViewMac* locationBarView =
+ static_cast<LocationBarViewMac*>(
+ [[window->cocoa_controller() toolbarController]
+ locationBarBridge]);
+ // Tell the location bar to show a preview of the page action icon, which
+ // would ordinarily only be displayed on a page of the appropriate type.
+ // We remove this preview when the extension installed bubble closes.
+ locationBarView->SetPreviewEnabledPageAction(extension_->page_action(),
+ true);
+
+ // Find the center of the bottom of the page action icon.
+ AutocompleteTextField* field =
+ locationBarView->GetAutocompleteTextField();
+ size_t index =
+ locationBarView->GetPageActionIndex(extension_->page_action());
+ NSView* browserContentWindow = [window->GetNativeHandle() contentView];
+ NSRect iconRect = [[field autocompleteTextFieldCell]
+ pageActionFrameForIndex:index inFrame:[field frame]];
+ NSRect boundsrect = [browserContentWindow convertRect:iconRect
+ fromView:[field superview]];
+ arrowPoint =
+ NSMakePoint(NSMinX(boundsrect) - NSWidth(boundsrect) / 2 - 1,
+ NSMinY(boundsrect));
+ break;
+ }
+ default: {
+ NOTREACHED() << "Generic extension type not allowed in install bubble.";
+ }
+ }
+ return arrowPoint;
+}
+
+// We want this to be a child of a browser window. addChildWindow:
+// (called from this function) will bring the window on-screen;
+// unfortunately, [NSWindowController showWindow:] will also bring it
+// on-screen (but will cause unexpected changes to the window's
+// position). We cannot have an addChildWindow: and a subsequent
+// showWindow:. Thus, we have our own version.
+- (void)showWindow:(id)sender {
+ // Generic extensions get an infobar rather than a bubble.
+ DCHECK(type_ != extension_installed_bubble::kGeneric);
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ // Load nib and calculate height based on messages to be shown.
+ NSWindow* window = [self initializeWindow];
+ int newWindowHeight = [self calculateWindowHeight];
+ NSSize windowDelta = NSMakeSize(
+ 0, newWindowHeight - NSHeight([[window contentView] bounds]));
+ [infoBubbleView_ setFrameSize:NSMakeSize(
+ NSWidth([[window contentView] bounds]), newWindowHeight)];
+ NSRect newFrame = [window frame];
+ newFrame.size.height += windowDelta.height;
+ [window setFrame:newFrame display:NO];
+
+ // Now that we have resized the window, adjust y pos of the messages.
+ [self setMessageFrames:newWindowHeight];
+
+ // Find window origin, taking into account bubble size and arrow location.
+ NSPoint origin =
+ [parentWindow_ convertBaseToScreen:[self calculateArrowPoint]];
+ origin.x -= NSWidth([window frame]) - kBubbleArrowXOffset -
+ kBubbleArrowWidth / 2;
+ origin.y -= NSHeight([window frame]);
+ [window setFrameOrigin:origin];
+
+ [parentWindow_ addChildWindow:window
+ ordered:NSWindowAbove];
+ [window makeKeyAndOrderFront:self];
+}
+
+// Finish nib loading, set arrow location and load icon into window. This
+// function is exposed for unit testing.
+- (NSWindow*)initializeWindow {
+ NSWindow* window = [self window]; // completes nib load
+ [infoBubbleView_ setArrowLocation:kTopRight];
+
+ // Set appropriate icon, resizing if necessary.
+ if ([icon_ size].width > extension_installed_bubble::kIconSize) {
+ [icon_ setSize:NSMakeSize(extension_installed_bubble::kIconSize,
+ extension_installed_bubble::kIconSize)];
+ }
+ [iconImage_ setImage:icon_];
+ [iconImage_ setNeedsDisplay:YES];
+ return window;
+ }
+
+// Calculate the height of each install message, resizing messages in their
+// frames to fit window width. Return the new window height, based on the
+// total of all message heights.
+- (int)calculateWindowHeight {
+ // Adjust the window height to reflect the sum height of all messages
+ // and vertical padding.
+ int newWindowHeight = 2 * extension_installed_bubble::kOuterVerticalMargin;
+
+ // First part of extension installed message.
+ [extensionInstalledMsg_ setStringValue:l10n_util::GetNSStringF(
+ IDS_EXTENSION_INSTALLED_HEADING, UTF8ToUTF16(extension_->name()))];
+ [GTMUILocalizerAndLayoutTweaker
+ sizeToFitFixedWidthTextField:extensionInstalledMsg_];
+ newWindowHeight += [extensionInstalledMsg_ frame].size.height +
+ extension_installed_bubble::kInnerVerticalMargin;
+
+ // If type is page action, include a special message about page actions.
+ if (type_ == extension_installed_bubble::kPageAction) {
+ [pageActionInfoMsg_ setHidden:NO];
+ [[pageActionInfoMsg_ cell]
+ setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [GTMUILocalizerAndLayoutTweaker
+ sizeToFitFixedWidthTextField:pageActionInfoMsg_];
+ newWindowHeight += [pageActionInfoMsg_ frame].size.height +
+ extension_installed_bubble::kInnerVerticalMargin;
+ }
+
+ // Second part of extension installed message.
+ [[extensionInstalledInfoMsg_ cell]
+ setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [GTMUILocalizerAndLayoutTweaker
+ sizeToFitFixedWidthTextField:extensionInstalledInfoMsg_];
+ newWindowHeight += [extensionInstalledInfoMsg_ frame].size.height;
+
+ return newWindowHeight;
+}
+
+// Adjust y-position of messages to sit properly in new window height.
+- (void)setMessageFrames:(int)newWindowHeight {
+ // The extension messages will always be shown.
+ NSRect extensionMessageFrame1 = [extensionInstalledMsg_ frame];
+ NSRect extensionMessageFrame2 = [extensionInstalledInfoMsg_ frame];
+
+ extensionMessageFrame1.origin.y = newWindowHeight - (
+ extensionMessageFrame1.size.height +
+ extension_installed_bubble::kOuterVerticalMargin);
+ [extensionInstalledMsg_ setFrame:extensionMessageFrame1];
+ if (type_ == extension_installed_bubble::kPageAction) {
+ // The page action message is only shown when appropriate.
+ NSRect pageActionMessageFrame = [pageActionInfoMsg_ frame];
+ pageActionMessageFrame.origin.y = extensionMessageFrame1.origin.y - (
+ pageActionMessageFrame.size.height +
+ extension_installed_bubble::kInnerVerticalMargin);
+ [pageActionInfoMsg_ setFrame:pageActionMessageFrame];
+ extensionMessageFrame2.origin.y = pageActionMessageFrame.origin.y - (
+ extensionMessageFrame2.size.height +
+ extension_installed_bubble::kInnerVerticalMargin);
+ } else {
+ extensionMessageFrame2.origin.y = extensionMessageFrame1.origin.y - (
+ extensionMessageFrame2.size.height +
+ extension_installed_bubble::kInnerVerticalMargin);
+ }
+ [extensionInstalledInfoMsg_ setFrame:extensionMessageFrame2];
+}
+
+// Exposed for unit testing.
+- (NSRect)getExtensionInstalledMsgFrame {
+ return [extensionInstalledMsg_ frame];
+}
+
+- (NSRect)getPageActionInfoMsgFrame {
+ return [pageActionInfoMsg_ frame];
+}
+
+- (NSRect)getExtensionInstalledInfoMsgFrame {
+ return [extensionInstalledInfoMsg_ frame];
+}
+
+@end
diff --git a/chrome/browser/cocoa/extension_installed_bubble_controller_unittest.mm b/chrome/browser/cocoa/extension_installed_bubble_controller_unittest.mm
new file mode 100644
index 0000000..8f1d4d5
--- /dev/null
+++ b/chrome/browser/cocoa/extension_installed_bubble_controller_unittest.mm
@@ -0,0 +1,201 @@
+// 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/basictypes.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/scoped_ptr.h"
+#import "chrome/browser/browser_window.h"
+#import "chrome/browser/cocoa/browser_test_helper.h"
+#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#import "chrome/browser/cocoa/extension_installed_bubble_controller.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "webkit/glue/image_decoder.h"
+
+// ExtensionInstalledBubbleController with removePageActionPreview overridden
+// to a no-op, because pageActions are not yet hooked up in the test browser.
+@interface ExtensionInstalledBubbleControllerForTest :
+ ExtensionInstalledBubbleController {
+ }
+
+ // Do nothing, because browser window is not set up with page actions
+ // for unit testing.
+- (void)removePageActionPreview;
+
+@end
+
+@implementation ExtensionInstalledBubbleControllerForTest
+
+ -(void)removePageActionPreview { }
+
+@end
+
+namespace keys = extension_manifest_keys;
+
+class ExtensionInstalledBubbleControllerTest : public CocoaTest {
+
+ public:
+ virtual void SetUp() {
+ CocoaTest::SetUp();
+ browser_ = helper_.browser();
+ window_ = helper_.CreateBrowserWindow()->GetNativeHandle();
+ icon_ = LoadTestIcon();
+ }
+
+ virtual void TearDown() {
+ helper_.CloseBrowserWindow();
+ CocoaTest::TearDown();
+ }
+
+ // Load test icon from extension test directory.
+ SkBitmap LoadTestIcon() {
+ FilePath path;
+ PathService::Get(chrome::DIR_TEST_DATA, &path);
+ path = path.AppendASCII("extensions").AppendASCII("icon1.png");
+
+ std::string file_contents;
+ file_util::ReadFileToString(path, &file_contents);
+ const unsigned char* data =
+ reinterpret_cast<const unsigned char*>(file_contents.data());
+
+ SkBitmap bitmap;
+ webkit_glue::ImageDecoder decoder;
+ bitmap = decoder.Decode(data, file_contents.length());
+
+ return bitmap;
+ }
+
+ // Create a skeletal framework of either page action or browser action
+ // type. This extension only needs to have a type and a name to initialize
+ // the ExtensionInstalledBubble for unit testing.
+ Extension* CreateExtension(extension_installed_bubble::ExtensionType type) {
+ FilePath path;
+ PathService::Get(chrome::DIR_TEST_DATA, &path);
+ path = path.AppendASCII("extensions").AppendASCII("dummy");
+
+ DictionaryValue extension_input_value;
+ extension_input_value.SetString(keys::kVersion, "1.0.0.0");
+ if (type == extension_installed_bubble::kPageAction) {
+ extension_input_value.SetString(keys::kName, "page action extension");
+ DictionaryValue* action = new DictionaryValue;
+ action->SetString(keys::kPageActionId, "ExtensionActionId");
+ action->SetString(keys::kPageActionDefaultTitle, "ExtensionActionTitle");
+ action->SetString(keys::kPageActionDefaultIcon, "image1.png");
+ ListValue* action_list = new ListValue;
+ action_list->Append(action);
+ extension_input_value.Set(keys::kPageActions, action_list);
+ } else {
+ extension_input_value.SetString(keys::kName, "browser action extension");
+ DictionaryValue* browser_action = new DictionaryValue;
+ // An empty dictionary is enough to create a Browser Action.
+ extension_input_value.Set(keys::kBrowserAction, browser_action);
+ }
+
+ Extension* extension = new Extension(path);
+ std::string error;
+ extension->InitFromValue(extension_input_value, false, &error);
+ return extension;
+ }
+
+ // Allows us to create the window and browser for testing.
+ BrowserTestHelper helper_;
+
+ // Required to initialize the extension installed bubble.
+ NSWindow* window_; // weak, owned by BrowserTestHelper.
+
+ // Required to initialize the extension installed bubble.
+ Browser* browser_; // weak, owned by BrowserTestHelper.
+
+ // The icon_ to be loaded into the bubble window.
+ SkBitmap icon_;
+};
+
+// Confirm that window sizes are set correctly for a page action extension.
+TEST_F(ExtensionInstalledBubbleControllerTest, PageActionTest) {
+ scoped_ptr<Extension> extension;
+ extension.reset(
+ CreateExtension(extension_installed_bubble::kPageAction));
+ ExtensionInstalledBubbleControllerForTest* controller =
+ [[ExtensionInstalledBubbleControllerForTest alloc]
+ initWithParentWindow:window_
+ extension:extension.get()
+ browser:browser_
+ icon:icon_];
+ EXPECT_TRUE(controller);
+
+ // Initialize window without having to calculate tabstrip locations.
+ [controller initializeWindow];
+ EXPECT_TRUE([controller window]);
+
+ int height = [controller calculateWindowHeight];
+ // Height should equal the vertical padding + height of all messages.
+ int correctHeight = 2 * extension_installed_bubble::kOuterVerticalMargin +
+ 2 * extension_installed_bubble::kInnerVerticalMargin +
+ [controller getExtensionInstalledMsgFrame].size.height +
+ [controller getExtensionInstalledInfoMsgFrame].size.height +
+ [controller getPageActionInfoMsgFrame].size.height;
+ EXPECT_EQ(height, correctHeight);
+
+ [controller setMessageFrames:height];
+ NSRect msg3Frame = [controller getExtensionInstalledInfoMsgFrame];
+ // Bottom message should be kOuterVerticalMargin pixels above window edge.
+ EXPECT_EQ(msg3Frame.origin.y,
+ extension_installed_bubble::kOuterVerticalMargin);
+ NSRect msg2Frame = [controller getPageActionInfoMsgFrame];
+ // Pageaction message should be kInnerVerticalMargin pixels above bottom msg.
+ EXPECT_EQ(msg2Frame.origin.y,
+ msg3Frame.origin.y + msg3Frame.size.height +
+ extension_installed_bubble::kInnerVerticalMargin);
+ NSRect msg1Frame = [controller getExtensionInstalledMsgFrame];
+ // Top message should be kInnerVerticalMargin pixels above Pageaction msg.
+ EXPECT_EQ(msg1Frame.origin.y,
+ msg2Frame.origin.y + msg2Frame.size.height +
+ extension_installed_bubble::kInnerVerticalMargin);
+
+ [controller close];
+}
+
+TEST_F(ExtensionInstalledBubbleControllerTest, BrowserActionTest) {
+ scoped_ptr<Extension> extension;
+ extension.reset(
+ CreateExtension(extension_installed_bubble::kBrowserAction));
+ ExtensionInstalledBubbleControllerForTest* controller =
+ [[ExtensionInstalledBubbleControllerForTest alloc]
+ initWithParentWindow:window_
+ extension:extension.get()
+ browser:browser_
+ icon:icon_];
+ EXPECT_TRUE(controller);
+
+ // Initialize window without having to calculate tabstrip locations.
+ [controller initializeWindow];
+ EXPECT_TRUE([controller window]);
+
+ int height = [controller calculateWindowHeight];
+ // Height should equal the vertical padding + height of all messages.
+ int correctHeight = 2 * extension_installed_bubble::kOuterVerticalMargin +
+ extension_installed_bubble::kInnerVerticalMargin +
+ [controller getExtensionInstalledMsgFrame].size.height +
+ [controller getExtensionInstalledInfoMsgFrame].size.height;
+ EXPECT_EQ(height, correctHeight);
+
+ [controller setMessageFrames:height];
+ NSRect msg3Frame = [controller getExtensionInstalledInfoMsgFrame];
+ // Bottom message should start kOuterVerticalMargin pixels above window edge.
+ EXPECT_EQ(msg3Frame.origin.y,
+ extension_installed_bubble::kOuterVerticalMargin);
+ NSRect msg1Frame = [controller getExtensionInstalledMsgFrame];
+ // Top message should start kInnerVerticalMargin pixels above top of
+ // extensionInstalled message, because page action message is hidden.
+ EXPECT_EQ(msg1Frame.origin.y,
+ msg3Frame.origin.y + msg3Frame.size.height +
+ extension_installed_bubble::kInnerVerticalMargin);
+
+ [controller close];
+}
diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.h b/chrome/browser/cocoa/extensions/browser_actions_controller.h
index 97d5130..1d38da6 100644
--- a/chrome/browser/cocoa/extensions/browser_actions_controller.h
+++ b/chrome/browser/cocoa/extensions/browser_actions_controller.h
@@ -78,6 +78,9 @@ extern NSString* const kBrowserActionsChangedNotification;
// Executes the action designated by the extension.
- (void)browserActionClicked:(BrowserActionButton*)sender;
+// Returns the NSView for the action button associated with an extension.
+- (NSView*)browserActionViewForExtension:(Extension*)extension;
+
@end // @interface BrowserActionsController
@interface BrowserActionsController(TestingAPI)
diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.mm b/chrome/browser/cocoa/extensions/browser_actions_controller.mm
index e17f12d..3d5cad9 100644
--- a/chrome/browser/cocoa/extensions/browser_actions_controller.mm
+++ b/chrome/browser/cocoa/extensions/browser_actions_controller.mm
@@ -280,6 +280,15 @@ class ExtensionsServiceObserverBridge : public NotificationObserver {
return selected_tab->controller().session_id().id();
}
+- (NSView*)browserActionViewForExtension:(Extension*)extension {
+ for (BrowserActionButton* button in buttonOrder_.get()) {
+ if ([button extension] == extension)
+ return button;
+ }
+ NOTREACHED();
+ return nil;
+}
+
- (NSButton*)buttonWithIndex:(int)index {
return [buttonOrder_ objectAtIndex:(NSUInteger)index];
}
diff --git a/chrome/browser/cocoa/location_bar_view_mac.h b/chrome/browser/cocoa/location_bar_view_mac.h
index 2d82997..be784b5 100644
--- a/chrome/browser/cocoa/location_bar_view_mac.h
+++ b/chrome/browser/cocoa/location_bar_view_mac.h
@@ -62,7 +62,7 @@ class LocationBarViewMac : public AutocompleteEditController,
}
virtual LocationBarTesting* GetLocationBarForTesting() { return this; }
- // Overriden from LocationBarTesting:
+ // Overridden from LocationBarTesting:
virtual int PageActionCount();
virtual int PageActionVisibleCount();
virtual ExtensionAction* GetPageAction(size_t index);
@@ -74,6 +74,25 @@ class LocationBarViewMac : public AutocompleteEditController,
// saved state from the tab (for tab switching).
void Update(const TabContents* tab, bool should_restore_state);
+ // Sets preview_enabled_ for the PageActionImageView associated with this
+ // |page_action|. If |preview_enabled|, the location bar will display the
+ // PageAction icon even if it has not been activated by the extension.
+ // This is used by the ExtensionInstalledBubble to preview what the icon
+ // will look like for the user upon installation of the extension.
+ void SetPreviewEnabledPageAction(ExtensionAction* page_action,
+ bool preview_enabled);
+
+ // Return the index of a given page_action.
+ size_t GetPageActionIndex(ExtensionAction* page_action);
+
+ // PageActionImageView is nested in LocationBarViewMac, and only needed
+ // here so that we can access the icon of a page action when preview_enabled_
+ // has been set.
+ class PageActionImageView;
+
+ // Return the PageActionImageView associated with |page_action|.
+ PageActionImageView* GetPageActionImageView(ExtensionAction* page_action);
+
virtual void OnAutocompleteAccept(const GURL& url,
WindowOpenDisposition disposition,
PageTransition::Type transition,
@@ -188,6 +207,12 @@ class LocationBarViewMac : public AutocompleteEditController,
void set_preview_enabled(bool enabled) { preview_enabled_ = enabled; }
+ bool preview_enabled() { return preview_enabled_; }
+
+ // Return the size of the image, or a default size if no image available
+ // and preview is enabled.
+ virtual NSSize GetImageSize();
+
// Either notify listeners or show a popup depending on the Page Action.
// Virtual so it can be overridden for testing.
virtual bool OnMousePressed(NSRect bounds);
@@ -255,7 +280,7 @@ class LocationBarViewMac : public AutocompleteEditController,
scoped_nsobject<NSString> tooltip_;
// This is used for post-install visual feedback. The page_action icon
- // is briefly shown even if it hasn't been enabled by it's extension.
+ // is briefly shown even if it hasn't been enabled by its extension.
bool preview_enabled_;
// Used to register for notifications received by NotificationObserver.
diff --git a/chrome/browser/cocoa/location_bar_view_mac.mm b/chrome/browser/cocoa/location_bar_view_mac.mm
index 52a28b2..ec592cb 100644
--- a/chrome/browser/cocoa/location_bar_view_mac.mm
+++ b/chrome/browser/cocoa/location_bar_view_mac.mm
@@ -328,6 +328,54 @@ int LocationBarViewMac::PageActionVisibleCount() {
return static_cast<int>(page_action_views_->VisibleCount());
}
+void LocationBarViewMac::SetPreviewEnabledPageAction(
+ ExtensionAction* page_action, bool preview_enabled) {
+ DCHECK(page_action);
+ Browser* browser = BrowserList::GetLastActive();
+ // GetLastActive returns NULL in current unit testing.
+ if (!browser)
+ return;
+ TabContents* contents = browser->GetSelectedTabContents();
+ DCHECK(contents);
+ page_action_views_->RefreshViews();
+
+ LocationBarViewMac::PageActionImageView* page_action_image_view =
+ GetPageActionImageView(page_action);
+ DCHECK(page_action_image_view);
+ if (!page_action_image_view)
+ return;
+
+ page_action_image_view->set_preview_enabled(preview_enabled);
+ page_action_image_view->UpdateVisibility(contents,
+ GURL(WideToUTF8(toolbar_model_->GetText())));
+
+ NotificationService::current()->Notify(
+ NotificationType::EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED,
+ Source<ExtensionAction>(page_action),
+ Details<TabContents>(contents));
+}
+
+size_t LocationBarViewMac::GetPageActionIndex(ExtensionAction* page_action) {
+ DCHECK(page_action);
+ for (size_t i = 0; i < page_action_views_->Count(); ++i) {
+ if (page_action_views_->ViewAt(i)->page_action() == page_action)
+ return i;
+ }
+ NOTREACHED();
+ return 0;
+}
+
+LocationBarViewMac::PageActionImageView*
+ LocationBarViewMac::GetPageActionImageView(ExtensionAction* page_action) {
+ DCHECK(page_action);
+ for (size_t i = 0; i < page_action_views_->Count(); ++i) {
+ if (page_action_views_->ViewAt(i)->page_action() == page_action)
+ return page_action_views_->ViewAt(i);
+ }
+ NOTREACHED();
+ return NULL;
+}
+
ExtensionAction* LocationBarViewMac::GetPageAction(size_t index) {
if (index < page_action_views_->Count())
return page_action_views_->ViewAt(index)->page_action();
@@ -544,6 +592,19 @@ LocationBarViewMac::PageActionImageView::~PageActionImageView() {
tracker_->StopTrackingImageLoad();
}
+NSSize LocationBarViewMac::PageActionImageView::GetImageSize() {
+ NSImage* image = GetImage();
+ if (preview_enabled_ && !image) {
+ return NSMakeSize(Extension::kPageActionIconMaxSize,
+ Extension::kPageActionIconMaxSize);
+ } else if (image) {
+ return [image size];
+ }
+ // Default value for image size is undefined when preview is not enabled.
+ NOTREACHED();
+ return NSMakeSize(0, 0);
+}
+
// Overridden from LocationBarImageView. Either notify listeners or show a
// popup depending on the Page Action.
bool LocationBarViewMac::PageActionImageView::OnMousePressed(NSRect bounds) {
@@ -603,6 +664,9 @@ void LocationBarViewMac::PageActionImageView::OnImageLoaded(SkBitmap* image,
tracker_ = NULL;
owner_->UpdatePageActions();
+
+ if (preview_enabled_)
+ [owner_->GetAutocompleteTextField() display];
}
void LocationBarViewMac::PageActionImageView::UpdateVisibility(
@@ -781,3 +845,4 @@ void LocationBarViewMac::PageActionViewList::OnMousePressed(NSRect iconFrame,
size_t index) {
ViewAt(index)->OnMousePressed(iconFrame);
}
+
diff --git a/chrome/browser/cocoa/toolbar_controller.h b/chrome/browser/cocoa/toolbar_controller.h
index 6cc130d..fe9c997 100644
--- a/chrome/browser/cocoa/toolbar_controller.h
+++ b/chrome/browser/cocoa/toolbar_controller.h
@@ -159,6 +159,8 @@ class ToolbarModel;
// Create and add the Browser Action buttons to the toolbar view.
- (void)createBrowserActionButtons;
+// Return the BrowserActionsController for this toolbar.
+- (BrowserActionsController*)browserActionsController;
@end
// A set of private methods used by tests, in the absence of "friends" in ObjC.
@@ -170,7 +172,6 @@ class ToolbarModel;
- (gfx::Rect)locationStackBounds;
// Return a hover button for the current event.
- (NSButton*)hoverButtonForEvent:(NSEvent*)theEvent;
-- (BrowserActionsController*)browserActionsController;
@end
#endif // CHROME_BROWSER_COCOA_TOOLBAR_CONTROLLER_H_
diff --git a/chrome/browser/cocoa/toolbar_controller.mm b/chrome/browser/cocoa/toolbar_controller.mm
index ba7075c..59b3c47 100644
--- a/chrome/browser/cocoa/toolbar_controller.mm
+++ b/chrome/browser/cocoa/toolbar_controller.mm
@@ -340,10 +340,6 @@ class PrefObserverBridge : public NotificationObserver {
return nil;
}
-- (BrowserActionsController*)browserActionsController {
- return browserActionsController_.get();
-}
-
- (void)mouseMoved:(NSEvent*)theEvent {
NSButton* targetView = [self hoverButtonForEvent:theEvent];
if (hoveredButton_ != targetView) {
@@ -724,6 +720,10 @@ class PrefObserverBridge : public NotificationObserver {
}
}
+- (BrowserActionsController*)browserActionsController {
+ return browserActionsController_.get();
+}
+
- (NSString*)view:(NSView*)view
stringForToolTip:(NSToolTipTag)tag
point:(NSPoint)point